mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-22 13:59:19 +02:00
EU7 scenery format and models streaming
This commit is contained in:
@@ -48,6 +48,7 @@ file(GLOB HEADERS "*.h"
|
|||||||
"global_include/*.h"
|
"global_include/*.h"
|
||||||
"simulation/*.h"
|
"simulation/*.h"
|
||||||
"scene/*.h"
|
"scene/*.h"
|
||||||
|
"scene/eu7/*.h"
|
||||||
"rendering/*.h"
|
"rendering/*.h"
|
||||||
"audio/*.h"
|
"audio/*.h"
|
||||||
"environment/*.h"
|
"environment/*.h"
|
||||||
@@ -82,6 +83,7 @@ option(GENERATE_PDB "Generate executable with program debugging symbols" ON)
|
|||||||
option(ENABLE_MCC "Enable multicore compilation" ON)
|
option(ENABLE_MCC "Enable multicore compilation" ON)
|
||||||
option(WITHDUMPGEN "Enable generating DMP files on crash" ON)
|
option(WITHDUMPGEN "Enable generating DMP files on crash" ON)
|
||||||
option(ENABLE_AVX2 "Enable AVX2 instruction set (requires AVX2-capable CPU, Haswell/2013 or newer)" ON)
|
option(ENABLE_AVX2 "Enable AVX2 instruction set (requires AVX2-capable CPU, Haswell/2013 or newer)" ON)
|
||||||
|
option(WITH_EU7_PARSER "Embed EU07 SCM→EU7 baker (eu07-parser)" ON)
|
||||||
|
|
||||||
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
@@ -148,6 +150,17 @@ set(SOURCES
|
|||||||
"stdafx.cpp"
|
"stdafx.cpp"
|
||||||
"input/messaging.cpp"
|
"input/messaging.cpp"
|
||||||
"scene/scene.cpp"
|
"scene/scene.cpp"
|
||||||
|
"scene/eu7/eu7_loader.cpp"
|
||||||
|
"scene/eu7/eu7_load_stats.cpp"
|
||||||
|
"scene/eu7/eu7_pack_bench.cpp"
|
||||||
|
"scene/eu7/eu7_section.cpp"
|
||||||
|
"scene/eu7/eu7_section_stream.cpp"
|
||||||
|
"scene/eu7/eu7_model_prefetch.cpp"
|
||||||
|
"scene/eu7/eu7_reader.cpp"
|
||||||
|
"scene/eu7/eu7_emit.cpp"
|
||||||
|
"scene/eu7/eu7_apply.cpp"
|
||||||
|
"scene/eu7/eu7_bake.cpp"
|
||||||
|
"scene/eu7/eu7_parameters.cpp"
|
||||||
"scene/scenenode.cpp"
|
"scene/scenenode.cpp"
|
||||||
"simulation/simulation.cpp"
|
"simulation/simulation.cpp"
|
||||||
"model/vertex.cpp"
|
"model/vertex.cpp"
|
||||||
@@ -392,9 +405,32 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
|
|||||||
add_compile_options("/utf-8")
|
add_compile_options("/utf-8")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
|
set(EU07_BAKE_LIBS "")
|
||||||
|
if(WITH_EU7_PARSER)
|
||||||
|
set(EU07_PARSER_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/eu07-parser/include")
|
||||||
|
if(NOT EXISTS "${EU07_PARSER_INCLUDE}/eu07/parser.hpp")
|
||||||
|
message(FATAL_ERROR "EU07 parser headers missing at ${EU07_PARSER_INCLUDE}. Sync eu07-parser/include from the parser repo.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(eu07_bake STATIC "scene/eu7/eu7_bake_parser.cpp")
|
||||||
|
target_compile_features(eu07_bake PUBLIC cxx_std_23)
|
||||||
|
set_target_properties(eu07_bake PROPERTIES CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON)
|
||||||
|
target_include_directories(eu07_bake PRIVATE "${EU07_PARSER_INCLUDE}")
|
||||||
|
target_link_libraries(eu07_bake PUBLIC Threads::Threads)
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(eu07_bake PRIVATE /utf-8 /Zc:__cplusplus /EHsc /wd4834)
|
||||||
|
endif()
|
||||||
|
list(APPEND EU07_BAKE_LIBS eu07_bake)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
|
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
|
||||||
|
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFINITIONS})
|
target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFINITIONS})
|
||||||
|
if(WITH_EU7_PARSER)
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE WITH_EU7_PARSER=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||||
"."
|
"."
|
||||||
@@ -442,6 +478,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
|||||||
target_precompile_headers(${PROJECT_NAME} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/stdafx.h>")
|
target_precompile_headers(${PROJECT_NAME} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/stdafx.h>")
|
||||||
set_source_files_properties("/glad/src/glad.c" "ref/dds-ktx/src/dds-ktx.c" "stb/stb_image.c"
|
set_source_files_properties("/glad/src/glad.c" "ref/dds-ktx/src/dds-ktx.c" "stb/stb_image.c"
|
||||||
"imgui/imgui.cpp" "imgui/imgui_demo.cpp" "imgui/imgui_draw.cpp" "imgui/imgui_widgets.cpp" "imgui/imgui_impl_glfw.cpp"
|
"imgui/imgui.cpp" "imgui/imgui_demo.cpp" "imgui/imgui_draw.cpp" "imgui/imgui_widgets.cpp" "imgui/imgui_impl_glfw.cpp"
|
||||||
|
"scene/eu7/eu7_bake_parser.cpp"
|
||||||
PROPERTIES SKIP_PRECOMPILE_HEADERS TRUE)
|
PROPERTIES SKIP_PRECOMPILE_HEADERS TRUE)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
@@ -483,8 +520,7 @@ else()
|
|||||||
target_link_libraries(${PROJECT_NAME} ${PNG_LIBRARIES})
|
target_link_libraries(${PROJECT_NAME} ${PNG_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
target_link_libraries(${PROJECT_NAME} Threads::Threads ${EU07_BAKE_LIBS})
|
||||||
target_link_libraries(${PROJECT_NAME} Threads::Threads)
|
|
||||||
|
|
||||||
find_package(GLM REQUIRED)
|
find_package(GLM REQUIRED)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${GLM_INCLUDE_DIR})
|
target_include_directories(${PROJECT_NAME} PRIVATE ${GLM_INCLUDE_DIR})
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "application/drivermode.h"
|
#include "application/drivermode.h"
|
||||||
#include "application/editormode.h"
|
#include "application/editormode.h"
|
||||||
#include "application/scenarioloadermode.h"
|
#include "application/scenarioloadermode.h"
|
||||||
|
#include "application/scenarioloaderuilayer.h"
|
||||||
|
#include "scene/eu7/eu7_load_stats.h"
|
||||||
|
#include "scene/eu7/eu7_section_stream.h"
|
||||||
#include "launcher/launchermode.h"
|
#include "launcher/launchermode.h"
|
||||||
|
|
||||||
#include "utilities/Globals.h"
|
#include "utilities/Globals.h"
|
||||||
@@ -707,9 +710,59 @@ void eu07_application::exit()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
eu07_application::extend_loading_overlay( std::chrono::milliseconds const duration ) {
|
||||||
|
if( duration.count() <= 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const until { std::chrono::steady_clock::now() + duration };
|
||||||
|
if( until > m_loading_overlay_until ) {
|
||||||
|
m_loading_overlay_until = until;
|
||||||
|
}
|
||||||
|
if( m_loading_overlay == nullptr ) {
|
||||||
|
m_loading_overlay = std::make_shared<scenarioloader_ui>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
eu07_application::loading_overlay_active() const {
|
||||||
|
if( m_modestack.empty() || m_modestack.top() != mode::driver ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( false == simulation::is_ready ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
eu07_application::sync_loading_overlay_state() {
|
||||||
|
if( m_loading_overlay == nullptr ) {
|
||||||
|
m_loading_overlay = std::make_shared<scenarioloader_ui>();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_loading_overlay->set_progress( 100.f, 0.f );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
eu07_application::render_loading_overlay() {
|
||||||
|
sync_loading_overlay_state();
|
||||||
|
if( m_loading_overlay == nullptr ) {
|
||||||
|
m_loading_overlay = std::make_shared<scenarioloader_ui>();
|
||||||
|
}
|
||||||
|
m_loading_overlay->render();
|
||||||
|
}
|
||||||
|
|
||||||
void eu07_application::render_ui()
|
void eu07_application::render_ui()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if( loading_overlay_active() ) {
|
||||||
|
render_loading_overlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_modestack.empty())
|
if (m_modestack.empty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -721,6 +774,12 @@ void eu07_application::render_ui()
|
|||||||
void eu07_application::begin_ui_frame()
|
void eu07_application::begin_ui_frame()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if( loading_overlay_active() ) {
|
||||||
|
sync_loading_overlay_state();
|
||||||
|
m_loading_overlay->begin_ui_frame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_modestack.empty())
|
if (m_modestack.empty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include "application/applicationmode.h"
|
#include "application/applicationmode.h"
|
||||||
#include "scripting/PyInt.h"
|
#include "scripting/PyInt.h"
|
||||||
#include "network/manager.h"
|
#include "network/manager.h"
|
||||||
@@ -55,6 +57,12 @@ public:
|
|||||||
render_ui();
|
render_ui();
|
||||||
void
|
void
|
||||||
begin_ui_frame();
|
begin_ui_frame();
|
||||||
|
void
|
||||||
|
extend_loading_overlay( std::chrono::milliseconds const Duration );
|
||||||
|
[[nodiscard]] bool
|
||||||
|
loading_overlay_active() const;
|
||||||
|
void
|
||||||
|
render_loading_overlay();
|
||||||
// switches application to specified mode
|
// switches application to specified mode
|
||||||
bool
|
bool
|
||||||
pop_mode();
|
pop_mode();
|
||||||
@@ -115,9 +123,12 @@ private:
|
|||||||
int init_modes();
|
int init_modes();
|
||||||
bool init_network();
|
bool init_network();
|
||||||
int run_crashgui();
|
int run_crashgui();
|
||||||
|
void sync_loading_overlay_state();
|
||||||
// members
|
// members
|
||||||
|
|
||||||
bool m_screenshot_queued = false;
|
bool m_screenshot_queued = false;
|
||||||
|
std::chrono::steady_clock::time_point m_loading_overlay_until {};
|
||||||
|
std::shared_ptr<class scenarioloader_ui> m_loading_overlay;
|
||||||
|
|
||||||
modeptr_array m_modes { nullptr }; // collection of available application behaviour modes
|
modeptr_array m_modes { nullptr }; // collection of available application behaviour modes
|
||||||
mode_stack m_modestack; // current behaviour mode
|
mode_stack m_modestack; // current behaviour mode
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public:
|
|||||||
void begin_ui_frame() {
|
void begin_ui_frame() {
|
||||||
if( m_userinterface != nullptr ) {
|
if( m_userinterface != nullptr ) {
|
||||||
m_userinterface->begin_ui_frame(); } }
|
m_userinterface->begin_ui_frame(); } }
|
||||||
inline
|
virtual
|
||||||
void set_progress( float const Progress = 0.f, float const Subtaskprogress = 0.f ) {
|
void set_progress( float const Progress = 0.f, float const Subtaskprogress = 0.f ) {
|
||||||
if( m_userinterface != nullptr ) {
|
if( m_userinterface != nullptr ) {
|
||||||
m_userinterface->set_progress( Progress, Subtaskprogress ); } }
|
m_userinterface->set_progress( Progress, Subtaskprogress ); } }
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "utilities/Timer.h"
|
#include "utilities/Timer.h"
|
||||||
#include "rendering/renderer.h"
|
#include "rendering/renderer.h"
|
||||||
#include "utilities/Logs.h"
|
#include "utilities/Logs.h"
|
||||||
|
#include "scene/eu7/eu7_section_stream.h"
|
||||||
|
#include "scene/eu7/eu7_pack_bench.h"
|
||||||
/*
|
/*
|
||||||
namespace input {
|
namespace input {
|
||||||
|
|
||||||
@@ -402,6 +404,36 @@ bool driver_mode::update()
|
|||||||
|
|
||||||
Timer::subsystem.sim_total.stop();
|
Timer::subsystem.sim_total.stop();
|
||||||
|
|
||||||
|
auto const world_presentable {
|
||||||
|
false == scene::eu7::section_stream_active()
|
||||||
|
|| scene::eu7::loading_screen_dismissed() };
|
||||||
|
auto const gameplay_stream {
|
||||||
|
scene::eu7::section_stream_active() && scene::eu7::loading_screen_dismissed() };
|
||||||
|
|
||||||
|
simulation::State.drain_deferred_eu7_trainsets( gameplay_stream ? 3.0 : 12.0 );
|
||||||
|
|
||||||
|
if( scene::eu7::section_stream_active() ) {
|
||||||
|
auto const stream_active {
|
||||||
|
simulation::is_ready || Application.loading_overlay_active() };
|
||||||
|
if( stream_active && scene::eu7::section_stream_needs_bootstrap() ) {
|
||||||
|
scene::eu7::kick_section_stream_bootstrap();
|
||||||
|
}
|
||||||
|
if( stream_active ) {
|
||||||
|
scene::eu7::update_section_stream( Global.pCamera.Pos );
|
||||||
|
scene::eu7::drain_section_stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( scene::eu7::pack_bench_stream_phase_active() ) {
|
||||||
|
static double s_last_stream_bench_log { 0.0 };
|
||||||
|
auto const now { Timer::GetTime() };
|
||||||
|
if( now - s_last_stream_bench_log >= 30.0
|
||||||
|
&& scene::eu7::pack_bench_stream().sections_finalized > 0 ) {
|
||||||
|
scene::eu7::log_pack_stream_bench();
|
||||||
|
s_last_stream_bench_log = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
simulation::Region->update_sounds();
|
simulation::Region->update_sounds();
|
||||||
audio::renderer.update(Global.iPause ? 0.0 : deltarealtime);
|
audio::renderer.update(Global.iPause ? 0.0 : deltarealtime);
|
||||||
|
|
||||||
@@ -410,7 +442,9 @@ bool driver_mode::update()
|
|||||||
|
|
||||||
GfxRenderer->Update(deltarealtime);
|
GfxRenderer->Update(deltarealtime);
|
||||||
|
|
||||||
simulation::is_ready = simulation::is_ready || ((simulation::Train != nullptr) && (simulation::Train->is_cab_initialized)) || (Global.local_start_vehicle == "ghostview");
|
simulation::is_ready = simulation::is_ready
|
||||||
|
|| ( ( simulation::Train != nullptr ) && ( simulation::Train->is_cab_initialized ) )
|
||||||
|
|| ( Global.local_start_vehicle == "ghostview" && world_presentable );
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -461,7 +495,10 @@ void driver_mode::enter()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maintenance method, called when the mode is deactivated
|
// maintenance method, called when the mode is deactivated
|
||||||
void driver_mode::exit() {}
|
void
|
||||||
|
driver_mode::exit() {
|
||||||
|
scene::eu7::flush_pack_stream_bench();
|
||||||
|
}
|
||||||
|
|
||||||
void driver_mode::on_key(int const Key, int const Scancode, int const Action, int const Mods)
|
void driver_mode::on_key(int const Key, int const Scancode, int const Action, int const Mods)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "rendering/renderer.h"
|
#include "rendering/renderer.h"
|
||||||
#include "utilities/Logs.h"
|
#include "utilities/Logs.h"
|
||||||
#include "utilities/translation.h"
|
#include "utilities/translation.h"
|
||||||
|
#include "scene/eu7/eu7_load_stats.h"
|
||||||
|
#include "scene/eu7/eu7_pack_bench.h"
|
||||||
|
#include "scene/eu7/eu7_section_stream.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr std::chrono::seconds kEu7LoadTimeout { 90 };
|
||||||
|
constexpr float kDeserializeBarShare { 70.f };
|
||||||
|
constexpr float kPackBarShare { 30.f };
|
||||||
|
} // namespace
|
||||||
|
|
||||||
scenarioloader_mode::scenarioloader_mode() {
|
scenarioloader_mode::scenarioloader_mode() {
|
||||||
m_userinterface = std::make_shared<scenarioloader_ui>();
|
m_userinterface = std::make_shared<scenarioloader_ui>();
|
||||||
@@ -30,6 +39,13 @@ bool scenarioloader_mode::init() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void scenarioloader_mode::update_bar_progress( float const progress ) {
|
||||||
|
m_bar_progress = std::max( m_bar_progress, std::clamp( progress, 0.f, 100.f ) );
|
||||||
|
if( m_userinterface != nullptr ) {
|
||||||
|
m_userinterface->set_progress( m_bar_progress, 0.f );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mode-specific update of simulation data. returns: false on error, true otherwise
|
// mode-specific update of simulation data. returns: false on error, true otherwise
|
||||||
bool scenarioloader_mode::update() {
|
bool scenarioloader_mode::update() {
|
||||||
if (!Global.ready_to_load)
|
if (!Global.ready_to_load)
|
||||||
@@ -48,17 +64,80 @@ bool scenarioloader_mode::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (simulation::State.deserialize_continue(state))
|
if( m_phase == load_phase::deserialize ) {
|
||||||
|
if( simulation::State.deserialize_continue( state ) ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
m_phase = load_phase::eu7_load;
|
||||||
|
m_eu7_load_started = std::chrono::steady_clock::now();
|
||||||
|
m_eu7_stream_primed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (invalid_scenery_exception &e) {
|
catch (invalid_scenery_exception &e) {
|
||||||
ErrorLog( "Bad init: scenario loading failed" );
|
ErrorLog( "Bad init: scenario loading failed" );
|
||||||
Application.pop_mode();
|
Application.pop_mode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( m_phase == load_phase::eu7_load ) {
|
||||||
|
if( scene::eu7::section_stream_active() ) {
|
||||||
|
simulation::State.drain_deferred_eu7_trainsets( 16.0 );
|
||||||
|
|
||||||
|
auto const position { scene::eu7::stream_loading_position() };
|
||||||
|
|
||||||
|
if( false == m_eu7_stream_primed ) {
|
||||||
|
if( scene::eu7::section_stream_needs_bootstrap() ) {
|
||||||
|
scene::eu7::kick_section_stream_bootstrap();
|
||||||
|
}
|
||||||
|
m_eu7_stream_primed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
scene::eu7::drain_section_stream();
|
||||||
|
|
||||||
|
auto const ring_progress {
|
||||||
|
scene::eu7::section_stream_ring_progress(
|
||||||
|
position,
|
||||||
|
scene::eu7::kSectionStreamBootstrapRadiusKm ) };
|
||||||
|
update_bar_progress(
|
||||||
|
kDeserializeBarShare + ring_progress * kPackBarShare );
|
||||||
|
|
||||||
|
auto const timed_out {
|
||||||
|
std::chrono::steady_clock::now() - m_eu7_load_started >= kEu7LoadTimeout };
|
||||||
|
auto const bootstrap_ready {
|
||||||
|
scene::eu7::section_stream_ready_around(
|
||||||
|
position,
|
||||||
|
scene::eu7::kSectionStreamBootstrapRadiusKm ) };
|
||||||
|
if(
|
||||||
|
bootstrap_ready
|
||||||
|
|| scene::eu7::section_stream_presentable_around(
|
||||||
|
position,
|
||||||
|
scene::eu7::kSectionStreamBootstrapRadiusKm ) ) {
|
||||||
|
WriteLog( "EU7 PACK: pierścień wokół pozycji startowej gotowy do pokazania" );
|
||||||
|
scene::eu7::dismiss_loading_screen();
|
||||||
|
m_phase = load_phase::finished;
|
||||||
|
}
|
||||||
|
else if( timed_out ) {
|
||||||
|
ErrorLog(
|
||||||
|
"EU7 PACK: timeout ładowania pierścienia wokół kamery — wchodzę w jazdę (ring=" +
|
||||||
|
std::to_string( static_cast<int>( ring_progress * 100.f ) ) + "%)" );
|
||||||
|
scene::eu7::dismiss_loading_screen();
|
||||||
|
m_phase = load_phase::finished;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
scene::eu7::dismiss_loading_screen();
|
||||||
|
m_phase = load_phase::finished;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLog( "Scenario loading time: " + std::to_string( std::chrono::duration_cast<std::chrono::seconds>( ( std::chrono::system_clock::now() - timestart ) ).count() ) + " seconds" );
|
WriteLog( "Scenario loading time: " + std::to_string( std::chrono::duration_cast<std::chrono::seconds>( ( std::chrono::system_clock::now() - timestart ) ).count() ) + " seconds" );
|
||||||
// TODO: implement and use next mode cue
|
scene::eu7::log_load_stats();
|
||||||
|
scene::eu7::log_pack_bench();
|
||||||
|
|
||||||
|
update_bar_progress( 100.f );
|
||||||
Application.pop_mode();
|
Application.pop_mode();
|
||||||
Application.push_mode( eu07_application::mode::driver );
|
Application.push_mode( eu07_application::mode::driver );
|
||||||
|
|
||||||
@@ -74,6 +153,9 @@ void scenarioloader_mode::enter() {
|
|||||||
// TBD: hide cursor in fullscreen mode?
|
// TBD: hide cursor in fullscreen mode?
|
||||||
Application.set_cursor( GLFW_CURSOR_NORMAL );
|
Application.set_cursor( GLFW_CURSOR_NORMAL );
|
||||||
|
|
||||||
|
m_phase = load_phase::deserialize;
|
||||||
|
m_eu7_stream_primed = false;
|
||||||
|
m_bar_progress = 0.f;
|
||||||
simulation::is_ready = false;
|
simulation::is_ready = false;
|
||||||
|
|
||||||
Application.set_title( Global.AppName + " (" + Global.SceneryFile + ")" );
|
Application.set_title( Global.AppName + " (" + Global.SceneryFile + ")" );
|
||||||
@@ -85,3 +167,15 @@ void scenarioloader_mode::exit() {
|
|||||||
simulation::Time.init( Global.starting_timestamp );
|
simulation::Time.init( Global.starting_timestamp );
|
||||||
simulation::Environment.init();
|
simulation::Environment.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void scenarioloader_mode::set_progress(
|
||||||
|
float const Progress,
|
||||||
|
float const Subtaskprogress ) {
|
||||||
|
if( m_phase != load_phase::deserialize ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const parser_progress {
|
||||||
|
std::max( Progress, Subtaskprogress ) };
|
||||||
|
update_bar_progress( parser_progress * kDeserializeBarShare / 100.f );
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,8 +13,21 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "simulation/simulation.h"
|
#include "simulation/simulation.h"
|
||||||
|
|
||||||
class scenarioloader_mode : public application_mode {
|
class scenarioloader_mode : public application_mode {
|
||||||
|
|
||||||
|
enum class load_phase {
|
||||||
|
deserialize,
|
||||||
|
eu7_load,
|
||||||
|
finished
|
||||||
|
};
|
||||||
|
|
||||||
std::shared_ptr<simulation::deserializer_state> state;
|
std::shared_ptr<simulation::deserializer_state> state;
|
||||||
std::chrono::system_clock::time_point timestart;
|
std::chrono::system_clock::time_point timestart;
|
||||||
|
load_phase m_phase { load_phase::deserialize };
|
||||||
|
std::chrono::steady_clock::time_point m_eu7_load_started {};
|
||||||
|
bool m_eu7_stream_primed { false };
|
||||||
|
float m_bar_progress { 0.f };
|
||||||
|
|
||||||
|
void update_bar_progress( float progress );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// constructors
|
// constructors
|
||||||
@@ -28,6 +41,7 @@ public:
|
|||||||
void enter() override;
|
void enter() override;
|
||||||
// maintenance method, called when the mode is deactivated
|
// maintenance method, called when the mode is deactivated
|
||||||
void exit() override;
|
void exit() override;
|
||||||
|
void set_progress( float Progress = 0.f, float Subtaskprogress = 0.f ) override;
|
||||||
// input handlers
|
// input handlers
|
||||||
void on_key( int const Key, int const Scancode, int const Action, int const Mods ) override { ; }
|
void on_key( int const Key, int const Scancode, int const Action, int const Mods ) override { ; }
|
||||||
void on_cursor_pos( double const Horizontal, double const Vertical ) override { ; }
|
void on_cursor_pos( double const Horizontal, double const Vertical ) override { ; }
|
||||||
|
|||||||
@@ -195,6 +195,10 @@ void scenarioloader_ui::render_()
|
|||||||
ImGui::SetNextWindowSize(ImVec2(Global.window_size.x + padding * 2, Global.window_size.y + padding * 2));
|
ImGui::SetNextWindowSize(ImVec2(Global.window_size.x + padding * 2, Global.window_size.y + padding * 2));
|
||||||
ImGui::Begin("Neo Loading Screen", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground);
|
ImGui::Begin("Neo Loading Screen", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground);
|
||||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
|
draw_list->AddRectFilled(
|
||||||
|
ImVec2( 0.f, 0.f ),
|
||||||
|
ImVec2( screen_size.x, screen_size.y ),
|
||||||
|
IM_COL32( 0, 0, 0, 255 ) );
|
||||||
ImGui::PushFont(font_loading);
|
ImGui::PushFont(font_loading);
|
||||||
ImGui::SetWindowFontScale(1);
|
ImGui::SetWindowFontScale(1);
|
||||||
const float font_scale_mult = 48 / ImGui::GetFontSize();
|
const float font_scale_mult = 48 / ImGui::GetFontSize();
|
||||||
@@ -228,7 +232,14 @@ void scenarioloader_ui::render_()
|
|||||||
icon_center_y - text_size.y * 0.5f);
|
icon_center_y - text_size.y * 0.5f);
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
//draw_list->AddImage(reinterpret_cast<ImTextureID>(loading_tex), icon_pos, ImVec2(icon_pos.x + loading_size.x, icon_pos.y + loading_size.y), ImVec2(0, 0), ImVec2(1, 1));
|
if( loading_tex != static_cast<GLuint>( -1 ) ) {
|
||||||
|
draw_list->AddImage(
|
||||||
|
reinterpret_cast<ImTextureID>( loading_tex ),
|
||||||
|
icon_pos,
|
||||||
|
ImVec2( icon_pos.x + loading_size.x, icon_pos.y + loading_size.y ),
|
||||||
|
ImVec2( 0, 0 ),
|
||||||
|
ImVec2( 1, 1 ) );
|
||||||
|
}
|
||||||
draw_list->AddText(text_pos, IM_COL32_WHITE, m_progresstext.c_str());
|
draw_list->AddText(text_pos, IM_COL32_WHITE, m_progresstext.c_str());
|
||||||
|
|
||||||
// Trivia
|
// Trivia
|
||||||
|
|||||||
951
docs/eu7-format.md
Normal file
951
docs/eu7-format.md
Normal file
@@ -0,0 +1,951 @@
|
|||||||
|
# Format pliku EU7B (`.eu7`)
|
||||||
|
|
||||||
|
> Dokumentacja oparta na kodzie źródłowym z:
|
||||||
|
> - `scene/eu7/` — runtime reader/loader (maszyna-fresh)
|
||||||
|
> - `parser/include/eu07/scene/binary/` — writer/reader (narzędzie CLI)
|
||||||
|
>
|
||||||
|
> Stan: **czerwiec 2026**, obejmuje wersje v4–v8.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spis treści
|
||||||
|
|
||||||
|
1. [Przegląd ogólny](#1-przegląd-ogólny)
|
||||||
|
2. [Nagłówek pliku i wersje](#2-nagłówek-pliku-i-wersje)
|
||||||
|
3. [Struktura chunku](#3-struktura-chunku)
|
||||||
|
4. [Kolejność i reguły chunkowania](#4-kolejność-i-reguły-chunkowania)
|
||||||
|
5. [Typy podstawowe i kodowanie](#5-typy-podstawowe-i-kodowanie)
|
||||||
|
6. [Slim Node — wspólny nagłówek węzła](#6-slim-node--wspólny-nagłówek-węzła)
|
||||||
|
7. [Transform Context](#7-transform-context)
|
||||||
|
8. [Packed vertex — pakowany wierzchołek](#8-packed-vertex--pakowany-wierzchołek)
|
||||||
|
9. [Lighting block — blok oświetlenia](#9-lighting-block--blok-oświetlenia)
|
||||||
|
10. [Catalog chunkowy — zestawienie](#10-catalog-chunkowy--zestawienie)
|
||||||
|
11. [STRS — tablica łańcuchów](#11-strs--tablica-łańcuchów)
|
||||||
|
12. [INCL — referencje do podmodułów](#12-incl--referencje-do-podmodułów)
|
||||||
|
13. [TRAK — tory](#13-trak--tory)
|
||||||
|
14. [TRAC — przewody trakcyjne](#14-trac--przewody-trakcyjne)
|
||||||
|
15. [PWRS — źródła zasilania trakcji](#15-pwrs--źródła-zasilania-trakcji)
|
||||||
|
16. [TERR — teren (terrain shapes)](#16-terr--teren-terrain-shapes)
|
||||||
|
17. [MESH — kształty geometryczne](#17-mesh--kształty-geometryczne)
|
||||||
|
18. [LINE — linie](#18-line--linie)
|
||||||
|
19. [MODL — instancje modeli 3D](#19-modl--instancje-modeli-3d)
|
||||||
|
20. [MEMC — komórki pamięci](#20-memc--komórki-pamięci)
|
||||||
|
21. [LAUN — wyzwalacze zdarzeń](#21-laun--wyzwalacze-zdarzeń)
|
||||||
|
22. [DYNM — pojazdy dynamiczne](#22-dynm--pojazdy-dynamiczne)
|
||||||
|
23. [SOND — dźwięki otoczenia](#23-sond--dźwięki-otoczenia)
|
||||||
|
24. [TRSE — zestawy wagonowe](#24-trse--zestawy-wagonowe)
|
||||||
|
25. [EVNT — zdarzenia](#25-evnt--zdarzenia)
|
||||||
|
26. [FINT — licznik first-init](#26-fint--licznik-first-init)
|
||||||
|
27. [PLAC — parametry umieszczenia include](#27-plac--parametry-umieszczenia-include)
|
||||||
|
28. [PIDX — indeks sekcji PACK (v7+)](#28-pidx--indeks-sekcji-pack-v7)
|
||||||
|
29. [PACK — instancje modeli per sekcja 1 km (v7+)](#29-pack--instancje-modeli-per-sekcja-1-km-v7)
|
||||||
|
30. [PROT — prototypy modeli (v8+)](#30-prot--prototypy-modeli-v8)
|
||||||
|
31. [Semantyka strumieniowania PACK w runtime](#31-semantyka-strumieniowania-pack-w-runtime)
|
||||||
|
32. [Historia wersji](#32-historia-wersji)
|
||||||
|
33. [Ścieżki plików i konwencje nazewnictwa](#33-ścieżki-plików-i-konwencje-nazewnictwa)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Przegląd ogólny
|
||||||
|
|
||||||
|
Plik `.eu7` (EU7B — **EU7 Binary**) to binarny format zapisu skompilowanego modułu scenerii symulatora EU07. Zastępuje tekst source (`*.scm`, `*.inc`, `*.scn`) jednym zoptymalizowanym plikiem binarnym gotowym do szybkiego wczytania przez silnik.
|
||||||
|
|
||||||
|
**Kluczowe cechy:**
|
||||||
|
- **Little-endian** dla wszystkich liczb całkowitych i zmiennoprzecinkowych
|
||||||
|
- **Chunki samoopisowe** — nieznane chunki mogą być pomijane (`seekg` po `chunk_size`)
|
||||||
|
- **Tablica łańcuchów (STRS)** — wszystkie stringi współdzielone przez indeks `uint32`
|
||||||
|
- **Strumieniowanie sekcji 1 km** — w plikach głównych scenariusza modele są dzielone na kafelki 1 km × 1 km (chunki PIDX + PACK), wczytywane asynchronicznie
|
||||||
|
- **Format skaluje się** od małych modułów include (kilka KB) do głównych scenariuszy (setki MB w PACK)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Nagłówek pliku i wersje
|
||||||
|
|
||||||
|
### Layout nagłówka
|
||||||
|
|
||||||
|
```
|
||||||
|
Offset Rozmiar Typ Opis
|
||||||
|
------ ------- -------- -------------------------
|
||||||
|
0 4 char[4] Magic = 'EU7B' (0x45 0x55 0x37 0x42)
|
||||||
|
4 4 uint32 Version (patrz tabela poniżej)
|
||||||
|
8 ... chunks Strumień chunkowy (do EOF)
|
||||||
|
```
|
||||||
|
|
||||||
|
Nie ma pola "łączny rozmiar pliku" w nagłówku. Po nagłówku następują chunki aż do EOF.
|
||||||
|
|
||||||
|
### Tabela wersji
|
||||||
|
|
||||||
|
| Wartość | Stała (maszyna-fresh) | Stała (parser CLI) | Opis |
|
||||||
|
|---------|-------------------------|---------------------------|----------------------------------------------------------------|
|
||||||
|
| `1` | — | `kVersionLegacy` | Legacy — **nieobsługiwane** w CLI ani runtime |
|
||||||
|
| `2` | — | `kVersionRuntimeF64` | Skalary i Vec3 jako `double` (float64) na dysku |
|
||||||
|
| `3` | — | `kVersionRuntimeF32` | Skalary i Vec3 jako `float32` na dysku |
|
||||||
|
| `4` | `kEu7VersionV4` | `kVersionRuntimeV4` | Slim node + packed mesh (snorm16 normy + half-float UV) |
|
||||||
|
| `5` | `kEu7VersionV5` | `kVersionRuntimeV5` | Adds TERR z batched terrain per sekcja 1 km |
|
||||||
|
| `6` | `kEu7VersionV6` | `kVersionRuntime` | Adds site transform w rekordach INCL |
|
||||||
|
| `7` | `kEu7VersionV7` | `kVersionRuntimeV7` | Adds PIDX + PACK — modele scenerii per sekcja 1 km (streaming) |
|
||||||
|
| `8` | `kEu7VersionV8` | — | Adds PROT (prototypy) + nowy format sekcji PACK v8 |
|
||||||
|
|
||||||
|
**Runtime maszyna-fresh obsługuje wersje 4–8.** Wersje 1–3 nie są obsługiwane przez żaden z dostępnych loaderów.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Struktura chunku
|
||||||
|
|
||||||
|
```
|
||||||
|
Offset Rozmiar Typ Opis
|
||||||
|
------ ------- ------- -----------------------------------------
|
||||||
|
0 4 uint32 Chunk ID — FourCC little-endian (4 znaki ASCII)
|
||||||
|
4 4 uint32 Chunk total size — ŁĄCZNIE z nagłówkiem (≥ 8)
|
||||||
|
8 ... bytes Payload (rozmiar = chunk_total_size - 8)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Uwaga:** pole `chunk_total_size` **wlicza** 8 bajtów własnego nagłówka. Aby obliczyć rozmiar payloadu: `payload_size = chunk_total_size - 8`.
|
||||||
|
|
||||||
|
Strumień chunkowy nie ma terminator record. Parsowanie trwa do momentu napotkania EOF.
|
||||||
|
|
||||||
|
### FourCC — sposób kodowania
|
||||||
|
|
||||||
|
ID chunku jest 4-znakowym ASCII wpisanym w kolejności little-endian do `uint32`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr uint32_t make_id4(char a, char b, char c, char d) {
|
||||||
|
return (uint32(d) << 24) | (uint32(c) << 16) | (uint32(b) << 8) | uint32(a);
|
||||||
|
}
|
||||||
|
// Np. kChunkPack = make_id4('P','A','C','K')
|
||||||
|
// Bytes w pliku: 50 41 43 4B → odczytane jako uint32 LE = 0x4B434150
|
||||||
|
```
|
||||||
|
|
||||||
|
Przy odczycie człowiek widzi w hex-dumpie bajty w kolejności `P A C K`, co odpowiada oczytaniu `'PACK'` wprost.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Kolejność i reguły chunkowania
|
||||||
|
|
||||||
|
1. **STRS** musi wystąpić **przed** wszystkimi chunkiami używającymi indeksów string (w praktyce jest zawsze pierwszym chunkiem).
|
||||||
|
2. **PACK** jest **pomijany przy pełnym parse** — runtime zapamiętuje jedynie offset początku payloadu i rozmiar. Faktyczne dane są czytane on-demand przez `read_pack_section()`.
|
||||||
|
3. **PROT** musi wystąpić **przed** PACK (prototypy muszą być załadowane do pamięci zanim sekcje PACK będą rozwijane).
|
||||||
|
4. **PIDX** musi wystąpić przed próbą streamingu (runtime czyta PIDX przy załadowaniu modułu).
|
||||||
|
5. Nierozpoznane chunki są **ignorowane** — reader skacze do `chunk_start + payload_size`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Typy podstawowe i kodowanie
|
||||||
|
|
||||||
|
Wszystkie typy całkowite i zmiennoprzecinkowe — **little-endian**.
|
||||||
|
|
||||||
|
| Typ w pliku | C++ typ docelowy | Rozmiar | Opis |
|
||||||
|
|-------------|------------------|---------|---------------------------------------------------|
|
||||||
|
| `uint8` | `uint8_t` | 1 B | Bajt bez znaku |
|
||||||
|
| `int16` | `int16_t` | 2 B | LE, ze znakiem |
|
||||||
|
| `uint16` | `uint16_t` | 2 B | LE, bez znaku |
|
||||||
|
| `int32` | `int32_t` | 4 B | LE, ze znakiem |
|
||||||
|
| `uint32` | `uint32_t` | 4 B | LE, bez znaku |
|
||||||
|
| `uint64` | `uint64_t` | 8 B | LE, bez znaku (lo-word najpierw) |
|
||||||
|
| `float32` | `float` | 4 B | IEEE 754 single precision |
|
||||||
|
| `f64_disk` | `double` | 4 B | **float32 na dysku** — wczytywany jako double w RAM |
|
||||||
|
| `Vec3` | `glm::dvec3` | 12 B | 3× `f64_disk` (= 3× float32, razem 12 B) |
|
||||||
|
| `snorm16` | `float` | 2 B | int16 / 32767.0 → zakres [-1, +1] |
|
||||||
|
| `half16` | `float` | 2 B | IEEE 754 half precision |
|
||||||
|
| `string_id` | `uint32_t` | 4 B | Indeks do tablicy STRS; `0xFFFFFFFF` = pusty |
|
||||||
|
|
||||||
|
> **`f64_disk`**: Od wersji v3 wzwyż skalary (dystanse, kąty, napięcia itp.) i współrzędne Vec3 są zapisywane jako **float32** (4 B), mimo że runtime przechowuje je jako `double`. Patrz: `io::writeF64()` w `io.hpp` — wewnętrznie rzutuje do `float` przed zapisem.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Slim Node — wspólny nagłówek węzła
|
||||||
|
|
||||||
|
Każdy obiekt sceny (tor, model, dźwięk itp.) poprzedzony jest **slim node** — skompresowanym nagłówkiem z opcjonalnymi polami sterowanymi bajtem flag.
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
Offset Rozmiar Typ Opis
|
||||||
|
------ ------- --------- -------------------------------------------
|
||||||
|
0 1 uint8 flags (bitmaska, patrz poniżej)
|
||||||
|
--- poniższe pola są OPCJONALNE, zależne od flags ---
|
||||||
|
+0 4 string_id name (jeśli bit 0 set)
|
||||||
|
+0 4 f64_disk range_squared_min (jeśli bit 1 set)
|
||||||
|
+0 4 f64_disk range_squared_max (jeśli bit 2 set; domyślnie +∞)
|
||||||
|
+0 16 Vec3+f32 bounding sphere: center(Vec3) + radius(float32) (jeśli bit 3)
|
||||||
|
+0 4 uint32 group_handle (jeśli bit 4 set)
|
||||||
|
+0 ... Transform transform context (jeśli bit 5 set)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bitmaska `flags`
|
||||||
|
|
||||||
|
| Bit | Stała | Znaczenie |
|
||||||
|
|-----|--------------------------|-----------------------------------------------------|
|
||||||
|
| 0 | `kNodeFlagHasName` | pole `name` (string_id) jest obecne |
|
||||||
|
| 1 | `kNodeFlagHasRangeMin` | pole `range_squared_min` (f64_disk) jest obecne |
|
||||||
|
| 2 | `kNodeFlagHasRangeMax` | pole `range_squared_max` jest obecne (inaczej +∞) |
|
||||||
|
| 3 | `kNodeFlagHasBounds` | bounding sphere: Vec3 center + float32 radius |
|
||||||
|
| 4 | `kNodeFlagHasGroup` | pole `group_handle` (uint32) jest obecne |
|
||||||
|
| 5 | `kNodeFlagHasTransform` | transform context jest obecny (patrz sekcja 7) |
|
||||||
|
| 6 | `kNodeFlagNotVisible` | węzeł domyślnie niewidoczny (`visible = false`) |
|
||||||
|
|
||||||
|
### Bounding sphere
|
||||||
|
|
||||||
|
```
|
||||||
|
Vec3 center (12 B)
|
||||||
|
float32 radius (4 B)
|
||||||
|
```
|
||||||
|
|
||||||
|
Łącznie 16 B, obecne tylko gdy bit 3 set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Transform Context
|
||||||
|
|
||||||
|
Kontekst transformacji lokacji węzła — stos offsetów i skal + obrót.
|
||||||
|
|
||||||
|
```
|
||||||
|
Offset Rozmiar Typ Opis
|
||||||
|
------ ---------------- -------- ----------------------------------------
|
||||||
|
0 1 uint8 origin_count (maks. 255)
|
||||||
|
1 12 × origin_count Vec3[] origin_stack (offsety translacji)
|
||||||
|
+ 1 uint8 scale_count (maks. 255)
|
||||||
|
+ 12 × scale_count Vec3[] scale_stack (skala)
|
||||||
|
+ 12 Vec3 rotation (kąty Eulera w stopniach, XYZ)
|
||||||
|
+ 1 uint8 group_depth
|
||||||
|
```
|
||||||
|
|
||||||
|
Kolejność stosowania transformacji (w writer: `transform_point()`):
|
||||||
|
1. Obrót Y (o `rotation.y` stopni)
|
||||||
|
2. Skalowanie (`scale_stack.back()`)
|
||||||
|
3. Translacja (`origin_stack.back()`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Packed vertex — pakowany wierzchołek
|
||||||
|
|
||||||
|
Używany w chunkach MESH i TERR (od v4).
|
||||||
|
|
||||||
|
```
|
||||||
|
Offset Rozmiar Typ Opis
|
||||||
|
------ ------- ------- ------------------------------------------
|
||||||
|
0 4 float32 position.x
|
||||||
|
4 4 float32 position.y
|
||||||
|
8 4 float32 position.z
|
||||||
|
12 2 int16 normal.x jako snorm16 (/ 32767)
|
||||||
|
14 2 int16 normal.y jako snorm16
|
||||||
|
16 2 int16 normal.z jako snorm16
|
||||||
|
18 2 uint16 u jako half-float (IEEE 754 fp16)
|
||||||
|
20 2 uint16 v jako half-float
|
||||||
|
```
|
||||||
|
|
||||||
|
Łącznie: **22 bajty** na wierzchołek.
|
||||||
|
|
||||||
|
W chunkach LINE wierzchołki to **tylko pozycja** (Vec3 = 12 B; brak normalnych i UV).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Lighting block — blok oświetlenia
|
||||||
|
|
||||||
|
Opcjonalny blok oświetlenia materiału węzła (48 bajtów).
|
||||||
|
|
||||||
|
```
|
||||||
|
Offset Rozmiar Typ Opis
|
||||||
|
------ ------- ------- ------------------
|
||||||
|
0 16 4×f32 diffuse RGBA
|
||||||
|
16 16 4×f32 ambient RGBA
|
||||||
|
32 16 4×f32 specular RGBA
|
||||||
|
```
|
||||||
|
|
||||||
|
Domyślne wartości (gdy brak bloku):
|
||||||
|
- `diffuse = (0.8, 0.8, 0.8, 1.0)`
|
||||||
|
- `ambient = (0.2, 0.2, 0.2, 1.0)`
|
||||||
|
- `specular = (0.0, 0.0, 0.0, 1.0)`
|
||||||
|
|
||||||
|
Blok jest poprzedzony znacznikiem `uint8` (`0` = brak, `!0` = blok obecny) w rekordach MESH i LINE.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Catalog chunkowy — zestawienie
|
||||||
|
|
||||||
|
| FourCC | Stała C++ | Wersja min | Opis |
|
||||||
|
|---------|------------------|------------|------------------------------------------------|
|
||||||
|
| `STRS` | `kChunkStrs` | v4 | Tablica shared strings (musi być pierwsza) |
|
||||||
|
| `INCL` | `kChunkIncl` | v4 | Referencje do submodułów `.eu7` |
|
||||||
|
| `TRAK` | `kChunkTrak` | v4 | Tory (Track) |
|
||||||
|
| `TRAC` | `kChunkTrac` | v4 | Przewody trakcyjne (Traction) |
|
||||||
|
| `PWRS` | `kChunkPwrs` | v4 | Źródła zasilania trakcji |
|
||||||
|
| `TERR` | `kChunkTerr` | v5 | Teren (batched triangles per sekcja) |
|
||||||
|
| `MESH` | `kChunkMesh` | v4 | Kształty geometryczne (triangles/strip/fan) |
|
||||||
|
| `LINE` | `kChunkLine` | v4 | Linie (lines/strip/loop) |
|
||||||
|
| `MODL` | `kChunkModl` | v4 | Instancje modeli 3D (flat list) |
|
||||||
|
| `MEMC` | `kChunkMemc` | v4 | Komórki pamięci (MemCell) |
|
||||||
|
| `LAUN` | `kChunkLaun` | v4 | Wyzwalacze zdarzeń (EventLauncher) |
|
||||||
|
| `DYNM` | `kChunkDynm` | v4 | Pojazdy dynamiczne (Dynamic) |
|
||||||
|
| `SOND` | `kChunkSond` | v4 | Dźwięki otoczenia (Sound) |
|
||||||
|
| `TRSE` | `kChunkTrset` | v4 | Zestawy wagonowe (Trainset) |
|
||||||
|
| `EVNT` | `kChunkEvnt` | v4 | Zdarzenia (Event) |
|
||||||
|
| `FINT` | `kChunkFint` | v4 | Licznik obiektów first-init |
|
||||||
|
| `PLAC` | `kChunkPlac` | v4 | Parametry umieszczenia include |
|
||||||
|
| `PIDX` | `kChunkPidx` | **v7** | Indeks sekcji 1 km → offset w PACK |
|
||||||
|
| `PACK` | `kChunkPack` | **v7** | Strumień modeli per sekcja 1 km |
|
||||||
|
| `PROT` | `kChunkProt` | **v8** | Prototypy modeli (współdzielone definicje) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. STRS — tablica łańcuchów
|
||||||
|
|
||||||
|
Wszystkie łańcuchy tekstu w pliku są przechowywane w jednej tablicy i referowane przez `string_id` (indeks `uint32`). Specjalny indeks `0xFFFFFFFF` oznacza pusty string.
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
uint32 length (w bajtach, bez null-terminatora)
|
||||||
|
char[] data (length bajtów UTF-8/ASCII, bez null)
|
||||||
|
```
|
||||||
|
|
||||||
|
Każdy kolejny string ma numer indeksu 0, 1, 2, …
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. INCL — referencje do podmodułów
|
||||||
|
|
||||||
|
Przechowuje listę `include` wskazujących na inne pliki `.eu7` — submoduły scenerii.
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
uint32 source_line — numer linii w pliku tekstowym źródłowym
|
||||||
|
string_id source_path — ścieżka do pliku tekstowego (.scm/.inc)
|
||||||
|
string_id binary_path — ścieżka do pliku .eu7
|
||||||
|
uint32 param_count
|
||||||
|
string_id[] parameters — param_count × string_id (parametry include)
|
||||||
|
[v6+] TransformContext site_transform (nieobecne w v4/v5)
|
||||||
|
```
|
||||||
|
|
||||||
|
`site_transform` przechowuje pełny kontekst transformacji miejsca osadzenia (origin/scale/rotation) i jest używany do złożenia transformacji przy nakładaniu submodułu.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. TRAK — tory
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node
|
||||||
|
uint8 track_type — patrz enum Eu7TrackType
|
||||||
|
uint8 category — patrz enum Eu7TrackCategory
|
||||||
|
float32 length [m]
|
||||||
|
float32 track_width [m]
|
||||||
|
float32 friction
|
||||||
|
float32 sound_distance
|
||||||
|
int32 quality_flag
|
||||||
|
int32 damage_flag
|
||||||
|
uint8 environment — wartość enum + 1 (Unknown = 0, Flat = 1, ...)
|
||||||
|
uint8 has_visibility — 0 = brak, ≠0 = blok widoczności następuje
|
||||||
|
[jeśli has_visibility]:
|
||||||
|
string_id material1
|
||||||
|
float32 tex_length
|
||||||
|
string_id material2
|
||||||
|
float32 tex_height1
|
||||||
|
float32 tex_width
|
||||||
|
float32 tex_slope
|
||||||
|
uint32 path_count (maks. 65536)
|
||||||
|
Powtórzone path_count razy:
|
||||||
|
Vec3 p_start
|
||||||
|
f64_disk roll_start [stopnie]
|
||||||
|
Vec3 cp_out — punkt kontrolny wyjściowy Béziera
|
||||||
|
Vec3 cp_in — punkt kontrolny wejściowy Béziera
|
||||||
|
Vec3 p_end
|
||||||
|
f64_disk roll_end
|
||||||
|
f64_disk radius [m], 0 = prosta
|
||||||
|
uint32 tail_count (maks. 256)
|
||||||
|
Powtórzone tail_count razy:
|
||||||
|
uint8 code — 1–18 = predefiniowane kw., 255 = custom
|
||||||
|
[jeśli code==255]: string_id custom_key
|
||||||
|
string_id value
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enum Eu7TrackType (uint8)
|
||||||
|
|
||||||
|
| Wartość | Nazwa |
|
||||||
|
|---------|-------------|
|
||||||
|
| 0 | Unknown |
|
||||||
|
| 1 | Normal |
|
||||||
|
| 2 | Switch |
|
||||||
|
| 3 | Table |
|
||||||
|
| 4 | Cross |
|
||||||
|
| 5 | Tributary |
|
||||||
|
|
||||||
|
### Enum Eu7TrackCategory (uint8)
|
||||||
|
|
||||||
|
| Wartość | Nazwa |
|
||||||
|
|---------|-------|
|
||||||
|
| 1 | Rail |
|
||||||
|
| 2 | Road |
|
||||||
|
| 4 | Water |
|
||||||
|
|
||||||
|
### Enum Eu7TrackEnvironment (uint8 w pliku = enum + 1)
|
||||||
|
|
||||||
|
| Wartość w pliku | Enum | Nazwa |
|
||||||
|
|-----------------|-------|-----------|
|
||||||
|
| 0 | -1 | Unknown |
|
||||||
|
| 1 | 0 | Flat |
|
||||||
|
| 2 | 1 | Mountains |
|
||||||
|
| 3 | 2 | Canyon |
|
||||||
|
| 4 | 3 | Tunnel |
|
||||||
|
| 5 | 4 | Bridge |
|
||||||
|
| 6 | 5 | Bank |
|
||||||
|
|
||||||
|
### Kody tail keywords (uint8)
|
||||||
|
|
||||||
|
| Kod | Słowo kluczowe |
|
||||||
|
|-----|----------------|
|
||||||
|
| 1 | `event0` |
|
||||||
|
| 2 | `eventall0` |
|
||||||
|
| 3 | `event1` |
|
||||||
|
| 4 | `eventall1` |
|
||||||
|
| 5 | `event2` |
|
||||||
|
| 6 | `eventall2` |
|
||||||
|
| 7 | `velocity` |
|
||||||
|
| 8 | `isolated` |
|
||||||
|
| 9 | `overhead` |
|
||||||
|
| 10 | `vradius` |
|
||||||
|
| 11 | `railprofile` |
|
||||||
|
| 12 | `trackbed` |
|
||||||
|
| 13 | `friction` |
|
||||||
|
| 14 | `fouling1` |
|
||||||
|
| 15 | `fouling2` |
|
||||||
|
| 16 | `sleepermodel` |
|
||||||
|
| 17 | `angle1` |
|
||||||
|
| 18 | `angle2` |
|
||||||
|
| 255 | *custom* |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. TRAC — przewody trakcyjne
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node
|
||||||
|
string_id power_supply_name
|
||||||
|
uint8 material — Eu7TractionWireMaterial (0=None,1=Copper,2=Aluminium)
|
||||||
|
float32 nominal_voltage [V]
|
||||||
|
float32 max_current [A]
|
||||||
|
float32 resistivity_ohm_per_m
|
||||||
|
f64_disk resistivity_legacy
|
||||||
|
string_id material_raw — oryginalny string materiału ("cu", "al", ...)
|
||||||
|
float32 wire_thickness [m]
|
||||||
|
int32 damage_flag
|
||||||
|
Vec3 wire_p1
|
||||||
|
Vec3 wire_p2
|
||||||
|
Vec3 wire_p3
|
||||||
|
Vec3 wire_p4
|
||||||
|
f64_disk min_height [m]
|
||||||
|
f64_disk segment_length [m]
|
||||||
|
int32 wire_count
|
||||||
|
float32 wire_offset
|
||||||
|
uint8 has_parallel — 0 = brak, ≠0 = nazwa równoległego sekcji
|
||||||
|
[jeśli has_parallel]:
|
||||||
|
string_id parallel_name
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. PWRS — źródła zasilania trakcji
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node
|
||||||
|
Vec3 position
|
||||||
|
float32 nominal_voltage [V]
|
||||||
|
float32 voltage_frequency [Hz]
|
||||||
|
f64_disk internal_resistance_legacy
|
||||||
|
float32 internal_resistance [Ω]
|
||||||
|
float32 max_output_current [A]
|
||||||
|
float32 fast_fuse_timeout [s]
|
||||||
|
float32 fast_fuse_repetition [s]
|
||||||
|
float32 slow_fuse_timeout [s]
|
||||||
|
uint8 modifier — Eu7PowerSourceModifier (0=None,1=Recuperation,2=Section)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. TERR — teren (terrain shapes)
|
||||||
|
|
||||||
|
Chunk TERR przechowuje trójkąty terenu. Może się pojawić wielokrotnie (po jednym per materiał lub per sekcja).
|
||||||
|
|
||||||
|
```
|
||||||
|
uint8 flags — bitmaska:
|
||||||
|
bit 0: translucent
|
||||||
|
bit 1: non-default lighting (blok lighting poniżej)
|
||||||
|
bit 2: batched (grupowany per sekcja 1km)
|
||||||
|
string_id material
|
||||||
|
|
||||||
|
[jeśli bit 1 set]:
|
||||||
|
LightingBlock lighting (48 B)
|
||||||
|
|
||||||
|
[jeśli bit 2 set — tryb batched]:
|
||||||
|
uint32 batch_count
|
||||||
|
Powtórzone batch_count razy:
|
||||||
|
int32 section_x_coord — współrzędna X sekcji (odczytane, ale ignorowane przez runtime)
|
||||||
|
int32 section_z_coord
|
||||||
|
uint32 vertex_count — musi być wielokrotnością 3
|
||||||
|
PackedVertex[] vertices — vertex_count wierzchołków
|
||||||
|
[else — tryb legacy]:
|
||||||
|
uint32 count — liczba trójkątów
|
||||||
|
Powtórzone count razy:
|
||||||
|
PackedVertex[3] vertices — dokładnie 3 wierzchołki (1 trójkąt)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Uwaga:** tryb batched (bit 2) jest charakterystyczny dla v5+. Pola `section_x_coord` i `section_z_coord` są odczytywane przez reader, ale ich wartości są ignorowane w runtime maszyna-fresh (batche traktowane jako płaskie listy kształtów).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. MESH — kształty geometryczne
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
uint8 subtype — 0=triangles, 1=triangle_strip, 2=triangle_fan
|
||||||
|
SlimNode node — typ węzła wyznaczony przez subtype
|
||||||
|
uint8 translucent — 0/1
|
||||||
|
string_id material_path
|
||||||
|
uint8 has_lighting — 0/1
|
||||||
|
[jeśli has_lighting]:
|
||||||
|
LightingBlock lighting
|
||||||
|
Vec3 origin — lokalny punkt odniesienia wierzchołków
|
||||||
|
uint32 vertex_count
|
||||||
|
PackedVertex[] vertices
|
||||||
|
```
|
||||||
|
|
||||||
|
Nazwa węzła (`node.node_type`) wyznaczona przez subtype: `"triangles"`, `"triangle_strip"`, `"triangle_fan"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. LINE — linie
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
uint8 subtype — 0=lines, 1=line_strip, 2=line_loop
|
||||||
|
SlimNode node
|
||||||
|
uint8 has_lighting — 0/1
|
||||||
|
[jeśli has_lighting]:
|
||||||
|
LightingBlock lighting
|
||||||
|
float32 line_width
|
||||||
|
Vec3 origin
|
||||||
|
uint32 vertex_count
|
||||||
|
Powtórzone vertex_count razy:
|
||||||
|
Vec3 position — TYLKO pozycja (brak normalnych/UV)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. MODL — instancje modeli 3D
|
||||||
|
|
||||||
|
Używane w submodułach include. W plikach głównych z PACK chunk, MODL z podmodułów jest zazwyczaj pomijane (sterowane flagą `pack_scenery_active()`).
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node
|
||||||
|
uint8 is_terrain — 0/1
|
||||||
|
uint8 transition — 0/1 (animowany przejazd)
|
||||||
|
Vec3 location — world-space, 3×float32
|
||||||
|
Vec3 angles — kąty Eulera (stopnie), 3×float32
|
||||||
|
Vec3 scale — skala XYZ, domyślnie (1,1,1)
|
||||||
|
string_id model_file — ścieżka do pliku .e3d
|
||||||
|
string_id texture_file — override tekstury (opcjonalnie pusty)
|
||||||
|
uint32 light_count
|
||||||
|
float32[] light_states — light_count wartości
|
||||||
|
uint32 color_count
|
||||||
|
uint32[] light_colors — color_count kolorów RGBA packed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aplikacja w runtime (maszyna-fresh)
|
||||||
|
|
||||||
|
Rekord MODL/PACK nie trafia przez parser tekstowy SCM. Ścieżka **instancingu GPU** (nie per-model drip):
|
||||||
|
|
||||||
|
1. `preload_unique_pack_meshes()` — jeden `GetModel()` na unikalny plik `.e3d` w całej sekcji
|
||||||
|
2. Dla każdego rekordu: `TAnimModel` + `LoadEu7()` (cache hit → szybkie `Init()`)
|
||||||
|
3. `update_instanceable_flag()` — modele bez świateł/animacji → `m_instanceable = true`
|
||||||
|
4. `Region->insert()` → `m_instancebuckets_opaque` → renderer: **`Render_Instanced()`**
|
||||||
|
|
||||||
|
PACK **nie** rejestruje instancji w `Instances` / `Hierarchy` (anonimowa sceneria, bez eventów po nazwie).
|
||||||
|
|
||||||
|
Deduplikacja meshów: **`TModelsManager`** (współdzielony `TModel3d`). GPU instancing: **`opengl33_renderer::Render_Instanced`** — jeden draw na submodel × N instancji tego samego mesha.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 20. MEMC — komórki pamięci
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node
|
||||||
|
string_id text
|
||||||
|
f64_disk value1
|
||||||
|
f64_disk value2
|
||||||
|
string_id track_name — 0xFFFFFFFF jeśli brak
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 21. LAUN — wyzwalacze zdarzeń
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node
|
||||||
|
Vec3 location
|
||||||
|
f64_disk radius_squared [m²]
|
||||||
|
string_id activation_key_raw
|
||||||
|
int32 activation_key
|
||||||
|
f64_disk delta_time [s], -1 = bez limitu
|
||||||
|
string_id event1_name
|
||||||
|
string_id event2_name
|
||||||
|
int32 launch_hour — -1 = bez warunku godzinowego
|
||||||
|
int32 launch_minute
|
||||||
|
uint8 has_condition — 0/1
|
||||||
|
[jeśli has_condition]:
|
||||||
|
string_id memcell_name
|
||||||
|
string_id compare_text
|
||||||
|
f64_disk compare_value1
|
||||||
|
f64_disk compare_value2
|
||||||
|
int32 check_mask
|
||||||
|
uint8 train_triggered — 0/1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 22. DYNM — pojazdy dynamiczne
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node
|
||||||
|
string_id data_folder
|
||||||
|
string_id skin_file
|
||||||
|
string_id mmd_file
|
||||||
|
string_id track_name
|
||||||
|
string_id driver_type
|
||||||
|
string_id load_type
|
||||||
|
string_id coupling_params
|
||||||
|
string_id coupling_raw — tekstowa reprezentacja sprzęgu (np. "3.automat")
|
||||||
|
f64_disk offset [m od początku toru], -1 = auto
|
||||||
|
int32 coupling — wartość sprzęgu (0-3)
|
||||||
|
int32 load_count
|
||||||
|
float32 velocity [km/h]
|
||||||
|
uint8 has_destination — 0/1
|
||||||
|
[jeśli has_destination]:
|
||||||
|
string_id destination
|
||||||
|
uint8 has_trainset_index — 0/1
|
||||||
|
[jeśli has_trainset_index]:
|
||||||
|
uint32 trainset_index
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 23. SOND — dźwięki otoczenia
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node
|
||||||
|
Vec3 location
|
||||||
|
string_id wav_file
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 24. TRSE — zestawy wagonowe
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
string_id name
|
||||||
|
string_id track
|
||||||
|
float32 offset [m]
|
||||||
|
float32 velocity [km/h]
|
||||||
|
uint32 assignment_count
|
||||||
|
Powtórzone assignment_count razy:
|
||||||
|
string_id key
|
||||||
|
string_id value
|
||||||
|
uint32 vehicle_count
|
||||||
|
uint32[] vehicle_indices — indeksy do tablicy DYNM w module
|
||||||
|
uint32 coupling_count
|
||||||
|
int32[] couplings
|
||||||
|
uint32 driver_index — indeks pojazdu-prowadzącego
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 25. EVNT — zdarzenia
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
string_id name
|
||||||
|
uint8 type — Eu7EventType (patrz tabela)
|
||||||
|
f64_disk delay [s]
|
||||||
|
uint32 target_count
|
||||||
|
string_id[] targets
|
||||||
|
f64_disk delay_random [s]
|
||||||
|
f64_disk delay_departure [s]
|
||||||
|
uint8 ignored — 0/1
|
||||||
|
uint8 passive — 0/1
|
||||||
|
uint32 payload_count
|
||||||
|
Powtórzone payload_count razy:
|
||||||
|
string_id key
|
||||||
|
string_id value
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enum Eu7EventType (uint8)
|
||||||
|
|
||||||
|
| Wartość | Nazwa |
|
||||||
|
|---------|---------------|
|
||||||
|
| 0 | AddValues |
|
||||||
|
| 1 | UpdateValues |
|
||||||
|
| 2 | CopyValues |
|
||||||
|
| 3 | GetValues |
|
||||||
|
| 4 | PutValues |
|
||||||
|
| 5 | Whois |
|
||||||
|
| 6 | LogValues |
|
||||||
|
| 7 | Multiple |
|
||||||
|
| 8 | Switch |
|
||||||
|
| 9 | TrackVel |
|
||||||
|
| 10 | Sound |
|
||||||
|
| 11 | Texture |
|
||||||
|
| 12 | Animation |
|
||||||
|
| 13 | Lights |
|
||||||
|
| 14 | Voltage |
|
||||||
|
| 15 | Visible |
|
||||||
|
| 16 | Friction |
|
||||||
|
| 17 | Message |
|
||||||
|
| 18 | Unknown |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 26. FINT — licznik first-init
|
||||||
|
|
||||||
|
Prosty licznik — ile obiektów wymaga obsługi pierwszego inicjowania.
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 first_init_count
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 27. PLAC — parametry umieszczenia include
|
||||||
|
|
||||||
|
Mapowanie parametrów wywołania include na składowe transformacji.
|
||||||
|
|
||||||
|
```
|
||||||
|
uint8 origin_x_param — numer parametru dla origin.x (0 = brak)
|
||||||
|
uint8 origin_y_param — numer parametru dla origin.y
|
||||||
|
uint8 origin_z_param — numer parametru dla origin.z
|
||||||
|
uint8 rotation_y_param — numer parametru dla rotation.y
|
||||||
|
```
|
||||||
|
|
||||||
|
Gdy wszystkie cztery pola są `0`, plik nie eksponuje parametryzowanego umieszczenia.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 28. PIDX — indeks sekcji PACK (v7+)
|
||||||
|
|
||||||
|
Katalog mapujący sekcje 1 km × 1 km na offsety w payloadzie chunku PACK.
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
uint16 row — indeks wiersza sekcji (siatka regionu)
|
||||||
|
uint16 column — indeks kolumny sekcji
|
||||||
|
uint32 model_count — liczba modeli w tej sekcji
|
||||||
|
uint64 pack_offset — offset w bajtach od początku payloadu PACK
|
||||||
|
```
|
||||||
|
|
||||||
|
**Uwagi:**
|
||||||
|
- `pack_offset` jest **względny** od początku payloadu PACK (nie od początku pliku).
|
||||||
|
- Sekcje bez modeli mogą mieć `model_count = 0` i być pominięte w PIDX.
|
||||||
|
- Indeksowanie sekcji: `row/column` odpowiadają siatce `kRegionSideSectionCount × kRegionSideSectionCount`, centrum siatki to `(0, 0)` w world-space.
|
||||||
|
- Runtime oblicza absolutny offset jako: `pack_payload_offset + entry.pack_offset`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 29. PACK — instancje modeli per sekcja 1 km (v7+)
|
||||||
|
|
||||||
|
Payload PACK jest **zbiorem sekcji** ustawionych sekwencyjnie wg offsetów z PIDX. Chunk PACK jest pomijany przy pełnym wczytaniu — reader zapamiętuje jedynie `pack_payload_offset`.
|
||||||
|
|
||||||
|
### Format sekcji — v7 (domyślny)
|
||||||
|
|
||||||
|
Każda sekcja to `model_count` rekordów `RuntimeModelInstance` (identyczny format jak rekordy MODL):
|
||||||
|
|
||||||
|
```
|
||||||
|
Powtórzone model_count razy:
|
||||||
|
(pełny rekord RuntimeModelInstance — patrz sekcja 19 MODL)
|
||||||
|
```
|
||||||
|
|
||||||
|
Transformacja węzła (`node.transform`) jest **zerowana** po wczytaniu — dane w PACK są zawsze w world-space (transform był bake'owany przy generacji).
|
||||||
|
|
||||||
|
### Format sekcji — v8 (`kPackSectionFormatV8 = 1`)
|
||||||
|
|
||||||
|
```
|
||||||
|
uint8 section_format — musi być = 1
|
||||||
|
uint32 solo_count — modele pełne (bez prototypu)
|
||||||
|
uint32 inst_count — instancje odwołujące się do PROT
|
||||||
|
|
||||||
|
Powtórzone solo_count razy:
|
||||||
|
(pełny rekord RuntimeModelInstance, transformacja zerowana)
|
||||||
|
|
||||||
|
Powtórzone inst_count razy:
|
||||||
|
uint32 proto_id — indeks do tablicy PROT
|
||||||
|
Vec3 location
|
||||||
|
Vec3 angles
|
||||||
|
Vec3 scale
|
||||||
|
string_id name
|
||||||
|
```
|
||||||
|
|
||||||
|
Instancje v8 są rozwijane przez `expand_prototype_instance()` łącząc dane z PROT z lokalizacją instancji.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 30. PROT — prototypy modeli (v8+)
|
||||||
|
|
||||||
|
Wspólne definicje modeli (bez informacji o lokalizacji instancji). Muszą być obecne przed odczytaniem sekcji PACK v8.
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32 count
|
||||||
|
Powtórzone count razy:
|
||||||
|
SlimNode node — name/range/bounds jak MODL, ale BEZ location/angles/scale
|
||||||
|
uint8 is_terrain
|
||||||
|
uint8 transition
|
||||||
|
string_id model_file
|
||||||
|
string_id texture_file
|
||||||
|
uint32 light_count
|
||||||
|
float32[] light_states
|
||||||
|
uint32 color_count
|
||||||
|
uint32[] light_colors
|
||||||
|
```
|
||||||
|
|
||||||
|
Różnica względem MODL: brak pól `location`, `angles`, `scale` — te są zapisane per instancja w PACK.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 31. Semantyka strumieniowania PACK w runtime
|
||||||
|
|
||||||
|
Gdy root scenariusz zawiera chunk PACK, silnik maszyna-fresh uruchamia asynchroniczny **sekcja-stream**:
|
||||||
|
|
||||||
|
### Parametry strumienia
|
||||||
|
|
||||||
|
| Parametr | Wartość | Opis |
|
||||||
|
|----------------------------|------------------|----------------------------------------------------|
|
||||||
|
| `kSectionSizeM` | 1000 m | Rozmiar sekcji |
|
||||||
|
| `kInitialBootstrapRadius` | 3 sekcje | Promień wstępnego ładowania wokół pozycji startowej|
|
||||||
|
| `kStreamRadius` | 9 sekcji | Bieżący promień streamingu |
|
||||||
|
| `kMovementLookahead` | 4 sekcje | Prefetch w kierunku jazdy |
|
||||||
|
| `kMaxInFlightSections` | 72 | Maks. sekcji w toku (backpressure) |
|
||||||
|
| `kMaxReadySections` | 36 | Maks. gotowych sekcji w kolejce |
|
||||||
|
| `kBootstrapTimeoutMs` | 120 000 ms | Timeout bootstrapu (tylko ścieżka blokująca) |
|
||||||
|
|
||||||
|
W **jazdzie** (`driver_mode`): **jedna sekcja PACK na klatkę** — `drain_section_stream()` → `apply_pending_section()` (cała sekcja naraz, bez fałszywych budżetów ms per model).
|
||||||
|
|
||||||
|
### Dysk vs wątek główny
|
||||||
|
|
||||||
|
| Etap | Wątek | API |
|
||||||
|
|------|-------|-----|
|
||||||
|
| Deserialize MODL z `.eu7` | Worker | `read_pack_section()` |
|
||||||
|
| Page cache `.e3d` | Worker | `prefetch_pack_models()` |
|
||||||
|
| `GetModel()` unikalnych meshów + instancje | **Main** | `insert_eu7_pack_models()` |
|
||||||
|
| GPU draw | **Main** | `Render_Instanced()` dla `m_instanceable` |
|
||||||
|
|
||||||
|
`TModel3d::LoadFromFile()` + `GfxRenderer` — **tylko main**. W sekcji: najpierw wszystkie unikalne meshe, potem tysiące lekkich `TAnimModel` trafiających do bucketów instancingu.
|
||||||
|
|
||||||
|
### Przepływ ładowania
|
||||||
|
|
||||||
|
```
|
||||||
|
1. read_module() → PIDX w RAM, PACK jako offset (seek)
|
||||||
|
2. init_section_stream() → worker pool
|
||||||
|
3. preload_section_stream() [loader, blokująco] → bootstrap + drain sekcji
|
||||||
|
4. W jazdzie (is_ready):
|
||||||
|
kick_section_stream_bootstrap() — enqueue, bez spin-wait
|
||||||
|
drain_section_stream() — jedna gotowa sekcja → apply_pending_section()
|
||||||
|
update_section_stream(camera)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wątki robocze
|
||||||
|
|
||||||
|
Po `read_pack_section()`: `prefetch_pack_models()` → `PackSectionReady` w kolejce.
|
||||||
|
|
||||||
|
### Drain (wątek główny)
|
||||||
|
|
||||||
|
`apply_pending_section()` — **cała sekcja** w jednym `insert_eu7_pack_models()`:
|
||||||
|
1. `preload_unique_pack_meshes` — cold load tylko dla nowych `.e3d`
|
||||||
|
2. Pętla instancji → `Region->insert` → GPU instancing bucket
|
||||||
|
|
||||||
|
Jedna sekcja na wywołanie `drain_section_stream()` (jedna klatka = jedna sekcja, nie jeden model).
|
||||||
|
|
||||||
|
### Diagnostyka (`eu7_load_stats`)
|
||||||
|
|
||||||
|
| Pole | Znaczenie |
|
||||||
|
|------|-----------|
|
||||||
|
| `model_ms` / `models` | Czas i liczba `TAnimModel` z PACK |
|
||||||
|
| `pack_sections_loaded` / `pack_models` | Sekcje / instancje ze streamingu |
|
||||||
|
|
||||||
|
Overlay renderera: `inst-pool:` — ile instancji zakwalifikowanych do `Render_Instanced`.
|
||||||
|
|
||||||
|
### Pliki implementacji (runtime)
|
||||||
|
|
||||||
|
| Temat | Plik |
|
||||||
|
|-------|------|
|
||||||
|
| Streaming sekcji | `scene/eu7/eu7_section_stream.cpp` |
|
||||||
|
| Prefetch dysku | `scene/eu7/eu7_model_prefetch.cpp` |
|
||||||
|
| Instancje PACK | `simulation/simulationstateserializer.cpp` |
|
||||||
|
| `LoadEu7` + `m_instanceable` | `model/AnimModel.cpp` |
|
||||||
|
| `Render_Instanced` | `rendering/opengl33renderer.cpp` |
|
||||||
|
| Cache meshów | `model/MdlMngr.cpp` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 32. Historia wersji
|
||||||
|
|
||||||
|
| Wersja | Zmiany |
|
||||||
|
|--------|--------------------------------------------------------------------------------------------------|
|
||||||
|
| v1 | Format legacy — szczegóły nieokreślone, nieobsługiwany |
|
||||||
|
| v2 | Skalary i Vec3 jako float64 (8 B) na dysku |
|
||||||
|
| v3 | Skalary i Vec3 jako float32 (4 B) na dysku — `writeF64()` → rzutuje do float32 |
|
||||||
|
| v4 | **Slim node** (flagowany nagłówek węzła) + **packed vertex** (snorm16 normy + half16 UV) |
|
||||||
|
| v5 | Chunk **TERR** z trybem batched (terrain per sekcja 1 km); TERR może być w osobnym pliku `.eu7` |
|
||||||
|
| v6 | Chunk INCL rozszerzony o **site transform** (pełny TransformContext per include) |
|
||||||
|
| v7 | Chunki **PIDX + PACK** — modele scenerii podzielone na kafelki 1 km, asynchroniczny streaming |
|
||||||
|
| v8 | Chunk **PROT** (prototypy) + nowy format sekcji PACK z `solo_count` + `inst_count` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 33. Ścieżki plików i konwencje nazewnictwa
|
||||||
|
|
||||||
|
### Rozszerzenie
|
||||||
|
|
||||||
|
Pliki EU7B używają rozszerzenia `.eu7`. Identyfikacja pliku odbywa się przez:
|
||||||
|
1. Sprawdzenie rozszerzenia (`isSceneBinaryPath()`)
|
||||||
|
2. Sondowanie magic bytes `EU7B` na pozycji 0 (`probeSceneBinaryMagic()`)
|
||||||
|
|
||||||
|
### Konwersja ścieżek source → binary
|
||||||
|
|
||||||
|
- `*.scm` → `*.eu7` (te same katalog i stem)
|
||||||
|
- `*.inc` → `*.eu7`
|
||||||
|
- `*.scn` → `*.eu7`
|
||||||
|
- `*.sbt` → terrain `*.eu7` (odpowiednik binarny terenu)
|
||||||
|
|
||||||
|
Funkcja `binary_path()` w loaderze zamienia rozszerzenie przy tym samym stemie.
|
||||||
|
|
||||||
|
### Warianty pliku terenu
|
||||||
|
|
||||||
|
Plik `.eu7` może zawierać chunk TERR (teren). Loader sprawdza obecność TERR przez `probe_terrain_file()` (pełne wczytanie nagłówków).
|
||||||
|
|
||||||
|
Funkcja `is_scenario_terrain()` sprawdza czy obok scenariusza `<stem>.scn` istnieje `<stem>.eu7` z chunkiem TERR.
|
||||||
|
|
||||||
|
### Pliki include
|
||||||
|
|
||||||
|
Submoduły include są wczytywane jeden raz per ścieżka (deduplication przez `is_module_loaded()`). Ścieżka pliku `.eu7` includowanego jest wyznaczana przez `include_eu7_path()` — ten sam katalog co plik SCM/INC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Dokumentacja wygenerowana na podstawie kodu źródłowego; stan na 11 czerwca 2026.*
|
||||||
117
eu07-parser/include/eu07/geo/puwg1992.hpp
Normal file
117
eu07-parser/include/eu07/geo/puwg1992.hpp
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// PUWG 1992 ↔ współrzędne sceny MaSzyny (jak terenAI GeoToSim).
|
||||||
|
// Master offset = pierwsza napotkana dyrektywa //$g w pliku sceny.
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <optional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace eu07::geo {
|
||||||
|
|
||||||
|
struct PuwgMasterOffset {
|
||||||
|
double eastM = 0.0; // wschód [m]
|
||||||
|
double northM = 0.0; // północ [m]
|
||||||
|
std::size_t sourceLine = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline double normalizePuwgKmToMeters(const double value) noexcept {
|
||||||
|
return std::abs(value) < 10000.0 ? value * 1000.0 : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<PuwgMasterOffset> parseGeoMapLine(std::string_view line) {
|
||||||
|
std::string trimmed(line);
|
||||||
|
while (!trimmed.empty() && (trimmed.back() == '\r' || trimmed.back() == '\n')) {
|
||||||
|
trimmed.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream ss(trimmed);
|
||||||
|
std::string tag;
|
||||||
|
std::string system;
|
||||||
|
double east = 0.0;
|
||||||
|
double north = 0.0;
|
||||||
|
if (!(ss >> tag >> system >> east >> north)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (tag != "//$g") {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
PuwgMasterOffset out;
|
||||||
|
out.eastM = normalizePuwgKmToMeters(east);
|
||||||
|
out.northM = normalizePuwgKmToMeters(north);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<PuwgMasterOffset> readFirstGeoMapFromFile(
|
||||||
|
const std::filesystem::path& scnPath) {
|
||||||
|
std::ifstream in(scnPath);
|
||||||
|
if (!in) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
for (std::size_t lineNo = 0; std::getline(in, line); ++lineNo) {
|
||||||
|
if (line.find("//$g") == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const std::optional<PuwgMasterOffset> parsed = parseGeoMapLine(line)) {
|
||||||
|
PuwgMasterOffset out = *parsed;
|
||||||
|
out.sourceLine = lineNo + 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<PuwgMasterOffset> readFirstGeoMap(
|
||||||
|
const std::filesystem::path& scnPath,
|
||||||
|
const std::vector<StarterDirective>& starters) {
|
||||||
|
for (const StarterDirective& s : starters) {
|
||||||
|
if (s.id != StarterId::GeoMap) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string synthetic = std::string("//$g ") + s.value;
|
||||||
|
if (const std::optional<PuwgMasterOffset> parsed = parseGeoMapLine(synthetic)) {
|
||||||
|
PuwgMasterOffset out = *parsed;
|
||||||
|
out.sourceLine = s.line + 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return readFirstGeoMapFromFile(scnPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PuwgPoint {
|
||||||
|
double east = 0.0;
|
||||||
|
double north = 0.0;
|
||||||
|
double height = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline scene::Vec3 geoToSim(
|
||||||
|
const double north,
|
||||||
|
const double east,
|
||||||
|
const double height,
|
||||||
|
const PuwgMasterOffset& master) noexcept {
|
||||||
|
return {
|
||||||
|
master.eastM - east,
|
||||||
|
height,
|
||||||
|
north - master.northM,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline PuwgPoint simToGeo(const scene::Vec3& sim, const PuwgMasterOffset& master) noexcept {
|
||||||
|
return {
|
||||||
|
master.eastM - sim.x,
|
||||||
|
sim.z + master.northM,
|
||||||
|
sim.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::geo
|
||||||
86
eu07-parser/include/eu07/geo/spatial_grid.hpp
Normal file
86
eu07-parser/include/eu07/geo/spatial_grid.hpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::geo {
|
||||||
|
|
||||||
|
template <int CellSizeM>
|
||||||
|
class SpatialGrid {
|
||||||
|
public:
|
||||||
|
struct ItemBounds {
|
||||||
|
double minX = 0.0;
|
||||||
|
double maxX = 0.0;
|
||||||
|
double minZ = 0.0;
|
||||||
|
double maxZ = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct CellKey {
|
||||||
|
int x = 0;
|
||||||
|
int z = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator==(const CellKey& other) const noexcept {
|
||||||
|
return x == other.x && z == other.z;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CellKeyHash {
|
||||||
|
[[nodiscard]] std::size_t operator()(const CellKey& key) const noexcept {
|
||||||
|
return static_cast<std::size_t>(key.x * 73856093) ^
|
||||||
|
static_cast<std::size_t>(key.z * 19349663);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<CellKey, std::vector<std::size_t>, CellKeyHash> grid_;
|
||||||
|
|
||||||
|
[[nodiscard]] static int cellIndex(const double value) noexcept {
|
||||||
|
return static_cast<int>(std::floor(value / static_cast<double>(CellSizeM)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void clear() { grid_.clear(); }
|
||||||
|
|
||||||
|
void insert(const std::size_t itemIndex, const ItemBounds& bounds) {
|
||||||
|
const int minIx = cellIndex(bounds.minX);
|
||||||
|
const int maxIx = cellIndex(bounds.maxX);
|
||||||
|
const int minIz = cellIndex(bounds.minZ);
|
||||||
|
const int maxIz = cellIndex(bounds.maxZ);
|
||||||
|
for (int x = minIx; x <= maxIx; ++x) {
|
||||||
|
for (int z = minIz; z <= maxIz; ++z) {
|
||||||
|
grid_[{x, z}].push_back(itemIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void query(
|
||||||
|
const scene::Vec3& point,
|
||||||
|
const double maxDist,
|
||||||
|
std::vector<std::size_t>& out) const {
|
||||||
|
out.clear();
|
||||||
|
const int cx = cellIndex(point.x);
|
||||||
|
const int cz = cellIndex(point.z);
|
||||||
|
int range = static_cast<int>(std::ceil(maxDist / static_cast<double>(CellSizeM)));
|
||||||
|
if (range < 1) {
|
||||||
|
range = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int dz = -range; dz <= range; ++dz) {
|
||||||
|
for (int dx = -range; dx <= range; ++dx) {
|
||||||
|
const auto it = grid_.find({cx + dx, cz + dz});
|
||||||
|
if (it == grid_.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const std::size_t idx : it->second) {
|
||||||
|
out.push_back(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::geo
|
||||||
323
eu07-parser/include/eu07/nmt/asc_index.hpp
Normal file
323
eu07-parser/include/eu07/nmt/asc_index.hpp
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Jednorazowy indeks nagłówków plików ASC (NMT1) — bbox w PUWG, bez wczytywania siatki.
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <system_error>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kAscIndexMagic = "# parser_nmt_index v1";
|
||||||
|
|
||||||
|
struct AscHeader {
|
||||||
|
int ncols = 0;
|
||||||
|
int nrows = 0;
|
||||||
|
double xll = 0.0;
|
||||||
|
double yll = 0.0;
|
||||||
|
double cellsize = 0.0;
|
||||||
|
double nodata = -9999.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AscIndexEntry {
|
||||||
|
std::filesystem::path relativePath;
|
||||||
|
std::int64_t mtimeUtc = 0;
|
||||||
|
AscHeader header{};
|
||||||
|
double northMin = 0.0;
|
||||||
|
double northMax = 0.0;
|
||||||
|
double eastMin = 0.0;
|
||||||
|
double eastMax = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AscIndex {
|
||||||
|
std::filesystem::path catalogRoot;
|
||||||
|
std::vector<AscIndexEntry> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::filesystem::path defaultIndexPath(const std::filesystem::path& catalogRoot) {
|
||||||
|
return catalogRoot / ".parser_nmt_index.tsv";
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::int64_t fileMtimeUtc(const std::filesystem::path& path) {
|
||||||
|
std::error_code ec;
|
||||||
|
const auto ftime = std::filesystem::last_write_time(path, ec);
|
||||||
|
if (ec) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||||
|
ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(sctp.time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseAscHeaderLine(
|
||||||
|
std::string_view line,
|
||||||
|
std::string_view key,
|
||||||
|
double& outValue) {
|
||||||
|
if (!line.starts_with(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::size_t pos = line.find_first_of(" \t");
|
||||||
|
if (pos == std::string_view::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string_view tail = line.substr(pos);
|
||||||
|
while (!tail.empty() && (tail.front() == ' ' || tail.front() == '\t')) {
|
||||||
|
tail.remove_prefix(1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
outValue = std::stod(std::string(tail));
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseAscHeaderLine(
|
||||||
|
std::string_view line,
|
||||||
|
std::string_view key,
|
||||||
|
int& outValue) {
|
||||||
|
double tmp = 0.0;
|
||||||
|
if (!parseAscHeaderLine(line, key, tmp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
outValue = static_cast<int>(tmp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<AscHeader> readAscHeaderOnly(const std::filesystem::path& path) {
|
||||||
|
std::ifstream in(path);
|
||||||
|
if (!in) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
AscHeader header;
|
||||||
|
std::string line;
|
||||||
|
int parsedLines = 0;
|
||||||
|
while (parsedLines < 6 && std::getline(in, line)) {
|
||||||
|
if (line.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (parseAscHeaderLine(line, "ncols", header.ncols) ||
|
||||||
|
parseAscHeaderLine(line, "NCOLS", header.ncols)) {
|
||||||
|
++parsedLines;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (parseAscHeaderLine(line, "nrows", header.nrows) ||
|
||||||
|
parseAscHeaderLine(line, "NROWS", header.nrows)) {
|
||||||
|
++parsedLines;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (parseAscHeaderLine(line, "xllcorner", header.xll) ||
|
||||||
|
parseAscHeaderLine(line, "XLLCORNER", header.xll) ||
|
||||||
|
parseAscHeaderLine(line, "xllcenter", header.xll) ||
|
||||||
|
parseAscHeaderLine(line, "XLLCENTER", header.xll)) {
|
||||||
|
++parsedLines;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (parseAscHeaderLine(line, "yllcorner", header.yll) ||
|
||||||
|
parseAscHeaderLine(line, "YLLCORNER", header.yll) ||
|
||||||
|
parseAscHeaderLine(line, "yllcenter", header.yll) ||
|
||||||
|
parseAscHeaderLine(line, "YLLCENTER", header.yll)) {
|
||||||
|
++parsedLines;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (parseAscHeaderLine(line, "cellsize", header.cellsize) ||
|
||||||
|
parseAscHeaderLine(line, "CELLSIZE", header.cellsize)) {
|
||||||
|
++parsedLines;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (parseAscHeaderLine(line, "NODATA_value", header.nodata) ||
|
||||||
|
parseAscHeaderLine(line, "nodata_value", header.nodata) ||
|
||||||
|
parseAscHeaderLine(line, "NODATA", header.nodata)) {
|
||||||
|
++parsedLines;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.ncols <= 0 || header.nrows <= 0 || header.cellsize <= 0.0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline AscIndexEntry makeIndexEntry(
|
||||||
|
const std::filesystem::path& catalogRoot,
|
||||||
|
const std::filesystem::path& ascPath,
|
||||||
|
const AscHeader& header) {
|
||||||
|
AscIndexEntry entry;
|
||||||
|
entry.relativePath = std::filesystem::relative(ascPath, catalogRoot);
|
||||||
|
entry.mtimeUtc = fileMtimeUtc(ascPath);
|
||||||
|
entry.header = header;
|
||||||
|
// Konwencja ESRI ASC + PUWG (jak terenAI): xll/yll = easting/northing rogu siatki.
|
||||||
|
entry.northMin = header.yll;
|
||||||
|
entry.northMax = header.yll + header.nrows * header.cellsize;
|
||||||
|
entry.eastMin = header.xll;
|
||||||
|
entry.eastMax = header.xll + header.ncols * header.cellsize;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool bboxIntersects(
|
||||||
|
const double northMin,
|
||||||
|
const double northMax,
|
||||||
|
const double eastMin,
|
||||||
|
const double eastMax,
|
||||||
|
const AscIndexEntry& entry) {
|
||||||
|
return entry.northMax >= northMin && entry.northMin <= northMax && entry.eastMax >= eastMin &&
|
||||||
|
entry.eastMin <= eastMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline AscIndex buildAscIndex(const std::filesystem::path& catalogRoot) {
|
||||||
|
AscIndex index;
|
||||||
|
index.catalogRoot = std::filesystem::absolute(catalogRoot);
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::is_directory(index.catalogRoot, ec)) {
|
||||||
|
throw std::runtime_error("Katalog NMT nie istnieje: " + index.catalogRoot.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry : std::filesystem::recursive_directory_iterator(
|
||||||
|
index.catalogRoot, std::filesystem::directory_options::skip_permission_denied, ec)) {
|
||||||
|
if (ec) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!entry.is_regular_file()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::filesystem::path path = entry.path();
|
||||||
|
const std::string ext = path.extension().string();
|
||||||
|
if (ext != ".asc" && ext != ".ASC") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (path.filename() == ".parser_nmt_index.tsv") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<AscHeader> header = readAscHeaderOnly(path);
|
||||||
|
if (!header) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
index.entries.push_back(makeIndexEntry(index.catalogRoot, path, *header));
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeAscIndex(const AscIndex& index, const std::filesystem::path& outPath) {
|
||||||
|
std::ofstream out(outPath);
|
||||||
|
if (!out) {
|
||||||
|
throw std::runtime_error("Nie mozna zapisac indeksu: " + outPath.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
out << kAscIndexMagic << '\n';
|
||||||
|
out << "# catalog=" << index.catalogRoot.string() << '\n';
|
||||||
|
out << "# path\tmtime\tncols\tnrows\txll\tyll\tcellsize\tnodata\tnorth_min\tnorth_max\teast_min\teast_max\n";
|
||||||
|
|
||||||
|
for (const AscIndexEntry& e : index.entries) {
|
||||||
|
out << e.relativePath.generic_string() << '\t' << e.mtimeUtc << '\t' << e.header.ncols << '\t'
|
||||||
|
<< e.header.nrows << '\t' << e.header.xll << '\t' << e.header.yll << '\t' << e.header.cellsize
|
||||||
|
<< '\t' << e.header.nodata << '\t' << e.northMin << '\t' << e.northMax << '\t' << e.eastMin
|
||||||
|
<< '\t' << e.eastMax << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<AscIndex> loadAscIndex(const std::filesystem::path& indexPath) {
|
||||||
|
std::ifstream in(indexPath);
|
||||||
|
if (!in) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
if (!std::getline(in, line) || line != kAscIndexMagic) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
AscIndex index;
|
||||||
|
while (std::getline(in, line)) {
|
||||||
|
if (line.empty() || line.starts_with('#')) {
|
||||||
|
if (line.starts_with("# catalog=")) {
|
||||||
|
index.catalogRoot = line.substr(std::string_view("# catalog=").size());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream ss(line);
|
||||||
|
AscIndexEntry entry;
|
||||||
|
std::string rel;
|
||||||
|
if (!(ss >> rel >> entry.mtimeUtc >> entry.header.ncols >> entry.header.nrows >> entry.header.xll >>
|
||||||
|
entry.header.yll >> entry.header.cellsize >> entry.header.nodata >> entry.northMin >>
|
||||||
|
entry.northMax >> entry.eastMin >> entry.eastMax)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entry.relativePath = rel;
|
||||||
|
index.entries.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool ascIndexEntryStale(
|
||||||
|
const AscIndexEntry& entry,
|
||||||
|
const std::filesystem::path& catalogRoot) {
|
||||||
|
const std::filesystem::path full = catalogRoot / entry.relativePath;
|
||||||
|
return fileMtimeUtc(full) != entry.mtimeUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class AscIndexStatus { Loaded, Created, Refreshed };
|
||||||
|
|
||||||
|
struct AscIndexResult {
|
||||||
|
AscIndex index;
|
||||||
|
AscIndexStatus status = AscIndexStatus::Loaded;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline AscIndexResult loadOrBuildAscIndex(const std::filesystem::path& catalogRoot) {
|
||||||
|
const std::filesystem::path indexPath = defaultIndexPath(catalogRoot);
|
||||||
|
|
||||||
|
if (const std::optional<AscIndex> loaded = loadAscIndex(indexPath)) {
|
||||||
|
AscIndex index = *loaded;
|
||||||
|
if (index.catalogRoot.empty()) {
|
||||||
|
index.catalogRoot = std::filesystem::absolute(catalogRoot);
|
||||||
|
}
|
||||||
|
bool stale = false;
|
||||||
|
for (const AscIndexEntry& e : index.entries) {
|
||||||
|
if (ascIndexEntryStale(e, index.catalogRoot)) {
|
||||||
|
stale = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!stale) {
|
||||||
|
return {std::move(index), AscIndexStatus::Loaded};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool hadIndexFile = std::filesystem::exists(indexPath);
|
||||||
|
AscIndex built = buildAscIndex(catalogRoot);
|
||||||
|
writeAscIndex(built, indexPath);
|
||||||
|
return {
|
||||||
|
std::move(built),
|
||||||
|
hadIndexFile ? AscIndexStatus::Refreshed : AscIndexStatus::Created,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<const AscIndexEntry*> selectEntriesForCorridor(
|
||||||
|
const AscIndex& index,
|
||||||
|
const double northMin,
|
||||||
|
const double northMax,
|
||||||
|
const double eastMin,
|
||||||
|
const double eastMax) {
|
||||||
|
std::vector<const AscIndexEntry*> selected;
|
||||||
|
selected.reserve(index.entries.size());
|
||||||
|
for (const AscIndexEntry& e : index.entries) {
|
||||||
|
if (bboxIntersects(northMin, northMax, eastMin, eastMax, e)) {
|
||||||
|
selected.push_back(&e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
152
eu07-parser/include/eu07/nmt/asc_lookup.hpp
Normal file
152
eu07-parser/include/eu07/nmt/asc_lookup.hpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// NMT1 (ASC): wyszukiwanie komorki w promieniu od probki profilu toru.
|
||||||
|
|
||||||
|
#include <eu07/geo/puwg1992.hpp>
|
||||||
|
#include <eu07/nmt/asc_index.hpp>
|
||||||
|
#include <eu07/nmt/asc_ram.hpp>
|
||||||
|
#include <eu07/nmt/profile_types.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
[[nodiscard]] inline const AscIndexEntry* findAscEntryForPoint(
|
||||||
|
const AscIndex& index,
|
||||||
|
const double north,
|
||||||
|
const double east) {
|
||||||
|
for (const AscIndexEntry& entry : index.entries) {
|
||||||
|
if (north >= entry.northMin && north <= entry.northMax && east >= entry.eastMin &&
|
||||||
|
east <= entry.eastMax) {
|
||||||
|
return &entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline bool locateCell(
|
||||||
|
const AscHeader& header,
|
||||||
|
const double north,
|
||||||
|
const double east,
|
||||||
|
int& outRow,
|
||||||
|
int& outCol) {
|
||||||
|
const double northTop = header.yll + header.nrows * header.cellsize;
|
||||||
|
if (north < header.yll || north > northTop || east < header.xll ||
|
||||||
|
east > header.xll + header.ncols * header.cellsize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCol = static_cast<int>(std::floor((east - header.xll) / header.cellsize));
|
||||||
|
outRow = static_cast<int>(std::floor((northTop - north) / header.cellsize));
|
||||||
|
return outCol >= 0 && outCol < header.ncols && outRow >= 0 && outRow < header.nrows;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline scene::Vec3 cellCenterSim(
|
||||||
|
const AscHeader& header,
|
||||||
|
const int row,
|
||||||
|
const int col,
|
||||||
|
const geo::PuwgMasterOffset& master) {
|
||||||
|
const double northTop = header.yll + header.nrows * header.cellsize;
|
||||||
|
const double north = northTop - (static_cast<double>(row) + 0.5) * header.cellsize;
|
||||||
|
const double east = header.xll + (static_cast<double>(col) + 0.5) * header.cellsize;
|
||||||
|
return {
|
||||||
|
master.eastM - east,
|
||||||
|
0.0,
|
||||||
|
north - master.northM,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
inline void fillProfileNmtHeights(
|
||||||
|
std::vector<RouteChainageSample>& samples,
|
||||||
|
const AscIndex& index,
|
||||||
|
const std::filesystem::path& catalogRoot,
|
||||||
|
const geo::PuwgMasterOffset& master,
|
||||||
|
const double radiusM,
|
||||||
|
AscTileCache& cache) {
|
||||||
|
if (radiusM <= 0.0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double radiusSq = radiusM * radiusM;
|
||||||
|
AscTileRam tile;
|
||||||
|
|
||||||
|
for (RouteChainageSample& sample : samples) {
|
||||||
|
const AscIndexEntry* entry =
|
||||||
|
findAscEntryForPoint(index, sample.geo.north, sample.geo.east);
|
||||||
|
if (entry == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cache.attachHeights(tile, *entry, catalogRoot)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int row0 = 0;
|
||||||
|
int col0 = 0;
|
||||||
|
if (!detail::locateCell(tile.header, sample.geo.north, sample.geo.east, row0, col0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double cell = tile.header.cellsize;
|
||||||
|
const int cellWindow = static_cast<int>(std::ceil(radiusM / cell)) + 1;
|
||||||
|
|
||||||
|
double bestDistSq = radiusSq + 1.0;
|
||||||
|
std::optional<double> bestHeight;
|
||||||
|
|
||||||
|
for (int dr = -cellWindow; dr <= cellWindow; ++dr) {
|
||||||
|
for (int dc = -cellWindow; dc <= cellWindow; ++dc) {
|
||||||
|
const int row = row0 + dr;
|
||||||
|
const int col = col0 + dc;
|
||||||
|
if (row < 0 || row >= tile.header.nrows || col < 0 || col >= tile.header.ncols) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<double> height = tile.heightAt(row, col);
|
||||||
|
if (!height) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene::Vec3 cellSim = detail::cellCenterSim(tile.header, row, col, master);
|
||||||
|
const double dx = cellSim.x - sample.sim.x;
|
||||||
|
const double dz = cellSim.z - sample.sim.z;
|
||||||
|
const double distSq = dx * dx + dz * dz;
|
||||||
|
if (distSq <= radiusSq && distSq < bestDistSq) {
|
||||||
|
bestDistSq = distSq;
|
||||||
|
bestHeight = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sample.nmtHeight = bestHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline ProfileNmtResult processRouteProfileNmt(
|
||||||
|
const AscIndex& index,
|
||||||
|
const std::filesystem::path& catalogRoot,
|
||||||
|
const geo::PuwgMasterOffset& master,
|
||||||
|
const double radiusM,
|
||||||
|
std::vector<RouteChainageSample>& samples,
|
||||||
|
AscTileCache& cache) {
|
||||||
|
ProfileNmtResult result;
|
||||||
|
const std::size_t tilesBefore = cache.heightsByPath.size();
|
||||||
|
|
||||||
|
fillProfileNmtHeights(samples, index, catalogRoot, master, radiusM, cache);
|
||||||
|
|
||||||
|
result.tilesLoaded = cache.heightsByPath.size() - tilesBefore;
|
||||||
|
for (const RouteChainageSample& sample : samples) {
|
||||||
|
if (sample.nmtHeight) {
|
||||||
|
++result.samplesWithNmt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
228
eu07-parser/include/eu07/nmt/asc_ram.hpp
Normal file
228
eu07-parser/include/eu07/nmt/asc_ram.hpp
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Kafel ASC w RAM + cache między trasami (lazy load per kafel).
|
||||||
|
|
||||||
|
#include <eu07/geo/puwg1992.hpp>
|
||||||
|
#include <eu07/nmt/asc_index.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
struct AscTileRam {
|
||||||
|
AscHeader header{};
|
||||||
|
std::shared_ptr<const std::vector<float>> heights;
|
||||||
|
std::filesystem::path relativePath;
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t index(const int row, const int col) const {
|
||||||
|
return static_cast<std::size_t>(row) * static_cast<std::size_t>(header.ncols) + static_cast<std::size_t>(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool validAt(const int row, const int col) const {
|
||||||
|
if (heights == nullptr || row < 0 || row >= header.nrows || col < 0 || col >= header.ncols) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const float z = (*heights)[index(row, col)];
|
||||||
|
return !std::isnan(z);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<double> heightAt(const int row, const int col) const {
|
||||||
|
if (!validAt(row, col)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return static_cast<double>((*heights)[index(row, col)]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline std::shared_ptr<std::vector<float>> makeMutableHeightsBuffer(const AscHeader& header) {
|
||||||
|
const std::size_t cellCount =
|
||||||
|
static_cast<std::size_t>(header.nrows) * static_cast<std::size_t>(header.ncols);
|
||||||
|
return std::make_shared<std::vector<float>>(
|
||||||
|
cellCount, std::numeric_limits<float>::quiet_NaN());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<char> readAscFileBuffer(const std::filesystem::path& ascPath) {
|
||||||
|
std::ifstream file(ascPath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const std::streamsize size = file.tellg();
|
||||||
|
if (size <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
std::vector<char> buffer(static_cast<std::size_t>(size) + 1);
|
||||||
|
if (!file.read(buffer.data(), size)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
buffer[static_cast<std::size_t>(size)] = '\0';
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline double fastParseDouble(char** ptr, char* endPtr) {
|
||||||
|
char* p = *ptr;
|
||||||
|
while (p < endPtr && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
if (p >= endPtr) {
|
||||||
|
*ptr = p;
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool neg = false;
|
||||||
|
if (*p == '-') {
|
||||||
|
neg = true;
|
||||||
|
++p;
|
||||||
|
} else if (*p == '+') {
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
|
||||||
|
double val = 0.0;
|
||||||
|
while (p < endPtr && *p >= '0' && *p <= '9') {
|
||||||
|
val = val * 10.0 + static_cast<double>(*p - '0');
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
if (p < endPtr && (*p == '.' || *p == ',')) {
|
||||||
|
++p;
|
||||||
|
double frac = 1.0;
|
||||||
|
while (p < endPtr && *p >= '0' && *p <= '9') {
|
||||||
|
frac *= 0.1;
|
||||||
|
val += static_cast<double>(*p - '0') * frac;
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr = p;
|
||||||
|
return neg ? -val : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void fastSkipToken(char** ptr, char* endPtr) {
|
||||||
|
char* p = *ptr;
|
||||||
|
while (p < endPtr && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
while (p < endPtr && !(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
*ptr = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseAscHeaderFromBuffer(char*& ptr, char* endPtr, AscHeader& header) {
|
||||||
|
for (int h = 0; h < 6; ++h) {
|
||||||
|
fastSkipToken(&ptr, endPtr);
|
||||||
|
if (h == 0) {
|
||||||
|
header.ncols = static_cast<int>(fastParseDouble(&ptr, endPtr));
|
||||||
|
} else if (h == 1) {
|
||||||
|
header.nrows = static_cast<int>(fastParseDouble(&ptr, endPtr));
|
||||||
|
} else if (h == 2) {
|
||||||
|
header.xll = fastParseDouble(&ptr, endPtr);
|
||||||
|
} else if (h == 3) {
|
||||||
|
header.yll = fastParseDouble(&ptr, endPtr);
|
||||||
|
} else if (h == 4) {
|
||||||
|
header.cellsize = fastParseDouble(&ptr, endPtr);
|
||||||
|
} else {
|
||||||
|
header.nodata = fastParseDouble(&ptr, endPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return header.ncols > 0 && header.nrows > 0 && header.cellsize > 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<AscTileRam> loadAscTileRam(
|
||||||
|
const AscIndexEntry& entry,
|
||||||
|
const std::filesystem::path& ascPath) {
|
||||||
|
std::vector<char> buffer = readAscFileBuffer(ascPath);
|
||||||
|
if (buffer.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* ptr = buffer.data();
|
||||||
|
char* endPtr = buffer.data() + buffer.size() - 1;
|
||||||
|
|
||||||
|
AscTileRam tile;
|
||||||
|
tile.relativePath = entry.relativePath;
|
||||||
|
|
||||||
|
if (!parseAscHeaderFromBuffer(ptr, endPtr, tile.header)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (tile.header.ncols > 20000 || tile.header.nrows > 20000) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::shared_ptr<std::vector<float>> heights = makeMutableHeightsBuffer(tile.header);
|
||||||
|
tile.heights = heights;
|
||||||
|
|
||||||
|
for (int row = 0; row < tile.header.nrows; ++row) {
|
||||||
|
for (int col = 0; col < tile.header.ncols; ++col) {
|
||||||
|
const double value = fastParseDouble(&ptr, endPtr);
|
||||||
|
const std::size_t idx =
|
||||||
|
static_cast<std::size_t>(row) * static_cast<std::size_t>(tile.header.ncols) +
|
||||||
|
static_cast<std::size_t>(col);
|
||||||
|
if (std::abs(value - tile.header.nodata) <= 0.1) {
|
||||||
|
(*heights)[idx] = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
} else {
|
||||||
|
(*heights)[idx] = static_cast<float>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
struct AscTileCache {
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<const std::vector<float>>> heightsByPath;
|
||||||
|
std::unordered_map<std::string, AscHeader> headerByPath;
|
||||||
|
std::size_t ramBytes = 0;
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
[[nodiscard]] bool attachHeights(
|
||||||
|
AscTileRam& tile,
|
||||||
|
const AscIndexEntry& entry,
|
||||||
|
const std::filesystem::path& catalogRoot) {
|
||||||
|
const std::string key = entry.relativePath.generic_string();
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
const auto headerIt = headerByPath.find(key);
|
||||||
|
const auto heightsIt = heightsByPath.find(key);
|
||||||
|
if (headerIt != headerByPath.end() && heightsIt != heightsByPath.end()) {
|
||||||
|
tile.header = headerIt->second;
|
||||||
|
tile.heights = heightsIt->second;
|
||||||
|
tile.relativePath = entry.relativePath;
|
||||||
|
return tile.header.ncols > 0 && tile.header.nrows > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const std::optional<AscTileRam> loaded =
|
||||||
|
detail::loadAscTileRam(entry, catalogRoot / entry.relativePath)) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
const std::string storeKey = loaded->relativePath.generic_string();
|
||||||
|
if (const auto heightsIt = heightsByPath.find(storeKey); heightsIt != heightsByPath.end()) {
|
||||||
|
tile.header = headerByPath[storeKey];
|
||||||
|
tile.heights = heightsIt->second;
|
||||||
|
} else {
|
||||||
|
heightsByPath.emplace(storeKey, loaded->heights);
|
||||||
|
headerByPath.emplace(storeKey, loaded->header);
|
||||||
|
ramBytes += loaded->heights->size() * sizeof(float);
|
||||||
|
tile.header = loaded->header;
|
||||||
|
tile.heights = loaded->heights;
|
||||||
|
}
|
||||||
|
tile.relativePath = loaded->relativePath;
|
||||||
|
return tile.header.ncols > 0 && tile.header.nrows > 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
16
eu07-parser/include/eu07/nmt/parallel.hpp
Normal file
16
eu07-parser/include/eu07/nmt/parallel.hpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
[[nodiscard]] inline unsigned int workerThreadCount() {
|
||||||
|
const unsigned int hw = std::thread::hardware_concurrency();
|
||||||
|
if (hw <= 2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return hw - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
92
eu07-parser/include/eu07/nmt/profile_export.hpp
Normal file
92
eu07-parser/include/eu07/nmt/profile_export.hpp
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/nmt/profile_height.hpp>
|
||||||
|
#include <eu07/nmt/profile_slope.hpp>
|
||||||
|
#include <eu07/nmt/profile_types.hpp>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
struct ProfileCsvOptions {
|
||||||
|
bool includeRouteColumn = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void writeProfileCsv(
|
||||||
|
const std::filesystem::path& outPath,
|
||||||
|
const std::vector<RouteChainageSample>& samples,
|
||||||
|
const ProfileCsvOptions& options = {}) {
|
||||||
|
std::ofstream out(outPath, std::ios::binary);
|
||||||
|
if (!out) {
|
||||||
|
throw std::runtime_error("Nie mozna zapisac: " + outPath.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::optional<double>> slopes = slopePromilleEvery50m(samples);
|
||||||
|
|
||||||
|
out << std::fixed << std::setprecision(3);
|
||||||
|
if (options.includeRouteColumn) {
|
||||||
|
out << "route,chainage_m,east,north,height,slope_promille\n";
|
||||||
|
} else {
|
||||||
|
out << "chainage_m,east,north,height,slope_promille\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < samples.size(); ++i) {
|
||||||
|
const RouteChainageSample& s = samples[i];
|
||||||
|
if (options.includeRouteColumn) {
|
||||||
|
out << s.routeIndex << ',';
|
||||||
|
}
|
||||||
|
out << s.chainageM << ',' << s.geo.east << ',' << s.geo.north << ','
|
||||||
|
<< profileSampleHeightZ(s) << ',';
|
||||||
|
if (slopes[i]) {
|
||||||
|
out << *slopes[i];
|
||||||
|
}
|
||||||
|
out << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeCombinedProfileCsv(
|
||||||
|
const std::filesystem::path& outPath,
|
||||||
|
const std::vector<RouteChainageSample>& samples) {
|
||||||
|
writeProfileCsv(outPath, samples, {.includeRouteColumn = true});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeCombinedProfileXyz(
|
||||||
|
const std::filesystem::path& outPath,
|
||||||
|
const std::vector<RouteChainageSample>& samples) {
|
||||||
|
std::ofstream out(outPath, std::ios::binary);
|
||||||
|
if (!out) {
|
||||||
|
throw std::runtime_error("Nie mozna zapisac: " + outPath.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
out << std::fixed << std::setprecision(1);
|
||||||
|
for (const RouteChainageSample& s : samples) {
|
||||||
|
out << s.geo.east << ' ' << s.geo.north << ' ' << profileSampleHeightZ(s) << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeRouteProfileReport(
|
||||||
|
const std::filesystem::path& outPath,
|
||||||
|
const std::vector<RouteChainageSample>& samples) {
|
||||||
|
std::ofstream out(outPath);
|
||||||
|
if (!out) {
|
||||||
|
throw std::runtime_error("Nie mozna zapisac: " + outPath.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
out << std::fixed << std::setprecision(3);
|
||||||
|
out << "# chainage_m east north track_h nmt_h (PUWG1992 / EPSG:2180)\n";
|
||||||
|
for (const RouteChainageSample& s : samples) {
|
||||||
|
out << s.chainageM << ' ' << s.geo.east << ' ' << s.geo.north << ' ' << s.trackHeight << ' ';
|
||||||
|
if (s.nmtHeight) {
|
||||||
|
out << *s.nmtHeight;
|
||||||
|
} else {
|
||||||
|
out << '-';
|
||||||
|
}
|
||||||
|
out << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
14
eu07-parser/include/eu07/nmt/profile_height.hpp
Normal file
14
eu07-parser/include/eu07/nmt/profile_height.hpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/nmt/profile_types.hpp>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
[[nodiscard]] inline double profileSampleHeightZ(const RouteChainageSample& sample) {
|
||||||
|
if (sample.nmtHeight) {
|
||||||
|
return *sample.nmtHeight;
|
||||||
|
}
|
||||||
|
return sample.trackHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
102
eu07-parser/include/eu07/nmt/profile_slope.hpp
Normal file
102
eu07-parser/include/eu07/nmt/profile_slope.hpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/nmt/profile_height.hpp>
|
||||||
|
#include <eu07/nmt/profile_types.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
[[nodiscard]] inline double horizontalDistanceXY(
|
||||||
|
const RouteChainageSample& a,
|
||||||
|
const RouteChainageSample& b) noexcept {
|
||||||
|
const double dx = b.geo.east - a.geo.east;
|
||||||
|
const double dy = b.geo.north - a.geo.north;
|
||||||
|
return std::sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<double> slopePermilleOverChainageSegment(
|
||||||
|
const std::vector<RouteChainageSample>& samples,
|
||||||
|
const std::size_t routeStart,
|
||||||
|
const std::size_t routeEnd,
|
||||||
|
const double segStartChainage,
|
||||||
|
const double segEndChainage,
|
||||||
|
std::optional<std::size_t>& outEndIndex) {
|
||||||
|
std::optional<std::size_t> startIndex;
|
||||||
|
std::optional<std::size_t> prevIndex;
|
||||||
|
double pathLengthM = 0.0;
|
||||||
|
|
||||||
|
for (std::size_t i = routeStart; i < routeEnd; ++i) {
|
||||||
|
if (samples[i].chainageM + 1e-6 < segStartChainage) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (samples[i].chainageM > segEndChainage + 1e-6) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startIndex) {
|
||||||
|
startIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevIndex) {
|
||||||
|
pathLengthM += horizontalDistanceXY(samples[*prevIndex], samples[i]);
|
||||||
|
}
|
||||||
|
prevIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startIndex || !prevIndex || *startIndex == *prevIndex || pathLengthM <= 1e-6) {
|
||||||
|
outEndIndex = std::nullopt;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
outEndIndex = prevIndex;
|
||||||
|
const double dh =
|
||||||
|
profileSampleHeightZ(samples[*prevIndex]) - profileSampleHeightZ(samples[*startIndex]);
|
||||||
|
return dh / pathLengthM * 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<std::optional<double>> slopePromilleEvery50m(
|
||||||
|
const std::vector<RouteChainageSample>& samples) {
|
||||||
|
std::vector<std::optional<double>> slopes(samples.size());
|
||||||
|
if (samples.empty()) {
|
||||||
|
return slopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr double kSlopeIntervalM = 50.0;
|
||||||
|
|
||||||
|
for (std::size_t routeStart = 0; routeStart < samples.size();) {
|
||||||
|
const int routeIndex = samples[routeStart].routeIndex;
|
||||||
|
std::size_t routeEnd = routeStart;
|
||||||
|
while (routeEnd < samples.size() && samples[routeEnd].routeIndex == routeIndex) {
|
||||||
|
++routeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double maxChainage = samples[routeEnd - 1].chainageM;
|
||||||
|
for (double segStart = 0.0; segStart <= maxChainage + 1e-6; segStart += kSlopeIntervalM) {
|
||||||
|
const double segEndTarget = segStart + kSlopeIntervalM;
|
||||||
|
const bool isLastSegment = segEndTarget >= maxChainage - 1e-6;
|
||||||
|
const double segEndChainage = isLastSegment ? maxChainage : segEndTarget;
|
||||||
|
|
||||||
|
std::optional<std::size_t> endIndex;
|
||||||
|
const std::optional<double> slope = slopePermilleOverChainageSegment(
|
||||||
|
samples,
|
||||||
|
routeStart,
|
||||||
|
routeEnd,
|
||||||
|
segStart,
|
||||||
|
segEndChainage,
|
||||||
|
endIndex);
|
||||||
|
if (slope && endIndex) {
|
||||||
|
slopes[*endIndex] = *slope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routeStart = routeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
25
eu07-parser/include/eu07/nmt/profile_types.hpp
Normal file
25
eu07-parser/include/eu07/nmt/profile_types.hpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/geo/puwg1992.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
struct RouteChainageSample {
|
||||||
|
double chainageM = 0.0;
|
||||||
|
scene::Vec3 sim{};
|
||||||
|
geo::PuwgPoint geo{};
|
||||||
|
double trackHeight = 0.0;
|
||||||
|
std::optional<double> nmtHeight;
|
||||||
|
int routeIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProfileNmtResult {
|
||||||
|
std::size_t samplesWithNmt = 0;
|
||||||
|
std::size_t tilesLoaded = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
52
eu07-parser/include/eu07/nmt/progress.hpp
Normal file
52
eu07-parser/include/eu07/nmt/progress.hpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
class ProgressLine {
|
||||||
|
public:
|
||||||
|
void begin(std::string label, const std::size_t total) {
|
||||||
|
label_ = std::move(label);
|
||||||
|
total_ = total > 0 ? total : 1;
|
||||||
|
current_ = 0;
|
||||||
|
lastPrint_ = std::chrono::steady_clock::now();
|
||||||
|
render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(const std::size_t current) {
|
||||||
|
current_ = current > total_ ? total_ : current;
|
||||||
|
render(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void advance(const std::size_t delta = 1) { set(current_ + delta); }
|
||||||
|
|
||||||
|
void end() {
|
||||||
|
set(total_);
|
||||||
|
std::cerr << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void render(const bool force) {
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
if (!force &&
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(now - lastPrint_).count() < 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastPrint_ = now;
|
||||||
|
|
||||||
|
const int pct = static_cast<int>((100.0 * static_cast<double>(current_)) / static_cast<double>(total_));
|
||||||
|
std::cerr << '\r' << label_ << ' ' << current_ << '/' << total_ << " (" << pct << "%) "
|
||||||
|
<< std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label_;
|
||||||
|
std::size_t total_ = 1;
|
||||||
|
std::size_t current_ = 0;
|
||||||
|
std::chrono::steady_clock::time_point lastPrint_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
462
eu07-parser/include/eu07/nmt/route_geometry.hpp
Normal file
462
eu07-parser/include/eu07/nmt/route_geometry.hpp
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Geometria trasy: próbkowanie co N metrów wzdłuż osi toru + odległość boczna (pas 1 m).
|
||||||
|
|
||||||
|
#include <eu07/geo/puwg1992.hpp>
|
||||||
|
#include <eu07/geo/spatial_grid.hpp>
|
||||||
|
#include <eu07/nmt/profile_types.hpp>
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/node/track.hpp>
|
||||||
|
#include <eu07/scene/track_routes.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
struct TrackLineSegment {
|
||||||
|
scene::Vec3 p1{};
|
||||||
|
scene::Vec3 p2{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BezierSpan {
|
||||||
|
scene::Vec3 p1;
|
||||||
|
scene::Vec3 cv1;
|
||||||
|
scene::Vec3 cv2;
|
||||||
|
scene::Vec3 p2;
|
||||||
|
double lengthM = 0.0;
|
||||||
|
double chainageStartM = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RouteGeometry {
|
||||||
|
std::string label;
|
||||||
|
double totalLengthM = 0.0;
|
||||||
|
std::vector<BezierSpan> spans;
|
||||||
|
std::vector<TrackLineSegment> segments;
|
||||||
|
geo::SpatialGrid<25> grid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RouteGeometryOptions {
|
||||||
|
double distanceSegmentLengthM = 2.0;
|
||||||
|
int minSegmentsPerBezier = 4;
|
||||||
|
int maxSegmentsPerBezier = 300;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline double vecLength(const scene::Vec3& v) noexcept {
|
||||||
|
return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline scene::Vec3 vecAdd(const scene::Vec3& a, const scene::Vec3& b) noexcept {
|
||||||
|
return {a.x + b.x, a.y + b.y, a.z + b.z};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline scene::Vec3 vecScale(const scene::Vec3& v, const double s) noexcept {
|
||||||
|
return {v.x * s, v.y * s, v.z * s};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline scene::Vec3 cubicBezier(
|
||||||
|
const double t,
|
||||||
|
const scene::Vec3& p1,
|
||||||
|
const scene::Vec3& cv1,
|
||||||
|
const scene::Vec3& cv2,
|
||||||
|
const scene::Vec3& p2) {
|
||||||
|
const scene::Vec3 c1 = vecAdd(p1, cv1);
|
||||||
|
const scene::Vec3 c2 = vecAdd(p2, cv2);
|
||||||
|
const double u = 1.0 - t;
|
||||||
|
const double tt = t * t;
|
||||||
|
const double uu = u * u;
|
||||||
|
const double uuu = uu * u;
|
||||||
|
const double ttt = tt * t;
|
||||||
|
|
||||||
|
scene::Vec3 p = vecScale(p1, uuu);
|
||||||
|
p = vecAdd(p, vecScale(c1, 3.0 * uu * t));
|
||||||
|
p = vecAdd(p, vecScale(c2, 3.0 * u * tt));
|
||||||
|
p = vecAdd(p, vecScale(p2, ttt));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline double approximateBezierLength(
|
||||||
|
const scene::Vec3& p1,
|
||||||
|
const scene::Vec3& cv1,
|
||||||
|
const scene::Vec3& cv2,
|
||||||
|
const scene::Vec3& p2) {
|
||||||
|
const scene::Vec3 c1 = vecAdd(p1, cv1);
|
||||||
|
const scene::Vec3 c2 = vecAdd(p2, cv2);
|
||||||
|
return vecLength(cv1) + vecLength(vecAdd(c2, vecScale(c1, -1.0))) + vecLength(cv2);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void appendBezierSpansAndSegments(
|
||||||
|
const scene::TrackBezier& bez,
|
||||||
|
const scene::Vec3& originOffset,
|
||||||
|
const RouteGeometryOptions& opts,
|
||||||
|
const double chainageStart,
|
||||||
|
std::vector<BezierSpan>& spans,
|
||||||
|
std::vector<TrackLineSegment>& segments,
|
||||||
|
double& outTotalLength) {
|
||||||
|
BezierSpan span;
|
||||||
|
span.p1 = scene::detail::track_routes::addOffset(bez.p1, originOffset);
|
||||||
|
span.p2 = scene::detail::track_routes::addOffset(bez.p2, originOffset);
|
||||||
|
span.cv1 = bez.cv1;
|
||||||
|
span.cv2 = bez.cv2;
|
||||||
|
span.lengthM = approximateBezierLength(span.p1, span.cv1, span.cv2, span.p2);
|
||||||
|
span.chainageStartM = chainageStart;
|
||||||
|
spans.push_back(span);
|
||||||
|
outTotalLength += span.lengthM;
|
||||||
|
|
||||||
|
int pieces = static_cast<int>(span.lengthM / opts.distanceSegmentLengthM);
|
||||||
|
if (pieces < opts.minSegmentsPerBezier) {
|
||||||
|
pieces = opts.minSegmentsPerBezier;
|
||||||
|
}
|
||||||
|
if (pieces > opts.maxSegmentsPerBezier) {
|
||||||
|
pieces = opts.maxSegmentsPerBezier;
|
||||||
|
}
|
||||||
|
|
||||||
|
scene::Vec3 prev = span.p1;
|
||||||
|
for (int i = 1; i <= pieces; ++i) {
|
||||||
|
const double t = static_cast<double>(i) / static_cast<double>(pieces);
|
||||||
|
const scene::Vec3 curr = cubicBezier(t, span.p1, span.cv1, span.cv2, span.p2);
|
||||||
|
segments.push_back({prev, curr});
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::vector<scene::TrackBezier>* beziersForSegment(
|
||||||
|
const scene::SceneDocument& doc,
|
||||||
|
const scene::TrackSegmentEndpoints& segment) {
|
||||||
|
switch (segment.ref.kind) {
|
||||||
|
case scene::TrackSegmentKind::Normal:
|
||||||
|
return &doc.nodeTrackNormal[segment.ref.index].beziers;
|
||||||
|
case scene::TrackSegmentKind::Switch:
|
||||||
|
return &doc.nodeTrackSwitch[segment.ref.index].beziers;
|
||||||
|
case scene::TrackSegmentKind::Road:
|
||||||
|
return &doc.nodeTrackRoad[segment.ref.index].beziers;
|
||||||
|
case scene::TrackSegmentKind::Cross:
|
||||||
|
return &doc.nodeTrackCross[segment.ref.index].beziers;
|
||||||
|
case scene::TrackSegmentKind::Other:
|
||||||
|
return &doc.nodeTrackOther[segment.ref.index].beziers;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline scene::Vec3 headerOffsetForSegment(
|
||||||
|
const scene::SceneDocument& doc,
|
||||||
|
const scene::TrackSegmentEndpoints& segment) {
|
||||||
|
switch (segment.ref.kind) {
|
||||||
|
case scene::TrackSegmentKind::Normal:
|
||||||
|
return doc.nodeTrackNormal[segment.ref.index].header.originOffset;
|
||||||
|
case scene::TrackSegmentKind::Switch:
|
||||||
|
return doc.nodeTrackSwitch[segment.ref.index].header.originOffset;
|
||||||
|
case scene::TrackSegmentKind::Road:
|
||||||
|
return doc.nodeTrackRoad[segment.ref.index].header.originOffset;
|
||||||
|
case scene::TrackSegmentKind::Cross:
|
||||||
|
return doc.nodeTrackCross[segment.ref.index].header.originOffset;
|
||||||
|
case scene::TrackSegmentKind::Other:
|
||||||
|
return doc.nodeTrackOther[segment.ref.index].header.originOffset;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline scene::Vec3 pointOnBezierSpan(const BezierSpan& span, const double localT) {
|
||||||
|
return cubicBezier(localT, span.p1, span.cv1, span.cv2, span.p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
[[nodiscard]] inline RouteGeometry buildRouteGeometry(
|
||||||
|
const scene::SceneDocument& doc,
|
||||||
|
const scene::TrackRouteBuildResult& routes,
|
||||||
|
const std::size_t routeIndex,
|
||||||
|
const RouteGeometryOptions& opts = {}) {
|
||||||
|
RouteGeometry geometry;
|
||||||
|
if (routeIndex >= routes.routes.size()) {
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene::TrackRoute& route = routes.routes[routeIndex];
|
||||||
|
geometry.label = route.label;
|
||||||
|
geometry.spans.reserve(route.links.size() * 4);
|
||||||
|
geometry.segments.reserve(route.links.size() * 32);
|
||||||
|
|
||||||
|
double chainage = 0.0;
|
||||||
|
for (const scene::TrackRouteLink& link : route.links) {
|
||||||
|
const scene::TrackSegmentEndpoints& segment = routes.segments[link.segmentIndex];
|
||||||
|
const std::vector<scene::TrackBezier>* beziers = detail::beziersForSegment(doc, segment);
|
||||||
|
if (beziers == nullptr || beziers->empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene::Vec3 origin = detail::headerOffsetForSegment(doc, segment);
|
||||||
|
const std::vector<scene::TrackBezier>& list = *beziers;
|
||||||
|
if (link.reversed) {
|
||||||
|
for (auto it = list.rbegin(); it != list.rend(); ++it) {
|
||||||
|
scene::TrackBezier flipped = *it;
|
||||||
|
std::swap(flipped.p1, flipped.p2);
|
||||||
|
std::swap(flipped.cv1, flipped.cv2);
|
||||||
|
detail::appendBezierSpansAndSegments(
|
||||||
|
flipped, origin, opts, chainage, geometry.spans, geometry.segments, chainage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const scene::TrackBezier& bez : list) {
|
||||||
|
detail::appendBezierSpansAndSegments(
|
||||||
|
bez, origin, opts, chainage, geometry.spans, geometry.segments, chainage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry.totalLengthM = chainage;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < geometry.segments.size(); ++i) {
|
||||||
|
const TrackLineSegment& seg = geometry.segments[i];
|
||||||
|
geo::SpatialGrid<25>::ItemBounds bounds{
|
||||||
|
std::min(seg.p1.x, seg.p2.x),
|
||||||
|
std::max(seg.p1.x, seg.p2.x),
|
||||||
|
std::min(seg.p1.z, seg.p2.z),
|
||||||
|
std::max(seg.p1.z, seg.p2.z),
|
||||||
|
};
|
||||||
|
geometry.grid.insert(i, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline RouteGeometry subsampleRouteGeometryForBand(
|
||||||
|
const RouteGeometry& source,
|
||||||
|
const std::size_t everyNthSegment = 4) {
|
||||||
|
RouteGeometry out;
|
||||||
|
out.label = source.label;
|
||||||
|
out.totalLengthM = source.totalLengthM;
|
||||||
|
if (source.segments.empty() || everyNthSegment == 0) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.segments.reserve(source.segments.size() / everyNthSegment + 1);
|
||||||
|
for (std::size_t i = 0; i < source.segments.size(); i += everyNthSegment) {
|
||||||
|
out.segments.push_back(source.segments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < out.segments.size(); ++i) {
|
||||||
|
const TrackLineSegment& seg = out.segments[i];
|
||||||
|
geo::SpatialGrid<25>::ItemBounds bounds{
|
||||||
|
std::min(seg.p1.x, seg.p2.x),
|
||||||
|
std::max(seg.p1.x, seg.p2.x),
|
||||||
|
std::min(seg.p1.z, seg.p2.z),
|
||||||
|
std::max(seg.p1.z, seg.p2.z),
|
||||||
|
};
|
||||||
|
out.grid.insert(i, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<RouteChainageSample> sampleRouteCenterlineFromGeometry(
|
||||||
|
const RouteGeometry& geometry,
|
||||||
|
const geo::PuwgMasterOffset& master,
|
||||||
|
const double stepM) {
|
||||||
|
std::vector<RouteChainageSample> samples;
|
||||||
|
if (geometry.spans.empty() || geometry.totalLengthM <= 0.0 || stepM <= 0.0) {
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (double chainage = 0.0; chainage <= geometry.totalLengthM + 1e-6; chainage += stepM) {
|
||||||
|
const BezierSpan* span = nullptr;
|
||||||
|
for (const BezierSpan& candidate : geometry.spans) {
|
||||||
|
if (chainage >= candidate.chainageStartM &&
|
||||||
|
chainage <= candidate.chainageStartM + candidate.lengthM + 1e-6) {
|
||||||
|
span = &candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (span == nullptr) {
|
||||||
|
span = &geometry.spans.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
const double local = span->lengthM > 0.0
|
||||||
|
? std::clamp((chainage - span->chainageStartM) / span->lengthM, 0.0, 1.0)
|
||||||
|
: 0.0;
|
||||||
|
const scene::Vec3 sim = detail::pointOnBezierSpan(*span, local);
|
||||||
|
|
||||||
|
RouteChainageSample sample;
|
||||||
|
sample.chainageM = chainage;
|
||||||
|
sample.sim = sim;
|
||||||
|
sample.trackHeight = sim.y;
|
||||||
|
sample.geo = geo::simToGeo(sim, master);
|
||||||
|
samples.push_back(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<RouteChainageSample> sampleRouteCenterlineEvery(
|
||||||
|
const scene::SceneDocument& doc,
|
||||||
|
const scene::TrackRouteBuildResult& routes,
|
||||||
|
const std::size_t routeIndex,
|
||||||
|
const geo::PuwgMasterOffset& master,
|
||||||
|
const double stepM) {
|
||||||
|
std::vector<RouteChainageSample> samples;
|
||||||
|
if (routeIndex >= routes.routes.size() || stepM <= 0.0) {
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene::TrackRoute& route = routes.routes[routeIndex];
|
||||||
|
std::vector<BezierSpan> spans;
|
||||||
|
spans.reserve(route.links.size() * 4);
|
||||||
|
|
||||||
|
double totalLength = 0.0;
|
||||||
|
for (const scene::TrackRouteLink& link : route.links) {
|
||||||
|
const scene::TrackSegmentEndpoints& segment = routes.segments[link.segmentIndex];
|
||||||
|
const std::vector<scene::TrackBezier>* beziers = detail::beziersForSegment(doc, segment);
|
||||||
|
if (beziers == nullptr || beziers->empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scene::Vec3 origin = detail::headerOffsetForSegment(doc, segment);
|
||||||
|
const std::vector<scene::TrackBezier>& list = *beziers;
|
||||||
|
if (link.reversed) {
|
||||||
|
for (auto it = list.rbegin(); it != list.rend(); ++it) {
|
||||||
|
scene::TrackBezier flipped = *it;
|
||||||
|
std::swap(flipped.p1, flipped.p2);
|
||||||
|
std::swap(flipped.cv1, flipped.cv2);
|
||||||
|
BezierSpan span;
|
||||||
|
span.p1 = scene::detail::track_routes::addOffset(flipped.p1, origin);
|
||||||
|
span.p2 = scene::detail::track_routes::addOffset(flipped.p2, origin);
|
||||||
|
span.cv1 = flipped.cv1;
|
||||||
|
span.cv2 = flipped.cv2;
|
||||||
|
span.lengthM =
|
||||||
|
detail::approximateBezierLength(span.p1, span.cv1, span.cv2, span.p2);
|
||||||
|
span.chainageStartM = totalLength;
|
||||||
|
spans.push_back(span);
|
||||||
|
totalLength += span.lengthM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const scene::TrackBezier& bez : list) {
|
||||||
|
BezierSpan span;
|
||||||
|
span.p1 = scene::detail::track_routes::addOffset(bez.p1, origin);
|
||||||
|
span.p2 = scene::detail::track_routes::addOffset(bez.p2, origin);
|
||||||
|
span.cv1 = bez.cv1;
|
||||||
|
span.cv2 = bez.cv2;
|
||||||
|
span.lengthM = detail::approximateBezierLength(span.p1, span.cv1, span.cv2, span.p2);
|
||||||
|
span.chainageStartM = totalLength;
|
||||||
|
spans.push_back(span);
|
||||||
|
totalLength += span.lengthM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spans.empty() || totalLength <= 0.0) {
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (double chainage = 0.0; chainage <= totalLength + 1e-6; chainage += stepM) {
|
||||||
|
const BezierSpan* span = nullptr;
|
||||||
|
for (const BezierSpan& candidate : spans) {
|
||||||
|
if (chainage >= candidate.chainageStartM &&
|
||||||
|
chainage <= candidate.chainageStartM + candidate.lengthM + 1e-6) {
|
||||||
|
span = &candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (span == nullptr) {
|
||||||
|
span = &spans.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
const double local = span->lengthM > 0.0
|
||||||
|
? std::clamp((chainage - span->chainageStartM) / span->lengthM, 0.0, 1.0)
|
||||||
|
: 0.0;
|
||||||
|
const scene::Vec3 sim = detail::pointOnBezierSpan(*span, local);
|
||||||
|
|
||||||
|
RouteChainageSample sample;
|
||||||
|
sample.chainageM = chainage;
|
||||||
|
sample.sim = sim;
|
||||||
|
sample.trackHeight = sim.y;
|
||||||
|
sample.geo = geo::simToGeo(sim, master);
|
||||||
|
samples.push_back(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline double distancePointToSegmentXZ(
|
||||||
|
const scene::Vec3& point,
|
||||||
|
const TrackLineSegment& seg,
|
||||||
|
double& outTrackY) noexcept {
|
||||||
|
const double dx = seg.p2.x - seg.p1.x;
|
||||||
|
const double dz = seg.p2.z - seg.p1.z;
|
||||||
|
const double l2 = dx * dx + dz * dz;
|
||||||
|
if (l2 <= 0.0) {
|
||||||
|
outTrackY = seg.p1.y;
|
||||||
|
const double px = point.x - seg.p1.x;
|
||||||
|
const double pz = point.z - seg.p1.z;
|
||||||
|
return std::sqrt(px * px + pz * pz);
|
||||||
|
}
|
||||||
|
|
||||||
|
double t = ((point.x - seg.p1.x) * dx + (point.z - seg.p1.z) * dz) / l2;
|
||||||
|
t = std::max(0.0, std::min(1.0, t));
|
||||||
|
|
||||||
|
const double projX = seg.p1.x + t * dx;
|
||||||
|
const double projZ = seg.p1.z + t * dz;
|
||||||
|
outTrackY = seg.p1.y + t * (seg.p2.y - seg.p1.y);
|
||||||
|
|
||||||
|
const double qx = point.x - projX;
|
||||||
|
const double qz = point.z - projZ;
|
||||||
|
return std::sqrt(qx * qx + qz * qz);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline double minDistanceToRouteXZ(
|
||||||
|
const scene::Vec3& point,
|
||||||
|
const RouteGeometry& geometry,
|
||||||
|
const double searchRadius,
|
||||||
|
std::vector<std::size_t>& scratch) {
|
||||||
|
geometry.grid.query(point, searchRadius, scratch);
|
||||||
|
|
||||||
|
double best = searchRadius + 1.0;
|
||||||
|
double trackY = 0.0;
|
||||||
|
for (const std::size_t idx : scratch) {
|
||||||
|
const double d = distancePointToSegmentXZ(point, geometry.segments[idx], trackY);
|
||||||
|
if (d < best) {
|
||||||
|
best = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void corridorBboxPUWG(
|
||||||
|
const RouteGeometry& geometry,
|
||||||
|
const geo::PuwgMasterOffset& master,
|
||||||
|
const double marginM,
|
||||||
|
double& northMin,
|
||||||
|
double& northMax,
|
||||||
|
double& eastMin,
|
||||||
|
double& eastMax) {
|
||||||
|
if (geometry.segments.empty()) {
|
||||||
|
northMin = northMax = eastMin = eastMax = 0.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
northMin = 1e18;
|
||||||
|
northMax = -1e18;
|
||||||
|
eastMin = 1e18;
|
||||||
|
eastMax = -1e18;
|
||||||
|
|
||||||
|
for (const TrackLineSegment& seg : geometry.segments) {
|
||||||
|
for (const scene::Vec3& sim : {seg.p1, seg.p2}) {
|
||||||
|
const geo::PuwgPoint geo = geo::simToGeo(sim, master);
|
||||||
|
northMin = std::min(northMin, geo.north);
|
||||||
|
northMax = std::max(northMax, geo.north);
|
||||||
|
eastMin = std::min(eastMin, geo.east);
|
||||||
|
eastMax = std::max(eastMax, geo.east);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
northMin -= marginM;
|
||||||
|
northMax += marginM;
|
||||||
|
eastMin -= marginM;
|
||||||
|
eastMax += marginM;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
14
eu07-parser/include/eu07/nmt/terrain.hpp
Normal file
14
eu07-parser/include/eu07/nmt/terrain.hpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Zbiorcze include modulow obrobki terenu (NMT + profil toru).
|
||||||
|
|
||||||
|
#include <eu07/nmt/asc_index.hpp>
|
||||||
|
#include <eu07/nmt/asc_lookup.hpp>
|
||||||
|
#include <eu07/nmt/asc_ram.hpp>
|
||||||
|
#include <eu07/nmt/profile_export.hpp>
|
||||||
|
#include <eu07/nmt/profile_height.hpp>
|
||||||
|
#include <eu07/nmt/profile_slope.hpp>
|
||||||
|
#include <eu07/nmt/profile_types.hpp>
|
||||||
|
#include <eu07/nmt/route_geometry.hpp>
|
||||||
|
#include <eu07/nmt/terrain_cli.hpp>
|
||||||
|
#include <eu07/nmt/terrain_pipeline.hpp>
|
||||||
33
eu07-parser/include/eu07/nmt/terrain_cli.hpp
Normal file
33
eu07-parser/include/eu07/nmt/terrain_cli.hpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
struct TerrainProfileOptions {
|
||||||
|
std::optional<std::filesystem::path> nmtDir;
|
||||||
|
double profileStepM = 10.0;
|
||||||
|
double nmtRadiusM = 1.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline TerrainProfileOptions parseTerrainProfileCli(int argc, char** argv) {
|
||||||
|
TerrainProfileOptions opts;
|
||||||
|
for (int i = 2; i < argc; ++i) {
|
||||||
|
const std::string_view arg = argv[i];
|
||||||
|
if (arg == "--nmt-dir" && i + 1 < argc) {
|
||||||
|
opts.nmtDir = std::filesystem::path{argv[++i]};
|
||||||
|
} else if (arg == "--step" && i + 1 < argc) {
|
||||||
|
opts.profileStepM = std::stod(argv[++i]);
|
||||||
|
} else if (arg == "--band" && i + 1 < argc) {
|
||||||
|
opts.nmtRadiusM = std::stod(argv[++i]);
|
||||||
|
} else if (arg == "--radius" && i + 1 < argc) {
|
||||||
|
opts.nmtRadiusM = std::stod(argv[++i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
102
eu07-parser/include/eu07/nmt/terrain_pipeline.hpp
Normal file
102
eu07-parser/include/eu07/nmt/terrain_pipeline.hpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Pipeline: geometria toru -> profil co N m -> NMT z ASC -> eksport XYZ.
|
||||||
|
|
||||||
|
#include <eu07/geo/puwg1992.hpp>
|
||||||
|
#include <eu07/nmt/asc_index.hpp>
|
||||||
|
#include <eu07/nmt/asc_lookup.hpp>
|
||||||
|
#include <eu07/nmt/asc_ram.hpp>
|
||||||
|
#include <eu07/nmt/parallel.hpp>
|
||||||
|
#include <eu07/nmt/profile_export.hpp>
|
||||||
|
#include <eu07/nmt/profile_types.hpp>
|
||||||
|
#include <eu07/nmt/route_geometry.hpp>
|
||||||
|
#include <eu07/nmt/terrain_cli.hpp>
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/track_routes.hpp>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
#include <print>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::nmt {
|
||||||
|
|
||||||
|
inline void runTerrainProfile(
|
||||||
|
const std::filesystem::path& stem,
|
||||||
|
const scene::SceneDocument& doc,
|
||||||
|
const scene::TrackRouteBuildResult& routes,
|
||||||
|
const geo::PuwgMasterOffset& master,
|
||||||
|
const std::filesystem::path& nmtDir,
|
||||||
|
const TerrainProfileOptions& options) {
|
||||||
|
const AscIndexResult loaded = loadOrBuildAscIndex(nmtDir);
|
||||||
|
const std::filesystem::path catalogRoot = loaded.index.catalogRoot;
|
||||||
|
|
||||||
|
std::println("\nNMT (ASC):");
|
||||||
|
std::println(" indeks: {} plikow ({})",
|
||||||
|
loaded.index.entries.size(),
|
||||||
|
loaded.status == AscIndexStatus::Created ? "utworzono"
|
||||||
|
: loaded.status == AscIndexStatus::Refreshed ? "odswiezono"
|
||||||
|
: "wczytano");
|
||||||
|
std::println(" profil: co {:.0f} m wzdloz osi toru", options.profileStepM);
|
||||||
|
std::println(" NMT: promien {:.1f} m od osi (najblizsza komorka ASC)", options.nmtRadiusM);
|
||||||
|
std::println(" watki: {}", workerThreadCount());
|
||||||
|
std::cerr << std::flush;
|
||||||
|
|
||||||
|
AscTileCache ascCache;
|
||||||
|
std::vector<RouteChainageSample> allProfileSamples;
|
||||||
|
allProfileSamples.reserve(routes.routes.size() * 512);
|
||||||
|
|
||||||
|
const std::size_t routeCount = routes.routes.size();
|
||||||
|
for (std::size_t routeIndex = 0; routeIndex < routeCount; ++routeIndex) {
|
||||||
|
const scene::TrackRoute& route = routes.routes[routeIndex];
|
||||||
|
std::cerr << "[NMT] trasa " << (routeIndex + 1) << '/' << routeCount << ": " << route.label
|
||||||
|
<< " — geometria toru...\n"
|
||||||
|
<< std::flush;
|
||||||
|
|
||||||
|
const RouteGeometry geometry = buildRouteGeometry(doc, routes, routeIndex);
|
||||||
|
if (geometry.segments.empty()) {
|
||||||
|
std::println(" trasa {}: {} — brak geometrii, pomijam", routeIndex + 1, route.label);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "[NMT] trasa " << (routeIndex + 1) << '/' << routeCount << " — profil co "
|
||||||
|
<< options.profileStepM << " m + NMT r=" << options.nmtRadiusM << " m...\n"
|
||||||
|
<< std::flush;
|
||||||
|
|
||||||
|
std::vector<RouteChainageSample> samples =
|
||||||
|
sampleRouteCenterlineFromGeometry(geometry, master, options.profileStepM);
|
||||||
|
for (RouteChainageSample& sample : samples) {
|
||||||
|
sample.routeIndex = static_cast<int>(routeIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileNmtResult nmt = processRouteProfileNmt(
|
||||||
|
loaded.index,
|
||||||
|
catalogRoot,
|
||||||
|
master,
|
||||||
|
options.nmtRadiusM,
|
||||||
|
samples,
|
||||||
|
ascCache);
|
||||||
|
|
||||||
|
allProfileSamples.insert(allProfileSamples.end(), samples.begin(), samples.end());
|
||||||
|
|
||||||
|
std::println(
|
||||||
|
" trasa {}: {} len={:.0f}m profil={} pkt (NMT {}) | cache {:.1f} MB",
|
||||||
|
routeIndex + 1,
|
||||||
|
route.label,
|
||||||
|
geometry.totalLengthM,
|
||||||
|
samples.size(),
|
||||||
|
nmt.samplesWithNmt,
|
||||||
|
static_cast<double>(ascCache.ramBytes) / (1024.0 * 1024.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allProfileSamples.empty()) {
|
||||||
|
const std::filesystem::path xyzPath = stem.string() + ".profil10m.txt";
|
||||||
|
writeCombinedProfileXyz(xyzPath, allProfileSamples);
|
||||||
|
std::println(
|
||||||
|
" profil XYZ: {} ({} pkt, ASCII: east north height, EPSG:2180)",
|
||||||
|
xyzPath.string(),
|
||||||
|
allProfileSamples.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::nmt
|
||||||
500
eu07-parser/include/eu07/parser.hpp
Normal file
500
eu07-parser/include/eu07/parser.hpp
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Plik_tekstowy
|
||||||
|
// + dyrektywy startera (//$…)
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07 {
|
||||||
|
|
||||||
|
struct SourceToken {
|
||||||
|
std::string value;
|
||||||
|
std::size_t sourceLine = 0; // indeks linii w pliku (0 = pierwsza)
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Tokeny (wiki)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isFieldSeparator(const unsigned char ch) noexcept {
|
||||||
|
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ';' || ch == ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void skipFieldSeparators(std::string_view& text) noexcept {
|
||||||
|
while (!text.empty() && isFieldSeparator(static_cast<unsigned char>(text.front()))) {
|
||||||
|
text.remove_prefix(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void tokenizeInto(
|
||||||
|
std::string_view buffer,
|
||||||
|
std::vector<SourceToken>& out,
|
||||||
|
const std::size_t sourceLine = 0) {
|
||||||
|
const auto lineFor = [&](const char* /*tokenStart*/) -> std::size_t { return sourceLine; };
|
||||||
|
|
||||||
|
std::string_view text = buffer;
|
||||||
|
skipFieldSeparators(text);
|
||||||
|
|
||||||
|
while (!text.empty()) {
|
||||||
|
const char* const tokenStart = text.data();
|
||||||
|
|
||||||
|
if (text.front() == '"') {
|
||||||
|
text.remove_prefix(1);
|
||||||
|
std::string quoted;
|
||||||
|
while (!text.empty()) {
|
||||||
|
if (text.front() == '"') {
|
||||||
|
text.remove_prefix(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (text.front() == '\\') {
|
||||||
|
text.remove_prefix(1);
|
||||||
|
if (text.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (text.front() == '\\') {
|
||||||
|
quoted.push_back('\\');
|
||||||
|
text.remove_prefix(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
quoted.push_back(text.front());
|
||||||
|
text.remove_prefix(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
quoted.push_back(text.front());
|
||||||
|
text.remove_prefix(1);
|
||||||
|
}
|
||||||
|
out.push_back(SourceToken{std::move(quoted), lineFor(tokenStart)});
|
||||||
|
skipFieldSeparators(text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.front() == '[') {
|
||||||
|
const std::size_t close = text.find(']', 1);
|
||||||
|
if (close != std::string_view::npos) {
|
||||||
|
out.push_back(SourceToken{
|
||||||
|
std::string(text.data(), close + 1),
|
||||||
|
lineFor(tokenStart),
|
||||||
|
});
|
||||||
|
text.remove_prefix(close + 1);
|
||||||
|
skipFieldSeparators(text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* const start = text.data();
|
||||||
|
while (!text.empty() &&
|
||||||
|
!isFieldSeparator(static_cast<unsigned char>(text.front())) &&
|
||||||
|
text.front() != '"' &&
|
||||||
|
text.front() != '[') {
|
||||||
|
text.remove_prefix(1);
|
||||||
|
}
|
||||||
|
const std::size_t len = static_cast<std::size_t>(text.data() - start);
|
||||||
|
if (len > 0) {
|
||||||
|
out.push_back(SourceToken{std::string(start, len), lineFor(tokenStart)});
|
||||||
|
}
|
||||||
|
skipFieldSeparators(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<std::string> tokenize(std::string_view text) {
|
||||||
|
std::vector<SourceToken> located;
|
||||||
|
located.reserve(8);
|
||||||
|
tokenizeInto(text, located);
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
tokens.reserve(located.size());
|
||||||
|
for (SourceToken& token : located) {
|
||||||
|
tokens.push_back(std::move(token.value));
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Dyrektywy startera (//$…)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
enum class StarterId {
|
||||||
|
Name,
|
||||||
|
Description,
|
||||||
|
Link,
|
||||||
|
ConsistDesc,
|
||||||
|
Image,
|
||||||
|
ImageTrain,
|
||||||
|
DecorSkip,
|
||||||
|
Archive,
|
||||||
|
Category,
|
||||||
|
Error,
|
||||||
|
GeoMap,
|
||||||
|
Reference,
|
||||||
|
TerrainRegen,
|
||||||
|
ConsistParams,
|
||||||
|
ExeVersion,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StarterDirective {
|
||||||
|
StarterId id{};
|
||||||
|
std::size_t line = 0;
|
||||||
|
std::string value;
|
||||||
|
bool hiddenConsist = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline constexpr std::array<std::pair<std::string_view, StarterId>, 15> kStarterTags{{
|
||||||
|
{"//$decor", StarterId::DecorSkip},
|
||||||
|
{"//$it", StarterId::ImageTrain},
|
||||||
|
{"//$n", StarterId::Name},
|
||||||
|
{"//$d", StarterId::Description},
|
||||||
|
{"//$f", StarterId::Link},
|
||||||
|
{"//$o", StarterId::ConsistDesc},
|
||||||
|
{"//$i", StarterId::Image},
|
||||||
|
{"//$a", StarterId::Archive},
|
||||||
|
{"//$l", StarterId::Category},
|
||||||
|
{"//$e", StarterId::Error},
|
||||||
|
{"//$g", StarterId::GeoMap},
|
||||||
|
{"//$r", StarterId::Reference},
|
||||||
|
{"//$t", StarterId::TerrainRegen},
|
||||||
|
{"//$w", StarterId::ConsistParams},
|
||||||
|
{"//$x", StarterId::ExeVersion},
|
||||||
|
}};
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool tagOk(const std::string_view line, const std::string_view tag) {
|
||||||
|
return line.size() == tag.size() ||
|
||||||
|
isFieldSeparator(static_cast<unsigned char>(line[tag.size()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<StarterId> matchStarter(std::string_view line) {
|
||||||
|
skipFieldSeparators(line);
|
||||||
|
for (const auto& [tag, id] : kStarterTags) {
|
||||||
|
if (line.starts_with(tag) && tagOk(line, tag)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isStarter(std::string_view line) {
|
||||||
|
return matchStarter(line).has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isLineComment(std::string_view line) {
|
||||||
|
skipFieldSeparators(line);
|
||||||
|
return line.starts_with("//") && !isStarter(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string_view stripInlineComment(std::string_view line) {
|
||||||
|
skipFieldSeparators(line);
|
||||||
|
if (line.starts_with("//$")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const std::size_t pos = line.find("//");
|
||||||
|
return pos == std::string_view::npos ? line : line.substr(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string_view starterTag(const StarterId id) {
|
||||||
|
for (const auto& [tag, sid] : detail::kStarterTags) {
|
||||||
|
if (sid == id) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "//$?";
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::array<std::string_view, 15> supportedStarterTags() {
|
||||||
|
std::array<std::string_view, 15> tags{};
|
||||||
|
for (std::size_t i = 0; i < detail::kStarterTags.size(); ++i) {
|
||||||
|
tags[i] = detail::kStarterTags[i].first;
|
||||||
|
}
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeStartersReport(
|
||||||
|
const std::filesystem::path& outPath,
|
||||||
|
const std::vector<StarterDirective>& starters) {
|
||||||
|
std::ofstream out(outPath);
|
||||||
|
if (!out) {
|
||||||
|
throw std::runtime_error("Nie mozna zapisac: " + outPath.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
out << "# dyrektywy startera (//$…)\n";
|
||||||
|
out << "# obslugiwane:\n";
|
||||||
|
for (const std::string_view tag : supportedStarterTags()) {
|
||||||
|
out << "# " << tag << '\n';
|
||||||
|
}
|
||||||
|
out << "# wykryte: " << starters.size() << "\n\n";
|
||||||
|
|
||||||
|
for (const StarterDirective& s : starters) {
|
||||||
|
out << starterTag(s.id) << " L" << (s.line + 1) << ": \"" << s.value << '"';
|
||||||
|
if (s.hiddenConsist) {
|
||||||
|
out << " [ukryty]";
|
||||||
|
}
|
||||||
|
out << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<StarterDirective> parseStarter(
|
||||||
|
std::string_view line,
|
||||||
|
const std::size_t lineNo) {
|
||||||
|
skipFieldSeparators(line);
|
||||||
|
const std::optional<StarterId> id = detail::matchStarter(line);
|
||||||
|
if (!id) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [tag, sid] : detail::kStarterTags) {
|
||||||
|
if (sid != *id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
StarterDirective d;
|
||||||
|
d.id = *id;
|
||||||
|
d.line = lineNo;
|
||||||
|
|
||||||
|
std::string_view rest = line.substr(tag.size());
|
||||||
|
skipFieldSeparators(rest);
|
||||||
|
d.value.assign(rest);
|
||||||
|
|
||||||
|
if (*id == StarterId::ConsistDesc && !d.value.empty() && d.value.front() == '-') {
|
||||||
|
d.hiddenConsist = true;
|
||||||
|
std::string_view tail = d.value;
|
||||||
|
tail.remove_prefix(1);
|
||||||
|
skipFieldSeparators(tail);
|
||||||
|
d.value.assign(tail);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Komentarze: // … oraz /* … */ (wiki)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
struct RawFile {
|
||||||
|
std::string data;
|
||||||
|
std::vector<std::string_view> lines;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline RawFile readRawFile(const std::filesystem::path& path) {
|
||||||
|
std::ifstream in(path, std::ios::binary);
|
||||||
|
if (!in) {
|
||||||
|
throw std::runtime_error("Nie mozna otworzyc: " + path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
RawFile f;
|
||||||
|
in.seekg(0, std::ios::end);
|
||||||
|
const std::streamoff fileSize = in.tellg();
|
||||||
|
in.seekg(0, std::ios::beg);
|
||||||
|
if (fileSize > 0) {
|
||||||
|
f.data.resize(static_cast<std::size_t>(fileSize));
|
||||||
|
in.read(f.data.data(), fileSize);
|
||||||
|
}
|
||||||
|
f.lines.reserve(f.data.empty() ? 0 : f.data.size() / 24 + 4);
|
||||||
|
|
||||||
|
std::size_t start = 0;
|
||||||
|
for (std::size_t i = 0; i <= f.data.size(); ++i) {
|
||||||
|
if (i == f.data.size() || f.data[i] == '\n') {
|
||||||
|
std::size_t end = i;
|
||||||
|
if (end > start && f.data[end - 1] == '\r') {
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
f.lines.emplace_back(f.data.data() + start, end - start);
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogicalSegment {
|
||||||
|
std::size_t sourceLine = 0;
|
||||||
|
std::string_view text;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LogicalPass {
|
||||||
|
std::vector<StarterDirective> starters;
|
||||||
|
std::vector<LogicalSegment> segments;
|
||||||
|
std::vector<std::string> spill;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline LogicalPass toLogicalLines(const std::vector<std::string_view>& raw) {
|
||||||
|
LogicalPass out;
|
||||||
|
out.segments.reserve(raw.size());
|
||||||
|
out.starters.reserve(8);
|
||||||
|
out.spill.reserve(4);
|
||||||
|
bool inBlock = false;
|
||||||
|
|
||||||
|
const auto emit = [&](std::string_view piece, const std::size_t lineNo) {
|
||||||
|
if (piece.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
skipFieldSeparators(piece);
|
||||||
|
|
||||||
|
if (detail::isStarter(piece)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (detail::isLineComment(piece)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string_view code = detail::stripInlineComment(piece);
|
||||||
|
if (code.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalSegment segment;
|
||||||
|
segment.sourceLine = lineNo;
|
||||||
|
if (code.data() >= piece.data() && code.data() + code.size() <= piece.data() + piece.size()) {
|
||||||
|
segment.text = code;
|
||||||
|
} else {
|
||||||
|
out.spill.emplace_back(code);
|
||||||
|
segment.text = out.spill.back();
|
||||||
|
}
|
||||||
|
out.segments.push_back(segment);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (std::size_t n = 0; n < raw.size(); ++n) {
|
||||||
|
std::string_view line = raw[n];
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string_view whole = line;
|
||||||
|
skipFieldSeparators(whole);
|
||||||
|
if (const std::optional<StarterDirective> starter = parseStarter(whole, n)) {
|
||||||
|
out.starters.push_back(*starter);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inBlock) {
|
||||||
|
std::size_t at = 0;
|
||||||
|
while (at < line.size()) {
|
||||||
|
const std::size_t slash = line.find('/', at);
|
||||||
|
if (slash == std::string_view::npos) {
|
||||||
|
emit(line.substr(at), n);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (slash + 1 < line.size() && line[slash + 1] == '/') {
|
||||||
|
if (slash > at) {
|
||||||
|
emit(line.substr(at, slash - at), n);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (slash + 1 < line.size() && line[slash + 1] == '*') {
|
||||||
|
if (slash > at) {
|
||||||
|
emit(line.substr(at, slash - at), n);
|
||||||
|
}
|
||||||
|
inBlock = true;
|
||||||
|
line = line.substr(slash + 2);
|
||||||
|
at = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Pojedynczy '/' to separator sciezki (np. 6-9-9/l33230.inc), nie komentarz.
|
||||||
|
emit(line.substr(at), n);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (inBlock && !line.empty()) {
|
||||||
|
const std::size_t close = line.find("*/");
|
||||||
|
if (close == std::string_view::npos) {
|
||||||
|
line = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
line.remove_prefix(close + 2);
|
||||||
|
inBlock = false;
|
||||||
|
}
|
||||||
|
if (!inBlock && !line.empty()) {
|
||||||
|
emit(line, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<StarterDirective> scanStarters(
|
||||||
|
const std::vector<std::string_view>& rawLines) {
|
||||||
|
return toLogicalLines(rawLines).starters;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isBracketToken(std::string_view token) {
|
||||||
|
skipFieldSeparators(token);
|
||||||
|
return token.size() >= 2 && token.front() == '[' && token.back() == ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ParseOptions {
|
||||||
|
bool tokenize = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParseResult {
|
||||||
|
std::vector<StarterDirective> starters;
|
||||||
|
std::vector<SourceToken> tokens;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<SourceToken> tokenizeSegments(
|
||||||
|
const std::vector<LogicalSegment>& segments) {
|
||||||
|
std::vector<SourceToken> tokens;
|
||||||
|
if (segments.empty()) {
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t chars = 0;
|
||||||
|
for (const LogicalSegment& segment : segments) {
|
||||||
|
chars += segment.text.size();
|
||||||
|
}
|
||||||
|
tokens.reserve(chars / 10 + 16);
|
||||||
|
|
||||||
|
for (const LogicalSegment& segment : segments) {
|
||||||
|
tokenizeInto(segment.text, tokens, segment.sourceLine);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline ParseResult parseFile(
|
||||||
|
const std::filesystem::path& path,
|
||||||
|
const ParseOptions& options = {}) {
|
||||||
|
const RawFile raw = readRawFile(path);
|
||||||
|
LogicalPass logical = toLogicalLines(raw.lines);
|
||||||
|
|
||||||
|
ParseResult result;
|
||||||
|
result.starters = std::move(logical.starters);
|
||||||
|
|
||||||
|
if (options.tokenize) {
|
||||||
|
result.tokens = tokenizeSegments(logical.segments);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline ParseResult parseText(const std::string& text) {
|
||||||
|
RawFile raw;
|
||||||
|
raw.data = text;
|
||||||
|
std::size_t start = 0;
|
||||||
|
for (std::size_t i = 0; i <= raw.data.size(); ++i) {
|
||||||
|
if (i == raw.data.size() || raw.data[i] == '\n') {
|
||||||
|
std::size_t end = i;
|
||||||
|
if (end > start && raw.data[end - 1] == '\r') {
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
raw.lines.emplace_back(raw.data.data() + start, end - start);
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalPass logical = toLogicalLines(raw.lines);
|
||||||
|
|
||||||
|
ParseResult result;
|
||||||
|
result.starters = std::move(logical.starters);
|
||||||
|
result.tokens = tokenizeSegments(logical.segments);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07
|
||||||
46
eu07-parser/include/eu07/scene/area.hpp
Normal file
46
eu07-parser/include/eu07/scene/area.hpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_area
|
||||||
|
// area name tracks endarea
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/boundary.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::area {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "area")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
while (!session.empty()) {
|
||||||
|
if (session.at("endarea")) {
|
||||||
|
session.take();
|
||||||
|
ctx.document.area.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string_view value = session.peek().value;
|
||||||
|
if (detail::isTopLevelStarter(value) || detail::isEmbeddedInNode(value)) {
|
||||||
|
session.fail();
|
||||||
|
stream.rewind(anchor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
session.fail();
|
||||||
|
stream.rewind(anchor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::area
|
||||||
35
eu07-parser/include/eu07/scene/atmo.hpp
Normal file
35
eu07-parser/include/eu07/scene/atmo.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_atmo
|
||||||
|
// atmo skyColor fogRangeStart fogRangeEnd fogColor overcast endatmo
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::atmo {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "atmo")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
while (!session.empty()) {
|
||||||
|
if (session.at("endatmo")) {
|
||||||
|
session.take();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.document.atmo.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::atmo
|
||||||
807
eu07-parser/include/eu07/scene/bake/bake_tree.hpp
Normal file
807
eu07-parser/include/eu07/scene/bake/bake_tree.hpp
Normal file
@@ -0,0 +1,807 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Rekurencyjny bake: kazdy plik tekstowy → wlasny .eu7, dzieci z INCL tez.
|
||||||
|
|
||||||
|
// Znaleziony include → od razu na kolejke i wolny watek (bez osobnej fazy odkrywania).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/compose_pack_models.hpp>
|
||||||
|
#include <eu07/scene/bake/module.hpp>
|
||||||
|
|
||||||
|
#include <eu07/scene/binary/runtime_module.hpp>
|
||||||
|
|
||||||
|
#include <eu07/scene/include_resolve.hpp>
|
||||||
|
|
||||||
|
#include <eu07/scene/include_scan.hpp>
|
||||||
|
|
||||||
|
#include <eu07/scene/processor.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct BakeTreeStats {
|
||||||
|
|
||||||
|
std::size_t modulesBaked = 0;
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> writtenPaths;
|
||||||
|
|
||||||
|
bool has_pack_diagnostics = false;
|
||||||
|
PackComposeStatsSnapshot pack_compose;
|
||||||
|
binary::PackWriteStats pack_write;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enum class BakeProgressPhase : std::uint8_t {
|
||||||
|
|
||||||
|
Starting,
|
||||||
|
|
||||||
|
Start,
|
||||||
|
|
||||||
|
PackModels,
|
||||||
|
|
||||||
|
PackCompose,
|
||||||
|
|
||||||
|
PackComposeDone,
|
||||||
|
|
||||||
|
Bake,
|
||||||
|
|
||||||
|
Done,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct BakeProgress {
|
||||||
|
|
||||||
|
BakeProgressPhase phase = BakeProgressPhase::Bake;
|
||||||
|
|
||||||
|
std::size_t current = 0;
|
||||||
|
|
||||||
|
std::size_t total = 0;
|
||||||
|
|
||||||
|
std::filesystem::path path;
|
||||||
|
|
||||||
|
unsigned threads = 1;
|
||||||
|
|
||||||
|
const PackComposeStats* pack_stats = nullptr;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
using BakeProgressCallback = std::function<void(const BakeProgress&)>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct BakeTreeOptions {
|
||||||
|
|
||||||
|
unsigned maxThreads = 0;
|
||||||
|
|
||||||
|
BakeProgressCallback onProgress;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct BakeTreeContext {
|
||||||
|
|
||||||
|
std::filesystem::path sceneryRoot;
|
||||||
|
|
||||||
|
std::string currentRelativeFile;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct BakeWorkItem {
|
||||||
|
|
||||||
|
std::filesystem::path path;
|
||||||
|
|
||||||
|
std::string relativeFile;
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> includeStack;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct BakePoolState {
|
||||||
|
|
||||||
|
std::mutex queueMutex;
|
||||||
|
|
||||||
|
std::condition_variable queueCv;
|
||||||
|
|
||||||
|
std::deque<BakeWorkItem> queue;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::mutex seenMutex;
|
||||||
|
|
||||||
|
std::unordered_set<std::string> seen;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::atomic<std::size_t> inFlight{0};
|
||||||
|
|
||||||
|
std::atomic<std::size_t> completed{0};
|
||||||
|
|
||||||
|
std::atomic<bool> stop{false};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::mutex statsMutex;
|
||||||
|
|
||||||
|
std::mutex rootMutex;
|
||||||
|
|
||||||
|
std::mutex progressMutex;
|
||||||
|
|
||||||
|
std::mutex errorMutex;
|
||||||
|
|
||||||
|
std::exception_ptr firstError;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline BakeTreeContext makeBakeTreeContext(const std::filesystem::path& inputPath) {
|
||||||
|
|
||||||
|
const std::filesystem::path resolved = inputPath.lexically_normal();
|
||||||
|
|
||||||
|
BakeTreeContext context;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (const std::optional<std::filesystem::path> sceneryRoot =
|
||||||
|
|
||||||
|
eu07::scene::detail::findSceneryRootInPath(resolved)) {
|
||||||
|
|
||||||
|
context.sceneryRoot = *sceneryRoot;
|
||||||
|
|
||||||
|
context.currentRelativeFile =
|
||||||
|
|
||||||
|
eu07::scene::detail::relativeSceneryFile(context.sceneryRoot, resolved);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
context.sceneryRoot = resolved.parent_path();
|
||||||
|
|
||||||
|
context.currentRelativeFile = resolved.filename().generic_string();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return context;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string canonicalPathKey(const std::filesystem::path& path) {
|
||||||
|
|
||||||
|
return path.lexically_normal().generic_string();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline unsigned defaultPoolThreadCount() {
|
||||||
|
|
||||||
|
const unsigned hw =
|
||||||
|
|
||||||
|
std::thread::hardware_concurrency() == 0 ? 4u : std::thread::hardware_concurrency();
|
||||||
|
|
||||||
|
return std::max(1u, hw);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline unsigned resolvePoolThreadCount(const unsigned maxThreads) {
|
||||||
|
|
||||||
|
if (maxThreads == 0) {
|
||||||
|
|
||||||
|
return defaultPoolThreadCount();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::max(1u, maxThreads);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
inline void recordError(BakePoolState& pool) {
|
||||||
|
|
||||||
|
std::lock_guard lock(pool.errorMutex);
|
||||||
|
|
||||||
|
if (!pool.firstError) {
|
||||||
|
|
||||||
|
pool.firstError = std::current_exception();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.stop.store(true, std::memory_order_release);
|
||||||
|
|
||||||
|
pool.queueCv.notify_all();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
inline void enqueueWork(BakePoolState& pool, BakeWorkItem item) {
|
||||||
|
|
||||||
|
const std::string key = canonicalPathKey(item.path);
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
std::lock_guard lock(pool.seenMutex);
|
||||||
|
|
||||||
|
if (!pool.seen.insert(key).second) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
std::lock_guard lock(pool.queueMutex);
|
||||||
|
|
||||||
|
pool.queue.push_back(std::move(item));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.queueCv.notify_one();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
inline void bakeWorkItem(
|
||||||
|
|
||||||
|
const BakeWorkItem& item,
|
||||||
|
|
||||||
|
const BakeTreeContext& context,
|
||||||
|
|
||||||
|
const std::string& rootKey,
|
||||||
|
|
||||||
|
BakePoolState& pool,
|
||||||
|
|
||||||
|
BakeTreeStats* stats,
|
||||||
|
|
||||||
|
RuntimeModule& rootModuleOut,
|
||||||
|
|
||||||
|
const BakeTreeOptions& options,
|
||||||
|
|
||||||
|
const unsigned threadCount) {
|
||||||
|
|
||||||
|
if (pool.stop.load(std::memory_order_acquire)) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (eu07::scene::detail::isIncludeCycle(item.path, item.includeStack)) {
|
||||||
|
|
||||||
|
throw std::runtime_error("EU7 bake: cykliczny include: " + item.path.string());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(item.path)) {
|
||||||
|
|
||||||
|
if (item.includeStack.empty()) {
|
||||||
|
|
||||||
|
throw std::runtime_error("EU7 bake: brak pliku: " + item.path.string());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pool.inFlight.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (options.onProgress) {
|
||||||
|
std::lock_guard lock(pool.progressMutex);
|
||||||
|
options.onProgress(
|
||||||
|
BakeProgress{
|
||||||
|
BakeProgressPhase::Start,
|
||||||
|
pool.completed.load(std::memory_order_relaxed),
|
||||||
|
0,
|
||||||
|
item.path,
|
||||||
|
threadCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const ParseResult parsed = parseFile(item.path);
|
||||||
|
|
||||||
|
SceneProcessOptions shallowOpts;
|
||||||
|
|
||||||
|
shallowOpts.expandIncludes = false;
|
||||||
|
|
||||||
|
const SceneDocument document =
|
||||||
|
|
||||||
|
processScene(parsed, context.sceneryRoot, shallowOpts).document;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const bool emitPackModels = canonicalPathKey(item.path) == rootKey;
|
||||||
|
RuntimeModule module = bakeModule(document, {.skipLocalModels = emitPackModels});
|
||||||
|
|
||||||
|
const std::filesystem::path eu7Path =
|
||||||
|
|
||||||
|
item.path.parent_path() / (item.path.stem().string() + ".eu7");
|
||||||
|
|
||||||
|
const std::string key = canonicalPathKey(item.path);
|
||||||
|
|
||||||
|
binary::WriteRuntimeModuleOptions writeOpts;
|
||||||
|
writeOpts.emitPackModels = emitPackModels;
|
||||||
|
std::vector<binary::codec::ModelSectionBatch> packBatches;
|
||||||
|
if (writeOpts.emitPackModels) {
|
||||||
|
if (options.onProgress) {
|
||||||
|
std::lock_guard lock(pool.progressMutex);
|
||||||
|
options.onProgress(
|
||||||
|
BakeProgress{
|
||||||
|
BakeProgressPhase::PackModels,
|
||||||
|
pool.completed.load(std::memory_order_relaxed),
|
||||||
|
0,
|
||||||
|
item.path,
|
||||||
|
threadCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
PackComposeStats pack_stats;
|
||||||
|
PackComposeProgress pack_progress;
|
||||||
|
if (options.onProgress) {
|
||||||
|
pack_progress.on_progress =
|
||||||
|
[&](const std::filesystem::path& file,
|
||||||
|
const std::size_t files_visited,
|
||||||
|
const std::size_t models_total) {
|
||||||
|
std::lock_guard lock(pool.progressMutex);
|
||||||
|
BakeProgress progress{
|
||||||
|
BakeProgressPhase::PackCompose,
|
||||||
|
files_visited,
|
||||||
|
models_total,
|
||||||
|
file,
|
||||||
|
threadCount,
|
||||||
|
};
|
||||||
|
progress.pack_stats = &pack_stats;
|
||||||
|
options.onProgress(progress);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PackComposeOptions pack_options;
|
||||||
|
pack_options.progress = &pack_progress;
|
||||||
|
pack_options.stats = &pack_stats;
|
||||||
|
pack_options.max_threads = threadCount;
|
||||||
|
pack_options.pack_batches = &packBatches;
|
||||||
|
PackComposeSeed root_seed;
|
||||||
|
root_seed.path = item.path.lexically_normal();
|
||||||
|
root_seed.entry = makePackFileCacheEntry(document);
|
||||||
|
pack_options.root_seed = &root_seed;
|
||||||
|
if (options.onProgress) {
|
||||||
|
std::cerr << "[EU7] PACK compose: " << threadCount << " watkow\n" << std::flush;
|
||||||
|
}
|
||||||
|
const std::size_t pack_model_count = composePackModels(
|
||||||
|
item.path, context.sceneryRoot, item.relativeFile, pack_options);
|
||||||
|
writeOpts.pack_batches = &packBatches;
|
||||||
|
if (options.onProgress) {
|
||||||
|
std::lock_guard lock(pool.progressMutex);
|
||||||
|
options.onProgress(
|
||||||
|
BakeProgress{
|
||||||
|
BakeProgressPhase::PackComposeDone,
|
||||||
|
pack_progress.files_visited,
|
||||||
|
pack_model_count,
|
||||||
|
item.path,
|
||||||
|
threadCount,
|
||||||
|
});
|
||||||
|
std::cerr << "[EU7] PACK gotowe: modele=" << pack_model_count
|
||||||
|
<< " sekcji=" << packBatches.size() << '\n'
|
||||||
|
<< std::flush;
|
||||||
|
}
|
||||||
|
printPackComposeStats(pack_stats, std::cerr);
|
||||||
|
if (stats != nullptr) {
|
||||||
|
stats->has_pack_diagnostics = true;
|
||||||
|
stats->pack_compose = snapshotPackComposeStats(pack_stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binary::PackWriteStats pack_write_stats;
|
||||||
|
if (writeOpts.emitPackModels) {
|
||||||
|
writeOpts.pack_write_stats = &pack_write_stats;
|
||||||
|
std::cerr << "[EU7] zapis .eu7 (PACK)...\n" << std::flush;
|
||||||
|
}
|
||||||
|
binary::writeRuntimeModule(eu7Path, module, writeOpts);
|
||||||
|
if (writeOpts.emitPackModels) {
|
||||||
|
printPackWriteStats(pack_write_stats, std::cerr);
|
||||||
|
if (stats != nullptr) {
|
||||||
|
stats->pack_write = pack_write_stats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const std::size_t done = pool.completed.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (stats != nullptr) {
|
||||||
|
|
||||||
|
std::lock_guard lock(pool.statsMutex);
|
||||||
|
|
||||||
|
++stats->modulesBaked;
|
||||||
|
|
||||||
|
stats->writtenPaths.push_back(eu7Path);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (key == rootKey) {
|
||||||
|
|
||||||
|
std::lock_guard lock(pool.rootMutex);
|
||||||
|
|
||||||
|
rootModuleOut = std::move(module);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (options.onProgress) {
|
||||||
|
|
||||||
|
std::lock_guard lock(pool.progressMutex);
|
||||||
|
|
||||||
|
options.onProgress(
|
||||||
|
|
||||||
|
BakeProgress{
|
||||||
|
|
||||||
|
BakeProgressPhase::Bake,
|
||||||
|
|
||||||
|
done,
|
||||||
|
|
||||||
|
0,
|
||||||
|
|
||||||
|
item.path,
|
||||||
|
|
||||||
|
threadCount,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BakeWorkItem child;
|
||||||
|
|
||||||
|
child.includeStack = item.includeStack;
|
||||||
|
|
||||||
|
child.includeStack.push_back(item.path);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (const std::string& includeFile : scanIncludeFilePaths(parsed.tokens)) {
|
||||||
|
|
||||||
|
child.path = eu07::scene::detail::resolveIncludeSourcePath(
|
||||||
|
|
||||||
|
context.sceneryRoot, item.relativeFile, includeFile);
|
||||||
|
|
||||||
|
child.relativeFile = eu07::scene::detail::relativeSceneryFile(
|
||||||
|
|
||||||
|
context.sceneryRoot, child.path);
|
||||||
|
|
||||||
|
enqueueWork(pool, child);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
recordError(pool);
|
||||||
|
|
||||||
|
pool.inFlight.fetch_sub(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
pool.queueCv.notify_all();
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pool.inFlight.fetch_sub(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
pool.queueCv.notify_all();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
inline void bakePoolWorker(
|
||||||
|
|
||||||
|
BakePoolState& pool,
|
||||||
|
|
||||||
|
const BakeTreeContext& context,
|
||||||
|
|
||||||
|
const std::string& rootKey,
|
||||||
|
|
||||||
|
BakeTreeStats* stats,
|
||||||
|
|
||||||
|
RuntimeModule& rootModuleOut,
|
||||||
|
|
||||||
|
const BakeTreeOptions& options,
|
||||||
|
|
||||||
|
const unsigned threadCount) {
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
BakeWorkItem item;
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_lock lock(pool.queueMutex);
|
||||||
|
|
||||||
|
pool.queueCv.wait(lock, [&]() {
|
||||||
|
|
||||||
|
return pool.stop.load(std::memory_order_acquire) || !pool.queue.empty() ||
|
||||||
|
|
||||||
|
pool.inFlight.load(std::memory_order_relaxed) == 0;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (pool.stop.load(std::memory_order_acquire)) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (pool.queue.empty()) {
|
||||||
|
|
||||||
|
if (pool.inFlight.load(std::memory_order_relaxed) == 0) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
item = std::move(pool.queue.front());
|
||||||
|
|
||||||
|
pool.queue.pop_front();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
bakeWorkItem(
|
||||||
|
|
||||||
|
item, context, rootKey, pool, stats, rootModuleOut, options, threadCount);
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
recordError(pool);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (pool.stop.load(std::memory_order_acquire)) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
inline void runBakePool(
|
||||||
|
|
||||||
|
const std::filesystem::path& rootSourcePath,
|
||||||
|
|
||||||
|
const BakeTreeContext& context,
|
||||||
|
|
||||||
|
BakeTreeStats* stats,
|
||||||
|
|
||||||
|
RuntimeModule& rootModuleOut,
|
||||||
|
|
||||||
|
const BakeTreeOptions& options) {
|
||||||
|
|
||||||
|
const std::string rootKey = canonicalPathKey(rootSourcePath.lexically_normal());
|
||||||
|
|
||||||
|
const unsigned threadCount = resolvePoolThreadCount(options.maxThreads);
|
||||||
|
|
||||||
|
if (options.onProgress) {
|
||||||
|
options.onProgress(
|
||||||
|
BakeProgress{
|
||||||
|
BakeProgressPhase::Starting,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
rootSourcePath,
|
||||||
|
threadCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BakePoolState pool;
|
||||||
|
|
||||||
|
enqueueWork(
|
||||||
|
|
||||||
|
pool,
|
||||||
|
|
||||||
|
BakeWorkItem{
|
||||||
|
|
||||||
|
rootSourcePath.lexically_normal(),
|
||||||
|
|
||||||
|
context.currentRelativeFile,
|
||||||
|
|
||||||
|
{},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::thread> workers;
|
||||||
|
|
||||||
|
workers.reserve(threadCount);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < threadCount; ++i) {
|
||||||
|
|
||||||
|
workers.emplace_back(
|
||||||
|
|
||||||
|
bakePoolWorker,
|
||||||
|
|
||||||
|
std::ref(pool),
|
||||||
|
|
||||||
|
std::cref(context),
|
||||||
|
|
||||||
|
std::cref(rootKey),
|
||||||
|
|
||||||
|
stats,
|
||||||
|
|
||||||
|
std::ref(rootModuleOut),
|
||||||
|
|
||||||
|
std::cref(options),
|
||||||
|
|
||||||
|
threadCount);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (std::thread& worker : workers) {
|
||||||
|
|
||||||
|
worker.join();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (pool.firstError) {
|
||||||
|
|
||||||
|
std::rethrow_exception(pool.firstError);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (options.onProgress) {
|
||||||
|
|
||||||
|
const std::size_t done = pool.completed.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
options.onProgress(
|
||||||
|
|
||||||
|
BakeProgress{
|
||||||
|
|
||||||
|
BakeProgressPhase::Done,
|
||||||
|
|
||||||
|
done,
|
||||||
|
|
||||||
|
done,
|
||||||
|
|
||||||
|
rootSourcePath,
|
||||||
|
|
||||||
|
threadCount,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline RuntimeModule bakeModuleTree(
|
||||||
|
|
||||||
|
const std::filesystem::path& inputPath,
|
||||||
|
|
||||||
|
BakeTreeStats* stats = nullptr,
|
||||||
|
|
||||||
|
const BakeTreeOptions& options = {}) {
|
||||||
|
|
||||||
|
const BakeTreeContext context = detail::makeBakeTreeContext(inputPath);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RuntimeModule rootModule;
|
||||||
|
|
||||||
|
detail::runBakePool(inputPath, context, stats, rootModule, options);
|
||||||
|
|
||||||
|
return rootModule;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
|
|
||||||
|
|
||||||
1222
eu07-parser/include/eu07/scene/bake/compose_pack_models.hpp
Normal file
1222
eu07-parser/include/eu07/scene/bake/compose_pack_models.hpp
Normal file
File diff suppressed because it is too large
Load Diff
33
eu07-parser/include/eu07/scene/bake/dynamic.hpp
Normal file
33
eu07-parser/include/eu07/scene/bake/dynamic.hpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/dynamic.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeDynamicObject bakeDynamic(const ParsedNodeDynamic& parsed) {
|
||||||
|
runtime::RuntimeDynamicObject vehicle;
|
||||||
|
vehicle.node = bakeBasicNode(parsed.header, "dynamic");
|
||||||
|
vehicle.dataFolder = parsed.datafolder;
|
||||||
|
vehicle.skinFile = parsed.skinfile;
|
||||||
|
vehicle.mmdFile = parsed.mmdfile;
|
||||||
|
vehicle.trackName = parsed.pathname;
|
||||||
|
vehicle.offset = parsed.vehicleOffset;
|
||||||
|
vehicle.driverType = parsed.driverType;
|
||||||
|
vehicle.coupling = parsed.coupling.type;
|
||||||
|
vehicle.couplingRaw = parsed.coupling.raw;
|
||||||
|
vehicle.couplingParams = parsed.coupling.params;
|
||||||
|
vehicle.velocity = static_cast<float>(parsed.velocity);
|
||||||
|
vehicle.loadCount = parsed.loadcount;
|
||||||
|
if (parsed.loadtype) {
|
||||||
|
vehicle.loadType = *parsed.loadtype;
|
||||||
|
}
|
||||||
|
vehicle.destination = parsed.destination;
|
||||||
|
if (parsed.header.trainsetIndex) {
|
||||||
|
vehicle.trainsetIndex = *parsed.header.trainsetIndex;
|
||||||
|
}
|
||||||
|
return vehicle;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
164
eu07-parser/include/eu07/scene/bake/event.hpp
Normal file
164
eu07-parser/include/eu07/scene/bake/event.hpp
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/runtime/directives.hpp>
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::EventType parseEventType(std::string_view type) {
|
||||||
|
if (isKeywordIgnoreCase(type, "addvalues")) {
|
||||||
|
return runtime::EventType::AddValues;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "updatevalues")) {
|
||||||
|
return runtime::EventType::UpdateValues;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "copyvalues")) {
|
||||||
|
return runtime::EventType::CopyValues;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "getvalues")) {
|
||||||
|
return runtime::EventType::GetValues;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "putvalues")) {
|
||||||
|
return runtime::EventType::PutValues;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "whois")) {
|
||||||
|
return runtime::EventType::Whois;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "logvalues")) {
|
||||||
|
return runtime::EventType::LogValues;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "multiple")) {
|
||||||
|
return runtime::EventType::Multiple;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "switch")) {
|
||||||
|
return runtime::EventType::Switch;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "trackvel")) {
|
||||||
|
return runtime::EventType::TrackVel;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "sound")) {
|
||||||
|
return runtime::EventType::Sound;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "texture")) {
|
||||||
|
return runtime::EventType::Texture;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "animation")) {
|
||||||
|
return runtime::EventType::Animation;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "lights")) {
|
||||||
|
return runtime::EventType::Lights;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "voltage")) {
|
||||||
|
return runtime::EventType::Voltage;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "visible")) {
|
||||||
|
return runtime::EventType::Visible;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "friction")) {
|
||||||
|
return runtime::EventType::Friction;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(type, "message")) {
|
||||||
|
return runtime::EventType::Message;
|
||||||
|
}
|
||||||
|
return runtime::EventType::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline void splitTargetList(const std::string& text, std::vector<std::string>& out) {
|
||||||
|
std::string current;
|
||||||
|
for (const char ch : text) {
|
||||||
|
if (ch == '|' || ch == ',') {
|
||||||
|
if (!current.empty() && !isKeywordIgnoreCase(current, "none")) {
|
||||||
|
out.push_back(current);
|
||||||
|
}
|
||||||
|
current.clear();
|
||||||
|
} else if (!std::isspace(static_cast<unsigned char>(ch))) {
|
||||||
|
current.push_back(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!current.empty() && !isKeywordIgnoreCase(current, "none")) {
|
||||||
|
out.push_back(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeEvent bakeEvent(const DirectiveBlock& block) {
|
||||||
|
runtime::RuntimeEvent event;
|
||||||
|
TokenStream stream(block.tokens);
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "event")) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
stream.consume();
|
||||||
|
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
std::string name;
|
||||||
|
std::string type;
|
||||||
|
double delay = 0.0;
|
||||||
|
if (!node::io::takeString(stream, raw, name) || !node::io::takeString(stream, raw, type) ||
|
||||||
|
!node::io::takeDouble(stream, raw, delay)) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.name = std::move(name);
|
||||||
|
event.type = parseEventType(type);
|
||||||
|
event.delay = delay;
|
||||||
|
|
||||||
|
if (event.name.starts_with("none_")) {
|
||||||
|
event.ignored = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream.empty() && !isKeyword(stream.peek().value, "endevent")) {
|
||||||
|
std::string targets;
|
||||||
|
if (node::io::takeString(stream, raw, targets)) {
|
||||||
|
detail::splitTargetList(targets, event.targets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stream.empty() && !isKeyword(stream.peek().value, "endevent")) {
|
||||||
|
std::string token;
|
||||||
|
if (!node::io::takeString(stream, raw, token)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(token, "randomdelay")) {
|
||||||
|
double value = 0.0;
|
||||||
|
if (node::io::takeDouble(stream, raw, value)) {
|
||||||
|
event.delayRandom = value;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(token, "departuredelay")) {
|
||||||
|
double value = 0.0;
|
||||||
|
if (node::io::takeDouble(stream, raw, value)) {
|
||||||
|
event.delayDeparture = value;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(token, "passive")) {
|
||||||
|
event.passive = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
event.payload.emplace_back(std::move(token), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<runtime::RuntimeEvent> bakeEvents(const SceneDocument& document) {
|
||||||
|
std::vector<runtime::RuntimeEvent> events;
|
||||||
|
events.reserve(document.event.size());
|
||||||
|
for (const DirectiveBlock& block : document.event) {
|
||||||
|
events.push_back(bakeEvent(block));
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
59
eu07-parser/include/eu07/scene/bake/eventlauncher.hpp
Normal file
59
eu07-parser/include/eu07/scene/bake/eventlauncher.hpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/geometry.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/eventlauncher.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline double parseLauncherTime(const std::string& token) {
|
||||||
|
try {
|
||||||
|
return std::stod(token);
|
||||||
|
} catch (...) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeEventLauncher bakeEventlauncher(const ParsedNodeEventlauncher& parsed) {
|
||||||
|
runtime::RuntimeEventLauncher launcher;
|
||||||
|
launcher.node = bakeBasicNode(parsed.header, "eventlauncher");
|
||||||
|
|
||||||
|
const runtime::RuntimeScratchpad scratch = scratchFromHeader(parsed.header);
|
||||||
|
launcher.location = runtime::transformPoint(parsed.position, scratch);
|
||||||
|
launcher.node.area.center = launcher.location;
|
||||||
|
|
||||||
|
if (parsed.radius > 0.0) {
|
||||||
|
launcher.radiusSquared = parsed.radius * parsed.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
launcher.activationKeyRaw = parsed.key;
|
||||||
|
launcher.event1Name = parsed.event1;
|
||||||
|
launcher.event2Name = parsed.event2.value_or("none");
|
||||||
|
|
||||||
|
double delta = parseLauncherTime(parsed.time);
|
||||||
|
if (delta > 0.0) {
|
||||||
|
launcher.launchMinute = static_cast<int>(delta) % 100;
|
||||||
|
launcher.launchHour = static_cast<int>(delta - launcher.launchMinute) / 100;
|
||||||
|
launcher.deltaTime = 0.0;
|
||||||
|
} else {
|
||||||
|
launcher.deltaTime = delta < 0.0 ? -delta : delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.condition) {
|
||||||
|
runtime::EventLauncherCondition cond;
|
||||||
|
cond.memcellName = parsed.condition->memcell;
|
||||||
|
cond.compareText = parsed.condition->parameter;
|
||||||
|
cond.compareValue1 = parsed.condition->value1.value_or(0.0);
|
||||||
|
cond.compareValue2 = parsed.condition->value2.value_or(0.0);
|
||||||
|
cond.checkMask = parsed.condition->checkMask;
|
||||||
|
launcher.condition = cond;
|
||||||
|
}
|
||||||
|
|
||||||
|
launcher.trainTriggered = parsed.trainTriggered;
|
||||||
|
return launcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
188
eu07-parser/include/eu07/scene/bake/geometry.hpp
Normal file
188
eu07-parser/include/eu07/scene/bake/geometry.hpp
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
#include <eu07/scene/runtime/transform.hpp>
|
||||||
|
#include <eu07/scene/runtime/types.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeScratchpad scratchFromHeader(const NodeHeader& header) {
|
||||||
|
runtime::RuntimeScratchpad scratch;
|
||||||
|
scratch.location.offsetStack.push_back(header.originOffset);
|
||||||
|
scratch.location.rotation = header.rotation;
|
||||||
|
scratch.location.scaleStack.push_back(header.scaleFactor);
|
||||||
|
return scratch;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::WorldVertex meshVertexToWorld(const MeshVertex& v) {
|
||||||
|
runtime::WorldVertex out;
|
||||||
|
out.position = {v.x, v.y, v.z};
|
||||||
|
out.normal = {v.nx, v.ny, v.nz};
|
||||||
|
out.u = v.u;
|
||||||
|
out.v = v.v;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool degenerateTriangle(
|
||||||
|
const runtime::Vec3& a,
|
||||||
|
const runtime::Vec3& b,
|
||||||
|
const runtime::Vec3& c) {
|
||||||
|
const runtime::Vec3 ab{b.x - a.x, b.y - a.y, b.z - a.z};
|
||||||
|
const runtime::Vec3 ac{c.x - a.x, c.y - a.y, c.z - a.z};
|
||||||
|
const runtime::Vec3 cross{
|
||||||
|
ab.y * ac.z - ab.z * ac.y,
|
||||||
|
ab.z * ac.x - ab.x * ac.z,
|
||||||
|
ab.x * ac.y - ab.y * ac.x};
|
||||||
|
const double len2 = cross.x * cross.x + cross.y * cross.y + cross.z * cross.z;
|
||||||
|
return len2 < 1e-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void pushTriangle(
|
||||||
|
std::vector<runtime::WorldVertex>& out,
|
||||||
|
const runtime::WorldVertex& v0,
|
||||||
|
const runtime::WorldVertex& v1,
|
||||||
|
const runtime::WorldVertex& v2) {
|
||||||
|
if (degenerateTriangle(v0.position, v1.position, v2.position)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out.push_back(v0);
|
||||||
|
out.push_back(v1);
|
||||||
|
out.push_back(v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<runtime::WorldVertex> triangulateTriangles(
|
||||||
|
const std::vector<MeshVertex>& source) {
|
||||||
|
std::vector<runtime::WorldVertex> out;
|
||||||
|
out.reserve(source.size());
|
||||||
|
for (std::size_t i = 0; i + 2 < source.size(); i += 3) {
|
||||||
|
pushTriangle(
|
||||||
|
out,
|
||||||
|
meshVertexToWorld(source[i]),
|
||||||
|
meshVertexToWorld(source[i + 1]),
|
||||||
|
meshVertexToWorld(source[i + 2]));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<runtime::WorldVertex> triangulateTriangleFan(
|
||||||
|
const std::vector<MeshVertex>& source) {
|
||||||
|
std::vector<runtime::WorldVertex> out;
|
||||||
|
if (source.size() < 3) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
const runtime::WorldVertex v0 = meshVertexToWorld(source[0]);
|
||||||
|
runtime::WorldVertex v1 = meshVertexToWorld(source[1]);
|
||||||
|
for (std::size_t i = 2; i < source.size(); ++i) {
|
||||||
|
const runtime::WorldVertex v2 = meshVertexToWorld(source[i]);
|
||||||
|
pushTriangle(out, v0, v1, v2);
|
||||||
|
v1 = v2;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<runtime::WorldVertex> triangulateTriangleStrip(
|
||||||
|
const std::vector<MeshVertex>& source) {
|
||||||
|
std::vector<runtime::WorldVertex> out;
|
||||||
|
if (source.size() < 3) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
runtime::WorldVertex v0 = meshVertexToWorld(source[0]);
|
||||||
|
runtime::WorldVertex v1 = meshVertexToWorld(source[1]);
|
||||||
|
for (std::size_t i = 2; i < source.size(); ++i) {
|
||||||
|
const runtime::WorldVertex v2 = meshVertexToWorld(source[i]);
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
pushTriangle(out, v0, v1, v2);
|
||||||
|
} else {
|
||||||
|
pushTriangle(out, v1, v0, v2);
|
||||||
|
}
|
||||||
|
v0 = v1;
|
||||||
|
v1 = v2;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void computeBounds(runtime::RuntimeShapeNode& shape) {
|
||||||
|
if (shape.vertices.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runtime::Vec3 center{};
|
||||||
|
for (const runtime::WorldVertex& v : shape.vertices) {
|
||||||
|
center.x += v.position.x;
|
||||||
|
center.y += v.position.y;
|
||||||
|
center.z += v.position.z;
|
||||||
|
}
|
||||||
|
const double inv = 1.0 / static_cast<double>(shape.vertices.size());
|
||||||
|
center.x *= inv;
|
||||||
|
center.y *= inv;
|
||||||
|
center.z *= inv;
|
||||||
|
shape.node.area.center = center;
|
||||||
|
|
||||||
|
double radiusSq = 0.0;
|
||||||
|
for (const runtime::WorldVertex& v : shape.vertices) {
|
||||||
|
const double dx = v.position.x - center.x;
|
||||||
|
const double dy = v.position.y - center.y;
|
||||||
|
const double dz = v.position.z - center.z;
|
||||||
|
radiusSq = std::max(radiusSq, dx * dx + dy * dy + dz * dz);
|
||||||
|
}
|
||||||
|
shape.node.area.radius = static_cast<float>(std::sqrt(radiusSq));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void computeLineBounds(runtime::RuntimeLinesNode& lines) {
|
||||||
|
if (lines.vertices.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runtime::Vec3 center{};
|
||||||
|
for (const runtime::WorldVertex& v : lines.vertices) {
|
||||||
|
center.x += v.position.x;
|
||||||
|
center.y += v.position.y;
|
||||||
|
center.z += v.position.z;
|
||||||
|
}
|
||||||
|
const double inv = 1.0 / static_cast<double>(lines.vertices.size());
|
||||||
|
center.x *= inv;
|
||||||
|
center.y *= inv;
|
||||||
|
center.z *= inv;
|
||||||
|
lines.node.area.center = center;
|
||||||
|
|
||||||
|
double radiusSq = 0.0;
|
||||||
|
for (const runtime::WorldVertex& v : lines.vertices) {
|
||||||
|
const double dx = v.position.x - center.x;
|
||||||
|
const double dy = v.position.y - center.y;
|
||||||
|
const double dz = v.position.z - center.z;
|
||||||
|
radiusSq = std::max(radiusSq, dx * dx + dy * dy + dz * dz);
|
||||||
|
}
|
||||||
|
lines.node.area.radius = static_cast<float>(std::sqrt(radiusSq));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::LightingData bakeLighting(const NodeMaterial& material) {
|
||||||
|
runtime::LightingData lit;
|
||||||
|
lit.ambient = {
|
||||||
|
static_cast<float>(material.ambient.r / 255.0),
|
||||||
|
static_cast<float>(material.ambient.g / 255.0),
|
||||||
|
static_cast<float>(material.ambient.b / 255.0),
|
||||||
|
1.f};
|
||||||
|
lit.diffuse = {
|
||||||
|
static_cast<float>(material.diffuse.r / 255.0),
|
||||||
|
static_cast<float>(material.diffuse.g / 255.0),
|
||||||
|
static_cast<float>(material.diffuse.b / 255.0),
|
||||||
|
1.f};
|
||||||
|
lit.specular = {
|
||||||
|
static_cast<float>(material.specular.r / 255.0),
|
||||||
|
static_cast<float>(material.specular.g / 255.0),
|
||||||
|
static_cast<float>(material.specular.b / 255.0),
|
||||||
|
1.f};
|
||||||
|
return lit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::LightingData defaultShapeLighting() {
|
||||||
|
return runtime::LightingData{};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool guessTranslucent(const std::string& texturePath) {
|
||||||
|
return texturePath.find('@') != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
82
eu07-parser/include/eu07/scene/bake/lines.hpp
Normal file
82
eu07-parser/include/eu07/scene/bake/lines.hpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/geometry.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/line_loop.hpp>
|
||||||
|
#include <eu07/scene/node/line_strip.hpp>
|
||||||
|
#include <eu07/scene/node/lines.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeLinesNode bakeLinesNode(
|
||||||
|
const NodeHeader& header,
|
||||||
|
const std::string& nodeType,
|
||||||
|
const MaterialRgb& color,
|
||||||
|
const double thickness,
|
||||||
|
std::vector<runtime::WorldVertex> vertices) {
|
||||||
|
runtime::RuntimeLinesNode lines;
|
||||||
|
lines.node = bakeBasicNode(header, nodeType);
|
||||||
|
lines.lineWidth = static_cast<float>(std::min(30.0, thickness));
|
||||||
|
lines.lighting.diffuse = {
|
||||||
|
static_cast<float>(color.r / 255.0),
|
||||||
|
static_cast<float>(color.g / 255.0),
|
||||||
|
static_cast<float>(color.b / 255.0),
|
||||||
|
1.f};
|
||||||
|
lines.origin = {};
|
||||||
|
|
||||||
|
runtime::RuntimeScratchpad scratch;
|
||||||
|
scratch.location.offsetStack.push_back(header.originOffset);
|
||||||
|
scratch.location.rotation = header.rotation;
|
||||||
|
runtime::transformShapeVertices(vertices, scratch);
|
||||||
|
|
||||||
|
lines.vertices = std::move(vertices);
|
||||||
|
computeLineBounds(lines);
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::WorldVertex pointVertex(const Vec3& p) {
|
||||||
|
runtime::WorldVertex v;
|
||||||
|
v.position = p;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeLinesNode bakeLines(const ParsedNodeLines& parsed) {
|
||||||
|
std::vector<runtime::WorldVertex> vertices;
|
||||||
|
vertices.reserve(parsed.segments.size() * 2);
|
||||||
|
for (const LineSegment& seg : parsed.segments) {
|
||||||
|
vertices.push_back(pointVertex(seg.a));
|
||||||
|
vertices.push_back(pointVertex(seg.b));
|
||||||
|
}
|
||||||
|
return bakeLinesNode(parsed.header, "lines", parsed.color, parsed.thickness, std::move(vertices));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeLinesNode bakeLineStrip(const ParsedNodeLineStrip& parsed) {
|
||||||
|
std::vector<runtime::WorldVertex> vertices;
|
||||||
|
if (parsed.points.size() > 1) {
|
||||||
|
vertices.reserve((parsed.points.size() - 1) * 2);
|
||||||
|
for (std::size_t i = 1; i < parsed.points.size(); ++i) {
|
||||||
|
vertices.push_back(pointVertex(parsed.points[i - 1]));
|
||||||
|
vertices.push_back(pointVertex(parsed.points[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bakeLinesNode(parsed.header, "line_strip", parsed.color, parsed.thickness, std::move(vertices));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeLinesNode bakeLineLoop(const ParsedNodeLineLoop& parsed) {
|
||||||
|
std::vector<runtime::WorldVertex> vertices;
|
||||||
|
if (parsed.points.size() > 1) {
|
||||||
|
vertices.reserve(parsed.points.size() * 2);
|
||||||
|
for (std::size_t i = 1; i < parsed.points.size(); ++i) {
|
||||||
|
vertices.push_back(pointVertex(parsed.points[i - 1]));
|
||||||
|
vertices.push_back(pointVertex(parsed.points[i]));
|
||||||
|
}
|
||||||
|
vertices.push_back(pointVertex(parsed.points.back()));
|
||||||
|
vertices.push_back(pointVertex(parsed.points.front()));
|
||||||
|
}
|
||||||
|
return bakeLinesNode(parsed.header, "line_loop", parsed.color, parsed.thickness, std::move(vertices));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
24
eu07-parser/include/eu07/scene/bake/memcell.hpp
Normal file
24
eu07-parser/include/eu07/scene/bake/memcell.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/geometry.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/memcell.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeMemCell bakeMemcell(const ParsedNodeMemcell& parsed) {
|
||||||
|
runtime::RuntimeMemCell cell;
|
||||||
|
cell.node = bakeBasicNode(parsed.header, "memcell");
|
||||||
|
cell.text = parsed.command;
|
||||||
|
cell.value1 = parsed.value1;
|
||||||
|
cell.value2 = parsed.value2;
|
||||||
|
cell.trackName = parsed.trackName;
|
||||||
|
|
||||||
|
const runtime::RuntimeScratchpad scratch = scratchFromHeader(parsed.header);
|
||||||
|
cell.node.area.center = runtime::transformPoint(parsed.position, scratch);
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
69
eu07-parser/include/eu07/scene/bake/mesh.hpp
Normal file
69
eu07-parser/include/eu07/scene/bake/mesh.hpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/geometry.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/triangle_fan.hpp>
|
||||||
|
#include <eu07/scene/node/triangle_strip.hpp>
|
||||||
|
#include <eu07/scene/node/triangles.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeShapeNode bakeShape(
|
||||||
|
const NodeHeader& header,
|
||||||
|
const std::string& nodeType,
|
||||||
|
const std::string& texturePath,
|
||||||
|
const std::optional<NodeMaterial>& material,
|
||||||
|
std::vector<runtime::WorldVertex> vertices) {
|
||||||
|
runtime::RuntimeShapeNode shape;
|
||||||
|
shape.node = bakeBasicNode(header, nodeType);
|
||||||
|
shape.materialPath = texturePath;
|
||||||
|
shape.translucent = guessTranslucent(texturePath);
|
||||||
|
shape.lighting = material ? bakeLighting(*material) : defaultShapeLighting();
|
||||||
|
shape.origin = {};
|
||||||
|
|
||||||
|
runtime::RuntimeScratchpad scratch;
|
||||||
|
scratch.location.offsetStack.push_back(header.originOffset);
|
||||||
|
scratch.location.rotation = header.rotation;
|
||||||
|
runtime::transformShapeVertices(vertices, scratch);
|
||||||
|
|
||||||
|
shape.vertices = std::move(vertices);
|
||||||
|
computeBounds(shape);
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeShapeNode bakeTriangles(const ParsedNodeTriangles& parsed) {
|
||||||
|
const bool hasMaterial =
|
||||||
|
parsed.material.ambient.r != 0.0 || parsed.material.ambient.g != 0.0 ||
|
||||||
|
parsed.material.ambient.b != 0.0 || parsed.material.diffuse.r != 0.0 ||
|
||||||
|
parsed.material.diffuse.g != 0.0 || parsed.material.diffuse.b != 0.0;
|
||||||
|
return bakeShape(
|
||||||
|
parsed.header,
|
||||||
|
"triangles",
|
||||||
|
parsed.texture,
|
||||||
|
hasMaterial ? std::optional<NodeMaterial>{parsed.material} : std::nullopt,
|
||||||
|
triangulateTriangles(parsed.vertices));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeShapeNode bakeTriangleStrip(const ParsedNodeTriangleStrip& parsed) {
|
||||||
|
return bakeShape(
|
||||||
|
parsed.header,
|
||||||
|
"triangle_strip",
|
||||||
|
parsed.texture,
|
||||||
|
std::nullopt,
|
||||||
|
triangulateTriangleStrip(parsed.vertices));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeShapeNode bakeTriangleFan(const ParsedNodeTriangleFan& parsed) {
|
||||||
|
return bakeShape(
|
||||||
|
parsed.header,
|
||||||
|
"triangle_fan",
|
||||||
|
parsed.texture,
|
||||||
|
std::nullopt,
|
||||||
|
triangulateTriangleFan(parsed.vertices));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
64
eu07-parser/include/eu07/scene/bake/model.hpp
Normal file
64
eu07-parser/include/eu07/scene/bake/model.hpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/geometry.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/model.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeModelInstance bakeModel(const ParsedNodeModel& parsed) {
|
||||||
|
runtime::RuntimeModelInstance model;
|
||||||
|
model.node = bakeBasicNode(parsed.header, "model");
|
||||||
|
model.isTerrain = parsed.header.rangeMin < 0.0;
|
||||||
|
|
||||||
|
const runtime::RuntimeScratchpad scratch = scratchFromHeader(parsed.header);
|
||||||
|
model.location = runtime::transformPoint(parsed.location, scratch);
|
||||||
|
|
||||||
|
model.angles = parsed.header.rotation;
|
||||||
|
model.angles.y += parsed.rotationY;
|
||||||
|
if (parsed.angles) {
|
||||||
|
model.angles.x += parsed.angles->x;
|
||||||
|
model.angles.y += parsed.angles->y;
|
||||||
|
model.angles.z += parsed.angles->z;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.scale = parsed.header.scaleFactor;
|
||||||
|
if (parsed.inlineScale) {
|
||||||
|
model.scale.x *= parsed.inlineScale->x;
|
||||||
|
model.scale.y *= parsed.inlineScale->y;
|
||||||
|
model.scale.z *= parsed.inlineScale->z;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.modelFile = parsed.modelPath;
|
||||||
|
model.textureFile = parsed.replaceableSkin;
|
||||||
|
model.transition = !parsed.noTransition;
|
||||||
|
|
||||||
|
if (parsed.lightStates) {
|
||||||
|
model.lightStates.reserve(parsed.lightStates->size());
|
||||||
|
for (double v : *parsed.lightStates) {
|
||||||
|
model.lightStates.push_back(static_cast<float>(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.lightColors) {
|
||||||
|
model.lightColors.reserve(parsed.lightColors->size());
|
||||||
|
for (const MaterialRgb& rgb : *parsed.lightColors) {
|
||||||
|
const auto pack = [](double c) {
|
||||||
|
return static_cast<std::uint32_t>(std::clamp(c, 0.0, 255.0));
|
||||||
|
};
|
||||||
|
const std::uint32_t r = pack(rgb.r);
|
||||||
|
const std::uint32_t g = pack(rgb.g);
|
||||||
|
const std::uint32_t b = pack(rgb.b);
|
||||||
|
model.lightColors.push_back((r << 16) | (g << 8) | b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.node.area.center = model.location;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
164
eu07-parser/include/eu07/scene/bake/module.hpp
Normal file
164
eu07-parser/include/eu07/scene/bake/module.hpp
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Jeden plik tekstowy (.scn/.inc) → jeden moduł runtime (RuntimeModule).
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/dynamic.hpp>
|
||||||
|
#include <eu07/scene/bake/event.hpp>
|
||||||
|
#include <eu07/scene/bake/eventlauncher.hpp>
|
||||||
|
#include <eu07/scene/bake/lines.hpp>
|
||||||
|
#include <eu07/scene/bake/memcell.hpp>
|
||||||
|
#include <eu07/scene/bake/mesh.hpp>
|
||||||
|
#include <eu07/scene/bake/model.hpp>
|
||||||
|
#include <eu07/scene/bake/sound.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/bake/traction.hpp>
|
||||||
|
#include <eu07/scene/bake/traction_power.hpp>
|
||||||
|
#include <eu07/scene/bake/trainset.hpp>
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/include_placement.hpp>
|
||||||
|
#include <eu07/scene/include_types.hpp>
|
||||||
|
#include <eu07/scene/runtime/scene.hpp>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
struct ModuleInclude {
|
||||||
|
std::uint32_t sourceLine = 0;
|
||||||
|
std::string sourcePath;
|
||||||
|
std::string binaryPath;
|
||||||
|
std::vector<std::string> parameters;
|
||||||
|
runtime::TransformContext siteTransform;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeModule {
|
||||||
|
std::vector<ModuleInclude> includes;
|
||||||
|
runtime::RuntimeScene scene;
|
||||||
|
IncludePlacement includePlacement;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BakeModuleOptions {
|
||||||
|
// Root + PACK: modele i tak pochodza z composePackModels.
|
||||||
|
bool skipLocalModels = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string textPathToBinaryPath(const std::string& textPath) {
|
||||||
|
std::filesystem::path path{textPath};
|
||||||
|
path.replace_extension(".eu7");
|
||||||
|
return path.generic_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline RuntimeModule bakeModule(
|
||||||
|
const SceneDocument& document,
|
||||||
|
const BakeModuleOptions& options = {}) {
|
||||||
|
RuntimeModule module;
|
||||||
|
|
||||||
|
module.includes.reserve(document.include.size());
|
||||||
|
for (const ParsedInclude& inc : document.include) {
|
||||||
|
ModuleInclude ref;
|
||||||
|
ref.sourceLine = static_cast<std::uint32_t>(inc.line);
|
||||||
|
ref.sourcePath = inc.file;
|
||||||
|
ref.binaryPath = textPathToBinaryPath(inc.file);
|
||||||
|
ref.parameters = inc.parameters;
|
||||||
|
ref.siteTransform = inc.siteTransform;
|
||||||
|
module.includes.push_back(std::move(ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& scene = module.scene;
|
||||||
|
|
||||||
|
scene.tracks.reserve(countTrackInstances(document));
|
||||||
|
for (const ParsedTrackNormal& parsed : document.nodeTrackNormal) {
|
||||||
|
scene.tracks.push_back(bakeTrackNormal(parsed));
|
||||||
|
}
|
||||||
|
for (const ParsedTrackSwitch& parsed : document.nodeTrackSwitch) {
|
||||||
|
scene.tracks.push_back(bakeTrackSwitch(parsed));
|
||||||
|
}
|
||||||
|
for (const ParsedTrackRoad& parsed : document.nodeTrackRoad) {
|
||||||
|
scene.tracks.push_back(bakeTrackRoad(parsed));
|
||||||
|
}
|
||||||
|
for (const ParsedTrackCross& parsed : document.nodeTrackCross) {
|
||||||
|
scene.tracks.push_back(bakeTrackCross(parsed));
|
||||||
|
}
|
||||||
|
for (const ParsedTrackOther& parsed : document.nodeTrackOther) {
|
||||||
|
scene.tracks.push_back(bakeTrackOther(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.traction.reserve(document.nodeTraction.size());
|
||||||
|
for (const ParsedNodeTraction& parsed : document.nodeTraction) {
|
||||||
|
scene.traction.push_back(bakeTraction(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.powerSources.reserve(document.nodeTractionPower.size());
|
||||||
|
for (const ParsedNodeTractionPowerSource& parsed : document.nodeTractionPower) {
|
||||||
|
scene.powerSources.push_back(bakeTractionPower(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t shapeCount =
|
||||||
|
document.nodeTriangles.size() + document.nodeTriangleStrip.size() +
|
||||||
|
document.nodeTriangleFan.size();
|
||||||
|
scene.shapes.reserve(shapeCount);
|
||||||
|
for (const ParsedNodeTriangles& parsed : document.nodeTriangles) {
|
||||||
|
scene.shapes.push_back(bakeTriangles(parsed));
|
||||||
|
}
|
||||||
|
for (const ParsedNodeTriangleStrip& parsed : document.nodeTriangleStrip) {
|
||||||
|
scene.shapes.push_back(bakeTriangleStrip(parsed));
|
||||||
|
}
|
||||||
|
for (const ParsedNodeTriangleFan& parsed : document.nodeTriangleFan) {
|
||||||
|
scene.shapes.push_back(bakeTriangleFan(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t lineCount =
|
||||||
|
document.nodeLines.size() + document.nodeLineStrip.size() + document.nodeLineLoop.size();
|
||||||
|
scene.lines.reserve(lineCount);
|
||||||
|
for (const ParsedNodeLines& parsed : document.nodeLines) {
|
||||||
|
scene.lines.push_back(bakeLines(parsed));
|
||||||
|
}
|
||||||
|
for (const ParsedNodeLineStrip& parsed : document.nodeLineStrip) {
|
||||||
|
scene.lines.push_back(bakeLineStrip(parsed));
|
||||||
|
}
|
||||||
|
for (const ParsedNodeLineLoop& parsed : document.nodeLineLoop) {
|
||||||
|
scene.lines.push_back(bakeLineLoop(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.skipLocalModels) {
|
||||||
|
scene.models.reserve(document.nodeModel.size());
|
||||||
|
for (const ParsedNodeModel& parsed : document.nodeModel) {
|
||||||
|
scene.models.push_back(bakeModel(parsed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.memcells.reserve(document.nodeMemcell.size());
|
||||||
|
for (const ParsedNodeMemcell& parsed : document.nodeMemcell) {
|
||||||
|
scene.memcells.push_back(bakeMemcell(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.eventLaunchers.reserve(document.nodeEventlauncher.size());
|
||||||
|
for (const ParsedNodeEventlauncher& parsed : document.nodeEventlauncher) {
|
||||||
|
scene.eventLaunchers.push_back(bakeEventlauncher(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.dynamics.reserve(document.nodeDynamic.size());
|
||||||
|
for (const ParsedNodeDynamic& parsed : document.nodeDynamic) {
|
||||||
|
scene.dynamics.push_back(bakeDynamic(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.sounds.reserve(document.nodeSound.size());
|
||||||
|
for (const ParsedNodeSound& parsed : document.nodeSound) {
|
||||||
|
scene.sounds.push_back(bakeSound(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.trainsets = bakeTrainsets(document, scene.dynamics);
|
||||||
|
scene.events = bakeEvents(document);
|
||||||
|
scene.firstInitCount = static_cast<std::uint32_t>(document.firstInit.size());
|
||||||
|
|
||||||
|
if (const std::optional<IncludePlacement> placement = extractIncludePlacement(document)) {
|
||||||
|
module.includePlacement = *placement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
22
eu07-parser/include/eu07/scene/bake/sound.hpp
Normal file
22
eu07-parser/include/eu07/scene/bake/sound.hpp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/geometry.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/sound.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeSoundSource bakeSound(const ParsedNodeSound& parsed) {
|
||||||
|
runtime::RuntimeSoundSource sound;
|
||||||
|
sound.node = bakeBasicNode(parsed.header, "sound");
|
||||||
|
sound.wavFile = parsed.wavFile;
|
||||||
|
|
||||||
|
const runtime::RuntimeScratchpad scratch = scratchFromHeader(parsed.header);
|
||||||
|
sound.location = runtime::transformPoint(parsed.position, scratch);
|
||||||
|
sound.node.area.center = sound.location;
|
||||||
|
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
330
eu07-parser/include/eu07/scene/bake/track.hpp
Normal file
330
eu07-parser/include/eu07/scene/bake/track.hpp
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/track.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline void appendTrackEventKeyword(
|
||||||
|
std::vector<std::pair<std::string, std::string>>& tail,
|
||||||
|
const std::string_view key,
|
||||||
|
const std::optional<std::string>& value) {
|
||||||
|
if (value && !value->empty()) {
|
||||||
|
tail.emplace_back(std::string(key), *value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void appendTrackOptionals(
|
||||||
|
std::vector<std::pair<std::string, std::string>>& tail,
|
||||||
|
const TrackOptionals& optionals) {
|
||||||
|
appendTrackEventKeyword(tail, "event0", optionals.events.occupiedStop);
|
||||||
|
appendTrackEventKeyword(tail, "eventall0", optionals.events.anyStop);
|
||||||
|
appendTrackEventKeyword(tail, "event1", optionals.events.occupiedEnterP1);
|
||||||
|
appendTrackEventKeyword(tail, "eventall1", optionals.events.anyEnterP1);
|
||||||
|
appendTrackEventKeyword(tail, "event2", optionals.events.occupiedEnterP2);
|
||||||
|
appendTrackEventKeyword(tail, "eventall2", optionals.events.anyEnterP2);
|
||||||
|
|
||||||
|
if (optionals.velocity) {
|
||||||
|
tail.emplace_back("velocity", std::format("{}", *optionals.velocity));
|
||||||
|
}
|
||||||
|
for (const std::string& isolated : optionals.isolated) {
|
||||||
|
tail.emplace_back("isolated", isolated);
|
||||||
|
}
|
||||||
|
if (optionals.overhead) {
|
||||||
|
tail.emplace_back("overhead", std::format("{}", *optionals.overhead));
|
||||||
|
}
|
||||||
|
if (optionals.verticalRadius) {
|
||||||
|
tail.emplace_back("vradius", std::format("{}", *optionals.verticalRadius));
|
||||||
|
}
|
||||||
|
if (optionals.railProfile) {
|
||||||
|
tail.emplace_back("railprofile", *optionals.railProfile);
|
||||||
|
}
|
||||||
|
if (optionals.trackBed) {
|
||||||
|
tail.emplace_back("trackbed", *optionals.trackBed);
|
||||||
|
}
|
||||||
|
if (optionals.frictionOverride) {
|
||||||
|
tail.emplace_back("friction", std::format("{}", *optionals.frictionOverride));
|
||||||
|
}
|
||||||
|
if (optionals.fouling1) {
|
||||||
|
tail.emplace_back("fouling1", *optionals.fouling1);
|
||||||
|
}
|
||||||
|
if (optionals.fouling2) {
|
||||||
|
tail.emplace_back("fouling2", *optionals.fouling2);
|
||||||
|
}
|
||||||
|
if (optionals.sleeperModel) {
|
||||||
|
const TrackSleeperModel& sleeper = *optionals.sleeperModel;
|
||||||
|
tail.emplace_back(
|
||||||
|
"sleepermodel",
|
||||||
|
std::format(
|
||||||
|
"{} {} {} {} {} {} {}",
|
||||||
|
sleeper.spacing,
|
||||||
|
sleeper.model,
|
||||||
|
sleeper.texture,
|
||||||
|
sleeper.offsetLateral,
|
||||||
|
sleeper.offsetAlong,
|
||||||
|
sleeper.offsetVertical,
|
||||||
|
sleeper.bedLowering));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [key, value] : optionals.extraKeywords) {
|
||||||
|
tail.emplace_back(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void bakeTrackTailKeywords(std::vector<std::pair<std::string, std::string>>& tail, const TrackOptionals& optionals) {
|
||||||
|
tail.clear();
|
||||||
|
appendTrackOptionals(tail, optionals);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::BasicNode bakeBasicNode(const NodeHeader& header, const std::string& nodeType) {
|
||||||
|
runtime::BasicNode node;
|
||||||
|
node.name = (header.name == "none" || header.name.empty()) ? std::string{} : header.name;
|
||||||
|
node.nodeType = nodeType;
|
||||||
|
node.rangeSquaredMin = header.rangeMin * header.rangeMin;
|
||||||
|
node.rangeSquaredMax =
|
||||||
|
header.rangeMax >= 0.0 ? header.rangeMax * header.rangeMax
|
||||||
|
: std::numeric_limits<double>::max();
|
||||||
|
node.visible = true;
|
||||||
|
if (header.groupHandle) {
|
||||||
|
node.groupHandle = *header.groupHandle;
|
||||||
|
node.groupValid = true;
|
||||||
|
}
|
||||||
|
node.transform.originStack = header.originStack;
|
||||||
|
node.transform.scaleStack = header.scaleStack;
|
||||||
|
node.transform.rotation = header.rotation;
|
||||||
|
node.transform.groupStackDepth = header.groupStackDepth;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::TrackType mapTrackType(const std::string& kind) {
|
||||||
|
if (kind == "normal") {
|
||||||
|
return runtime::TrackType::Normal;
|
||||||
|
}
|
||||||
|
if (kind == "switch") {
|
||||||
|
return runtime::TrackType::Switch;
|
||||||
|
}
|
||||||
|
if (kind == "turn" || kind == "table") {
|
||||||
|
return runtime::TrackType::Table;
|
||||||
|
}
|
||||||
|
if (kind == "cross") {
|
||||||
|
return runtime::TrackType::Cross;
|
||||||
|
}
|
||||||
|
if (kind == "tributary") {
|
||||||
|
return runtime::TrackType::Tributary;
|
||||||
|
}
|
||||||
|
return runtime::TrackType::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::TrackCategory mapTrackCategory(const std::string& kind) {
|
||||||
|
if (kind == "road" || kind == "cross") {
|
||||||
|
return runtime::TrackCategory::Road;
|
||||||
|
}
|
||||||
|
if (kind == "river" || kind == "tributary") {
|
||||||
|
return runtime::TrackCategory::Water;
|
||||||
|
}
|
||||||
|
return runtime::TrackCategory::Rail;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::TrackEnvironment mapEnvironment(const std::string& env) {
|
||||||
|
if (env == "flat") {
|
||||||
|
return runtime::TrackEnvironment::Flat;
|
||||||
|
}
|
||||||
|
if (env == "mountains" || env == "mountain") {
|
||||||
|
return runtime::TrackEnvironment::Mountains;
|
||||||
|
}
|
||||||
|
if (env == "canyon") {
|
||||||
|
return runtime::TrackEnvironment::Canyon;
|
||||||
|
}
|
||||||
|
if (env == "tunnel") {
|
||||||
|
return runtime::TrackEnvironment::Tunnel;
|
||||||
|
}
|
||||||
|
if (env == "bridge") {
|
||||||
|
return runtime::TrackEnvironment::Bridge;
|
||||||
|
}
|
||||||
|
if (env == "bank") {
|
||||||
|
return runtime::TrackEnvironment::Bank;
|
||||||
|
}
|
||||||
|
return runtime::TrackEnvironment::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::SegmentPath bakeSegment(
|
||||||
|
const TrackBezier& bez,
|
||||||
|
const Vec3& originOffset) {
|
||||||
|
runtime::SegmentPath seg;
|
||||||
|
seg.pStart = {bez.p1.x + originOffset.x, bez.p1.y + originOffset.y, bez.p1.z + originOffset.z};
|
||||||
|
seg.rollStart = bez.roll1;
|
||||||
|
seg.cpOut = bez.cv1;
|
||||||
|
seg.cpIn = bez.cv2;
|
||||||
|
seg.pEnd = {bez.p2.x + originOffset.x, bez.p2.y + originOffset.y, bez.p2.z + originOffset.z};
|
||||||
|
seg.rollEnd = bez.roll2;
|
||||||
|
seg.radius = bez.radius;
|
||||||
|
return seg;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline void appendSegments(
|
||||||
|
runtime::RuntimeTrack& track,
|
||||||
|
const std::vector<TrackBezier>& beziers,
|
||||||
|
const Vec3& originOffset) {
|
||||||
|
track.paths.reserve(track.paths.size() + beziers.size());
|
||||||
|
for (const TrackBezier& bez : beziers) {
|
||||||
|
track.paths.push_back(bakeSegment(bez, originOffset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeTrack bakeTrackNormal(const ParsedTrackNormal& parsed) {
|
||||||
|
runtime::RuntimeTrack track;
|
||||||
|
track.node = bakeBasicNode(parsed.header, "track");
|
||||||
|
track.trackType = mapTrackType(parsed.trajectoryKind);
|
||||||
|
track.category = mapTrackCategory(parsed.trajectoryKind);
|
||||||
|
track.length = static_cast<float>(parsed.length);
|
||||||
|
track.trackWidth = static_cast<float>(parsed.gauge);
|
||||||
|
track.friction = static_cast<float>(parsed.friction);
|
||||||
|
track.soundDistance = static_cast<float>(parsed.soundDist);
|
||||||
|
track.qualityFlag = parsed.quality;
|
||||||
|
track.damageFlag = parsed.damageFlag;
|
||||||
|
track.environment = mapEnvironment(parsed.environment);
|
||||||
|
if (parsed.visibilityMode == TrackVisibilityMode::Visible && parsed.visibility) {
|
||||||
|
runtime::TrackVisibility vis;
|
||||||
|
vis.material1 = parsed.visibility->railTexture;
|
||||||
|
vis.texLength = static_cast<float>(parsed.visibility->railTexLength);
|
||||||
|
vis.material2 = parsed.visibility->ballastTexture;
|
||||||
|
vis.texHeight1 = static_cast<float>(parsed.visibility->ballastHeight);
|
||||||
|
vis.texWidth = static_cast<float>(parsed.visibility->ballastWidth);
|
||||||
|
vis.texSlope = static_cast<float>(parsed.visibility->ballastSlope);
|
||||||
|
track.visibility = vis;
|
||||||
|
}
|
||||||
|
appendSegments(track, parsed.beziers, parsed.header.originOffset);
|
||||||
|
detail::bakeTrackTailKeywords(track.tailKeywords, parsed.optionals);
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeTrack bakeTrackSwitch(const ParsedTrackSwitch& parsed) {
|
||||||
|
runtime::RuntimeTrack track;
|
||||||
|
track.node = bakeBasicNode(parsed.header, "track");
|
||||||
|
track.trackType = runtime::TrackType::Switch;
|
||||||
|
track.category = runtime::TrackCategory::Rail;
|
||||||
|
track.length = static_cast<float>(parsed.length);
|
||||||
|
track.trackWidth = static_cast<float>(parsed.gauge);
|
||||||
|
track.friction = static_cast<float>(parsed.friction);
|
||||||
|
track.soundDistance = static_cast<float>(parsed.soundDist);
|
||||||
|
track.qualityFlag = parsed.quality;
|
||||||
|
track.damageFlag = parsed.damageFlag;
|
||||||
|
track.environment = mapEnvironment(parsed.environment);
|
||||||
|
if (parsed.visibilityMode == TrackVisibilityMode::Visible && parsed.visibility) {
|
||||||
|
runtime::TrackVisibility vis;
|
||||||
|
vis.material1 = parsed.visibility->mainRailTexture;
|
||||||
|
vis.texLength = static_cast<float>(parsed.visibility->mainRailTexLength);
|
||||||
|
vis.material2 = parsed.visibility->switchRailTexture;
|
||||||
|
vis.texHeight1 = static_cast<float>(parsed.visibility->unusedHeight);
|
||||||
|
vis.texWidth = static_cast<float>(parsed.visibility->unusedWidth);
|
||||||
|
vis.texSlope = static_cast<float>(parsed.visibility->unusedSlope);
|
||||||
|
track.visibility = vis;
|
||||||
|
}
|
||||||
|
appendSegments(track, parsed.beziers, parsed.header.originOffset);
|
||||||
|
detail::bakeTrackTailKeywords(track.tailKeywords, parsed.optionals);
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeTrack bakeTrackRoad(const ParsedTrackRoad& parsed) {
|
||||||
|
runtime::RuntimeTrack track;
|
||||||
|
track.node = bakeBasicNode(parsed.header, "track");
|
||||||
|
track.trackType = runtime::TrackType::Normal;
|
||||||
|
track.category = runtime::TrackCategory::Road;
|
||||||
|
track.length = static_cast<float>(parsed.length);
|
||||||
|
track.trackWidth = static_cast<float>(parsed.roadWidth);
|
||||||
|
track.friction = static_cast<float>(parsed.friction);
|
||||||
|
track.soundDistance = static_cast<float>(parsed.soundDist);
|
||||||
|
track.qualityFlag = parsed.quality;
|
||||||
|
track.damageFlag = parsed.damageFlag;
|
||||||
|
track.environment = mapEnvironment(parsed.environment);
|
||||||
|
if (parsed.visibilityMode == TrackVisibilityMode::Visible && parsed.visibility) {
|
||||||
|
runtime::TrackVisibility vis;
|
||||||
|
vis.material1 = parsed.visibility->surfaceTexture;
|
||||||
|
vis.texLength = static_cast<float>(parsed.visibility->surfaceTexLength);
|
||||||
|
vis.material2 = parsed.visibility->shoulderTexture;
|
||||||
|
vis.texHeight1 = static_cast<float>(parsed.visibility->shoulderHeight);
|
||||||
|
vis.texWidth = static_cast<float>(parsed.visibility->shoulderWidth);
|
||||||
|
vis.texSlope = static_cast<float>(parsed.visibility->shoulderSlope);
|
||||||
|
track.visibility = vis;
|
||||||
|
}
|
||||||
|
appendSegments(track, parsed.beziers, parsed.header.originOffset);
|
||||||
|
detail::bakeTrackTailKeywords(track.tailKeywords, parsed.optionals);
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeTrack bakeTrackCross(const ParsedTrackCross& parsed) {
|
||||||
|
runtime::RuntimeTrack track;
|
||||||
|
track.node = bakeBasicNode(parsed.header, "track");
|
||||||
|
track.trackType = runtime::TrackType::Cross;
|
||||||
|
track.category = runtime::TrackCategory::Road;
|
||||||
|
track.length = static_cast<float>(parsed.length);
|
||||||
|
track.trackWidth = static_cast<float>(parsed.roadWidth);
|
||||||
|
track.friction = static_cast<float>(parsed.friction);
|
||||||
|
track.soundDistance = static_cast<float>(parsed.soundDist);
|
||||||
|
track.qualityFlag = parsed.quality;
|
||||||
|
track.damageFlag = parsed.damageFlag;
|
||||||
|
track.environment = mapEnvironment(parsed.environment);
|
||||||
|
if (parsed.visibilityMode == TrackVisibilityMode::Visible && parsed.visibility) {
|
||||||
|
runtime::TrackVisibility vis;
|
||||||
|
vis.material1 = parsed.visibility->surfaceTexture;
|
||||||
|
vis.texLength = static_cast<float>(parsed.visibility->surfaceTexLength);
|
||||||
|
vis.material2 = parsed.visibility->shoulderTexture;
|
||||||
|
vis.texHeight1 = static_cast<float>(parsed.visibility->shoulderHeight);
|
||||||
|
vis.texWidth = static_cast<float>(parsed.visibility->shoulderWidth);
|
||||||
|
vis.texSlope = static_cast<float>(parsed.visibility->shoulderSlope);
|
||||||
|
track.visibility = vis;
|
||||||
|
}
|
||||||
|
appendSegments(track, parsed.beziers, parsed.header.originOffset);
|
||||||
|
detail::bakeTrackTailKeywords(track.tailKeywords, parsed.optionals);
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeTrack bakeTrackOther(const ParsedTrackOther& parsed) {
|
||||||
|
runtime::RuntimeTrack track;
|
||||||
|
track.node = bakeBasicNode(parsed.header, "track");
|
||||||
|
track.trackType = mapTrackType(parsed.trajectoryKind);
|
||||||
|
track.category = mapTrackCategory(parsed.trajectoryKind);
|
||||||
|
track.length = static_cast<float>(parsed.length);
|
||||||
|
track.trackWidth = static_cast<float>(parsed.width);
|
||||||
|
track.friction = static_cast<float>(parsed.friction);
|
||||||
|
track.soundDistance = static_cast<float>(parsed.soundDist);
|
||||||
|
track.qualityFlag = parsed.quality;
|
||||||
|
track.damageFlag = parsed.damageFlag;
|
||||||
|
track.environment = mapEnvironment(parsed.environment);
|
||||||
|
if (parsed.visibilityMode == TrackVisibilityMode::Visible) {
|
||||||
|
if (parsed.riverVisibility) {
|
||||||
|
runtime::TrackVisibility vis;
|
||||||
|
vis.material1 = parsed.riverVisibility->waterTexture;
|
||||||
|
vis.texLength = static_cast<float>(parsed.riverVisibility->waterTexLength);
|
||||||
|
vis.material2 = parsed.riverVisibility->bankTexture;
|
||||||
|
vis.texHeight1 = static_cast<float>(parsed.riverVisibility->bankHeight);
|
||||||
|
vis.texWidth = static_cast<float>(parsed.riverVisibility->leftBankWidth);
|
||||||
|
vis.texSlope = static_cast<float>(parsed.riverVisibility->rightBankWidth);
|
||||||
|
track.visibility = vis;
|
||||||
|
} else if (parsed.railVisibility) {
|
||||||
|
runtime::TrackVisibility vis;
|
||||||
|
vis.material1 = parsed.railVisibility->railTexture;
|
||||||
|
vis.texLength = static_cast<float>(parsed.railVisibility->railTexLength);
|
||||||
|
vis.material2 = parsed.railVisibility->ballastTexture;
|
||||||
|
vis.texHeight1 = static_cast<float>(parsed.railVisibility->ballastHeight);
|
||||||
|
vis.texWidth = static_cast<float>(parsed.railVisibility->ballastWidth);
|
||||||
|
vis.texSlope = static_cast<float>(parsed.railVisibility->ballastSlope);
|
||||||
|
track.visibility = vis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appendSegments(track, parsed.beziers, parsed.header.originOffset);
|
||||||
|
detail::bakeTrackTailKeywords(track.tailKeywords, parsed.optionals);
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
64
eu07-parser/include/eu07/scene/bake/traction.hpp
Normal file
64
eu07-parser/include/eu07/scene/bake/traction.hpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/geometry.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/traction.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::TractionWireMaterial mapWireMaterial(const TractionMaterialKind kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case TractionMaterialKind::None:
|
||||||
|
return runtime::TractionWireMaterial::None;
|
||||||
|
case TractionMaterialKind::Aluminum:
|
||||||
|
return runtime::TractionWireMaterial::Aluminium;
|
||||||
|
default:
|
||||||
|
return runtime::TractionWireMaterial::Copper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vec3 offsetPoint(const Vec3& point, const Vec3& origin) {
|
||||||
|
return {point.x + origin.x, point.y + origin.y, point.z + origin.z};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeTraction bakeTraction(const ParsedNodeTraction& parsed) {
|
||||||
|
runtime::RuntimeTraction traction;
|
||||||
|
traction.node = bakeBasicNode(parsed.header, "traction");
|
||||||
|
traction.powerSupplyName = parsed.powerSourceName;
|
||||||
|
traction.nominalVoltage = static_cast<float>(parsed.nominalVoltage);
|
||||||
|
traction.maxCurrent = static_cast<float>(parsed.maxCurrent);
|
||||||
|
|
||||||
|
traction.resistivityLegacy = parsed.resistivity;
|
||||||
|
double resistivity = parsed.resistivity;
|
||||||
|
if (resistivity == 0.01) {
|
||||||
|
resistivity = 0.075;
|
||||||
|
}
|
||||||
|
traction.resistivityOhmPerM = static_cast<float>(resistivity * 0.001);
|
||||||
|
|
||||||
|
traction.materialRaw = parsed.materialRaw;
|
||||||
|
traction.material = mapWireMaterial(parsed.materialKind);
|
||||||
|
traction.wireThickness = static_cast<float>(parsed.wireThickness);
|
||||||
|
traction.damageFlag = parsed.damageFlag;
|
||||||
|
|
||||||
|
const Vec3& origin = parsed.header.originOffset;
|
||||||
|
traction.wireP1 = offsetPoint(parsed.wireLowerStart, origin);
|
||||||
|
traction.wireP2 = offsetPoint(parsed.wireLowerEnd, origin);
|
||||||
|
traction.wireP3 = offsetPoint(parsed.wireUpperStart, origin);
|
||||||
|
traction.wireP4 = offsetPoint(parsed.wireUpperEnd, origin);
|
||||||
|
traction.minHeight = parsed.minHeight;
|
||||||
|
traction.segmentLength = parsed.segmentLength;
|
||||||
|
traction.wireCount = parsed.wires;
|
||||||
|
traction.wireOffset = static_cast<float>(parsed.wireOffset);
|
||||||
|
traction.parallelName = parsed.parallel;
|
||||||
|
traction.node.visible = parsed.visibilityMode == NodeVisibilityMode::Visible;
|
||||||
|
|
||||||
|
traction.node.area.center = {
|
||||||
|
(traction.wireP2.x + traction.wireP1.x) * 0.5,
|
||||||
|
(traction.wireP2.y + traction.wireP1.y) * 0.5,
|
||||||
|
(traction.wireP2.z + traction.wireP1.z) * 0.5};
|
||||||
|
|
||||||
|
return traction;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
48
eu07-parser/include/eu07/scene/bake/traction_power.hpp
Normal file
48
eu07-parser/include/eu07/scene/bake/traction_power.hpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/geometry.hpp>
|
||||||
|
#include <eu07/scene/bake/track.hpp>
|
||||||
|
#include <eu07/scene/node/traction_power.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::PowerSourceModifier mapPowerModifier(
|
||||||
|
const ParsedNodeTractionPowerSource& parsed) {
|
||||||
|
if (parsed.section) {
|
||||||
|
return runtime::PowerSourceModifier::Section;
|
||||||
|
}
|
||||||
|
if (parsed.recuperation) {
|
||||||
|
return runtime::PowerSourceModifier::Recuperation;
|
||||||
|
}
|
||||||
|
return runtime::PowerSourceModifier::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::RuntimeTractionPowerSource bakeTractionPower(
|
||||||
|
const ParsedNodeTractionPowerSource& parsed) {
|
||||||
|
runtime::RuntimeTractionPowerSource source;
|
||||||
|
source.node = bakeBasicNode(parsed.header, "tractionpowersource");
|
||||||
|
|
||||||
|
const runtime::RuntimeScratchpad scratch = scratchFromHeader(parsed.header);
|
||||||
|
source.position = runtime::transformPoint(parsed.position, scratch);
|
||||||
|
source.node.area.center = source.position;
|
||||||
|
|
||||||
|
source.nominalVoltage = static_cast<float>(parsed.nominalVoltage);
|
||||||
|
source.voltageFrequency = static_cast<float>(parsed.voltageFrequency);
|
||||||
|
source.maxOutputCurrent = static_cast<float>(parsed.maxOutputCurrent);
|
||||||
|
source.fastFuseTimeout = static_cast<float>(parsed.fastFuseTimeout);
|
||||||
|
source.fastFuseRepetition = static_cast<float>(parsed.fastFuseRepetition);
|
||||||
|
source.slowFuseTimeout = static_cast<float>(parsed.slowFuseTimeout);
|
||||||
|
source.modifier = mapPowerModifier(parsed);
|
||||||
|
|
||||||
|
source.internalResistanceLegacy = parsed.internalResistance;
|
||||||
|
float internalResistance = static_cast<float>(parsed.internalResistance);
|
||||||
|
if (internalResistance < 0.1f) {
|
||||||
|
internalResistance = 0.2f;
|
||||||
|
}
|
||||||
|
source.internalResistance = internalResistance;
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
77
eu07-parser/include/eu07/scene/bake/trainset.hpp
Normal file
77
eu07-parser/include/eu07/scene/bake/trainset.hpp
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/runtime/directives.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::bake {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseTrainsetHeader(
|
||||||
|
const DirectiveBlock& block,
|
||||||
|
runtime::RuntimeTrainset& trainset) {
|
||||||
|
TokenStream stream(block.tokens);
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "trainset")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stream.consume();
|
||||||
|
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
std::string name;
|
||||||
|
std::string track;
|
||||||
|
double offset = 0.0;
|
||||||
|
double velocity = 0.0;
|
||||||
|
if (!node::io::takeString(stream, raw, name) || !node::io::takeString(stream, raw, track) ||
|
||||||
|
!node::io::takeDouble(stream, raw, offset) ||
|
||||||
|
!node::io::takeDouble(stream, raw, velocity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
trainset.name = std::move(name);
|
||||||
|
trainset.track = std::move(track);
|
||||||
|
trainset.offset = static_cast<float>(offset);
|
||||||
|
trainset.velocity = static_cast<float>(velocity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<runtime::RuntimeTrainset> bakeTrainsets(
|
||||||
|
const SceneDocument& document,
|
||||||
|
const std::vector<runtime::RuntimeDynamicObject>& dynamics) {
|
||||||
|
std::vector<runtime::RuntimeTrainset> trainsets;
|
||||||
|
trainsets.reserve(document.trainset.size());
|
||||||
|
|
||||||
|
for (std::size_t trainsetIdx = 0; trainsetIdx < document.trainset.size(); ++trainsetIdx) {
|
||||||
|
runtime::RuntimeTrainset trainset;
|
||||||
|
if (!detail::parseTrainsetHeader(document.trainset[trainsetIdx], trainset)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t dynIdx = 0; dynIdx < dynamics.size(); ++dynIdx) {
|
||||||
|
const runtime::RuntimeDynamicObject& vehicle = dynamics[dynIdx];
|
||||||
|
if (vehicle.trainsetIndex != trainsetIdx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
trainset.vehicleIndices.push_back(dynIdx);
|
||||||
|
trainset.couplings.push_back(vehicle.coupling);
|
||||||
|
if (trainset.driverIndex == static_cast<std::size_t>(-1) &&
|
||||||
|
!vehicle.driverType.empty() && vehicle.driverType != "0") {
|
||||||
|
trainset.driverIndex = trainset.vehicleIndices.size() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trainsets.push_back(std::move(trainset));
|
||||||
|
}
|
||||||
|
|
||||||
|
return trainsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::bake
|
||||||
272
eu07-parser/include/eu07/scene/binary/codec.hpp
Normal file
272
eu07-parser/include/eu07/scene/binary/codec.hpp
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// EU7B v1 — binarna scena (SCM/SCN + referencje include, bez rozwijania INC).
|
||||||
|
// Little-endian. Bez NMT, bez mesh/e3d.
|
||||||
|
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/include_types.hpp>
|
||||||
|
#include <eu07/scene/node/track.hpp>
|
||||||
|
|
||||||
|
#include <eu07/scene/binary/common.hpp>
|
||||||
|
#include <eu07/scene/binary/io.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::binary {
|
||||||
|
|
||||||
|
inline constexpr std::uint32_t kVersion = kVersionLegacy;
|
||||||
|
|
||||||
|
inline constexpr std::array<char, 4> kChunkIncl{'I', 'N', 'C', 'L'};
|
||||||
|
inline constexpr std::array<char, 4> kChunkTrak{'T', 'R', 'A', 'K'};
|
||||||
|
|
||||||
|
enum class TrackKind : std::uint8_t {
|
||||||
|
Normal = 0,
|
||||||
|
Switch = 1,
|
||||||
|
Road = 2,
|
||||||
|
Cross = 3,
|
||||||
|
Other = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LoadedSceneBinary {
|
||||||
|
SceneDocument document;
|
||||||
|
std::uint32_t version = 0;
|
||||||
|
struct ChunkSummary {
|
||||||
|
std::array<char, 4> id{};
|
||||||
|
std::uint32_t size = 0;
|
||||||
|
bool recognized = false;
|
||||||
|
};
|
||||||
|
std::vector<ChunkSummary> chunks;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline void writeBezier(std::ostream& out, const TrackBezier& bez) {
|
||||||
|
io::writeVec3(out, bez.p1);
|
||||||
|
io::writeVec3(out, bez.cv1);
|
||||||
|
io::writeVec3(out, bez.cv2);
|
||||||
|
io::writeVec3(out, bez.p2);
|
||||||
|
io::writeF64(out, bez.roll1);
|
||||||
|
io::writeF64(out, bez.roll2);
|
||||||
|
io::writeF64(out, bez.radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeIncludeRecord(std::ostream& out, const ParsedInclude& inc) {
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(inc.line));
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(inc.file.size()));
|
||||||
|
out.write(inc.file.data(), static_cast<std::streamsize>(inc.file.size()));
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(inc.parameters.size()));
|
||||||
|
for (const std::string& param : inc.parameters) {
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(param.size()));
|
||||||
|
out.write(param.data(), static_cast<std::streamsize>(param.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TrackBezier readBezier(std::istream& in) {
|
||||||
|
TrackBezier bez;
|
||||||
|
bez.p1 = io::readVec3(in);
|
||||||
|
bez.cv1 = io::readVec3(in);
|
||||||
|
bez.cv2 = io::readVec3(in);
|
||||||
|
bez.p2 = io::readVec3(in);
|
||||||
|
bez.roll1 = io::readF64(in);
|
||||||
|
bez.roll2 = io::readF64(in);
|
||||||
|
bez.radius = io::readF64(in);
|
||||||
|
return bez;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline ParsedInclude readIncludeRecord(std::istream& in) {
|
||||||
|
ParsedInclude inc;
|
||||||
|
inc.line = io::readU32(in);
|
||||||
|
inc.file = io::readString(in);
|
||||||
|
const std::uint32_t paramCount = io::readU32(in);
|
||||||
|
inc.parameters.reserve(paramCount);
|
||||||
|
for (std::uint32_t i = 0; i < paramCount; ++i) {
|
||||||
|
inc.parameters.push_back(io::readString(in));
|
||||||
|
}
|
||||||
|
inc.expanded = false;
|
||||||
|
return inc;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TrackNode>
|
||||||
|
inline void writeTrackRecord(std::ostream& out, const TrackKind kind, const TrackNode& node) {
|
||||||
|
io::writeU8(out, static_cast<std::uint8_t>(kind));
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(node.header.name.size()));
|
||||||
|
out.write(node.header.name.data(), static_cast<std::streamsize>(node.header.name.size()));
|
||||||
|
io::writeVec3(out, node.header.originOffset);
|
||||||
|
io::writeF64(out, node.length);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(node.beziers.size()));
|
||||||
|
for (const TrackBezier& bez : node.beziers) {
|
||||||
|
writeBezier(out, bez);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TrackNode, typename PushFn>
|
||||||
|
inline void readTrackBody(std::istream& in, PushFn push) {
|
||||||
|
TrackNode node;
|
||||||
|
node.header.name = io::readString(in);
|
||||||
|
node.header.originOffset = io::readVec3(in);
|
||||||
|
node.length = io::readF64(in);
|
||||||
|
const std::uint32_t bezCount = io::readU32(in);
|
||||||
|
node.beziers.reserve(bezCount);
|
||||||
|
for (std::uint32_t i = 0; i < bezCount; ++i) {
|
||||||
|
node.beziers.push_back(readBezier(in));
|
||||||
|
}
|
||||||
|
push(std::move(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildInclPayload(const SceneDocument& doc) {
|
||||||
|
std::ostringstream inclOut(std::ios::binary);
|
||||||
|
io::writeU32(inclOut, static_cast<std::uint32_t>(doc.include.size()));
|
||||||
|
for (const ParsedInclude& inc : doc.include) {
|
||||||
|
writeIncludeRecord(inclOut, inc);
|
||||||
|
}
|
||||||
|
const std::string blob = inclOut.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildTrakPayload(const SceneDocument& doc) {
|
||||||
|
std::ostringstream trakOut(std::ios::binary);
|
||||||
|
for (const ParsedTrackNormal& node : doc.nodeTrackNormal) {
|
||||||
|
writeTrackRecord(trakOut, TrackKind::Normal, node);
|
||||||
|
}
|
||||||
|
for (const ParsedTrackSwitch& node : doc.nodeTrackSwitch) {
|
||||||
|
writeTrackRecord(trakOut, TrackKind::Switch, node);
|
||||||
|
}
|
||||||
|
for (const ParsedTrackRoad& node : doc.nodeTrackRoad) {
|
||||||
|
writeTrackRecord(trakOut, TrackKind::Road, node);
|
||||||
|
}
|
||||||
|
for (const ParsedTrackCross& node : doc.nodeTrackCross) {
|
||||||
|
writeTrackRecord(trakOut, TrackKind::Cross, node);
|
||||||
|
}
|
||||||
|
for (const ParsedTrackOther& node : doc.nodeTrackOther) {
|
||||||
|
writeTrackRecord(trakOut, TrackKind::Other, node);
|
||||||
|
}
|
||||||
|
const std::string blob = trakOut.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
inline void writeSceneBinary(const std::filesystem::path& outPath, const SceneDocument& doc) {
|
||||||
|
const std::vector<char> inclPayload = detail::buildInclPayload(doc);
|
||||||
|
const std::vector<char> trakPayload = detail::buildTrakPayload(doc);
|
||||||
|
|
||||||
|
std::ofstream out(outPath, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!out) {
|
||||||
|
throw std::runtime_error("Nie mozna zapisac: " + outPath.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(kMagic.data(), 4);
|
||||||
|
io::writeU32(out, kVersion);
|
||||||
|
|
||||||
|
io::writeChunkHeader(out, kChunkIncl, 8 + static_cast<std::uint32_t>(inclPayload.size()));
|
||||||
|
out.write(inclPayload.data(), static_cast<std::streamsize>(inclPayload.size()));
|
||||||
|
|
||||||
|
io::writeChunkHeader(out, kChunkTrak, 8 + static_cast<std::uint32_t>(trakPayload.size()));
|
||||||
|
out.write(trakPayload.data(), static_cast<std::streamsize>(trakPayload.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline LoadedSceneBinary readSceneBinary(const std::filesystem::path& path) {
|
||||||
|
std::ifstream in(path, std::ios::binary);
|
||||||
|
if (!in) {
|
||||||
|
throw std::runtime_error("Nie mozna otworzyc: " + path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<char, 4> magic{};
|
||||||
|
in.read(magic.data(), 4);
|
||||||
|
if (!in || magic != kMagic) {
|
||||||
|
throw std::runtime_error("EU7B: zly magic");
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint32_t version = io::readU32(in);
|
||||||
|
if (version != kVersion) {
|
||||||
|
throw std::runtime_error("EU7B: nieobslugiwana wersja " + std::to_string(version));
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadedSceneBinary loaded;
|
||||||
|
loaded.document = SceneDocument{};
|
||||||
|
loaded.version = version;
|
||||||
|
|
||||||
|
while (in.peek() != EOF) {
|
||||||
|
std::array<char, 4> chunkId{};
|
||||||
|
in.read(chunkId.data(), 4);
|
||||||
|
if (!in) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const std::uint32_t chunkSize = io::readU32(in);
|
||||||
|
if (chunkSize < 8) {
|
||||||
|
throw std::runtime_error("EU7B: uszkodzony chunk");
|
||||||
|
}
|
||||||
|
const std::uint32_t payloadSize = chunkSize - 8;
|
||||||
|
|
||||||
|
LoadedSceneBinary::ChunkSummary chunkSummary;
|
||||||
|
chunkSummary.id = chunkId;
|
||||||
|
chunkSummary.size = chunkSize;
|
||||||
|
|
||||||
|
if (chunkId == kChunkIncl) {
|
||||||
|
chunkSummary.recognized = true;
|
||||||
|
const auto chunkStart = in.tellg();
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
loaded.document.include.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
loaded.document.include.push_back(detail::readIncludeRecord(in));
|
||||||
|
}
|
||||||
|
if (in.tellg() - chunkStart != static_cast<std::streamoff>(payloadSize)) {
|
||||||
|
throw std::runtime_error("EU7B: uszkodzony chunk INCL");
|
||||||
|
}
|
||||||
|
loaded.chunks.push_back(chunkSummary);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkId == kChunkTrak) {
|
||||||
|
chunkSummary.recognized = true;
|
||||||
|
const auto chunkStart = in.tellg();
|
||||||
|
while (in.tellg() - chunkStart < static_cast<std::streamoff>(payloadSize)) {
|
||||||
|
switch (static_cast<TrackKind>(io::readU8(in))) {
|
||||||
|
case TrackKind::Normal:
|
||||||
|
detail::readTrackBody<ParsedTrackNormal>(in, [&](ParsedTrackNormal node) {
|
||||||
|
loaded.document.nodeTrackNormal.push_back(std::move(node));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case TrackKind::Switch:
|
||||||
|
detail::readTrackBody<ParsedTrackSwitch>(in, [&](ParsedTrackSwitch node) {
|
||||||
|
loaded.document.nodeTrackSwitch.push_back(std::move(node));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case TrackKind::Road:
|
||||||
|
detail::readTrackBody<ParsedTrackRoad>(in, [&](ParsedTrackRoad node) {
|
||||||
|
loaded.document.nodeTrackRoad.push_back(std::move(node));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case TrackKind::Cross:
|
||||||
|
detail::readTrackBody<ParsedTrackCross>(in, [&](ParsedTrackCross node) {
|
||||||
|
loaded.document.nodeTrackCross.push_back(std::move(node));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case TrackKind::Other:
|
||||||
|
detail::readTrackBody<ParsedTrackOther>(in, [&](ParsedTrackOther node) {
|
||||||
|
loaded.document.nodeTrackOther.push_back(std::move(node));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("EU7B: nieznany typ toru");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loaded.chunks.push_back(chunkSummary);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
in.seekg(static_cast<std::streamoff>(payloadSize), std::ios::cur);
|
||||||
|
loaded.chunks.push_back(chunkSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::binary
|
||||||
119
eu07-parser/include/eu07/scene/binary/common.hpp
Normal file
119
eu07-parser/include/eu07/scene/binary/common.hpp
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/binary/io.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace eu07::scene::binary {
|
||||||
|
|
||||||
|
inline constexpr std::array<char, 4> kMagic{'E', 'U', '7', 'B'};
|
||||||
|
// Pole version w EU7B: 5 = TERR terrain-lite. 4 = slim node + packed mesh. 3 = float32. 2 = float64.
|
||||||
|
inline constexpr std::uint32_t kVersionLegacy = 1; // codec.hpp — nieobslugiwane w CLI
|
||||||
|
inline constexpr std::uint32_t kVersionRuntime = 6;
|
||||||
|
inline constexpr std::uint32_t kVersionRuntimeV7 = 7;
|
||||||
|
inline constexpr std::uint32_t kVersionRuntimeV5 = 5;
|
||||||
|
inline constexpr std::uint32_t kVersionRuntimeV4 = 4;
|
||||||
|
inline constexpr std::uint32_t kVersionRuntimeF32 = 3;
|
||||||
|
inline constexpr std::uint32_t kVersionRuntimeF64 = 2;
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isSupportedEu7bVersion(const std::uint32_t version) noexcept {
|
||||||
|
return version == kVersionRuntimeV7 || version == kVersionRuntime ||
|
||||||
|
version == kVersionRuntimeV5 || version == kVersionRuntimeV4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string formatVersionName(const std::uint32_t version) {
|
||||||
|
if (version == kVersionRuntimeV7) {
|
||||||
|
return "RuntimeV7";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntime) {
|
||||||
|
return "RuntimeV6";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntimeV5) {
|
||||||
|
return "RuntimeV5";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntimeV4) {
|
||||||
|
return "RuntimeV4";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntimeF32) {
|
||||||
|
return "RuntimeF32";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntimeF64) {
|
||||||
|
return "RuntimeF64";
|
||||||
|
}
|
||||||
|
if (version == kVersionLegacy) {
|
||||||
|
return "Legacy";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string formatVersionDescription(const std::uint32_t version) {
|
||||||
|
if (version == kVersionRuntimeV7) {
|
||||||
|
return "bake v6 + MODL w chunkach PIDX/PACK per sekcja 1km (v7)";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntime) {
|
||||||
|
return "bake + chunki STRS/INCL/TRAK/... + INCL site transform (v6)";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntimeV5) {
|
||||||
|
return "bake + chunki STRS/INCL/TRAK/... + TERR batched per sekcja 1km (v5)";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntimeV4) {
|
||||||
|
return "bake slim v4 — przebakeuj do v5 dla TERR";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntimeF32) {
|
||||||
|
return "bake float32 v3 — przebakeuj do v4";
|
||||||
|
}
|
||||||
|
if (version == kVersionRuntimeF64) {
|
||||||
|
return "bake float64 v2 — przebakeuj do v4";
|
||||||
|
}
|
||||||
|
if (version == kVersionLegacy) {
|
||||||
|
return "legacy (nieobslugiwane w CLI)";
|
||||||
|
}
|
||||||
|
return "nieobslugiwana wersja " + std::to_string(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isSceneBinaryPath(const std::filesystem::path& path) {
|
||||||
|
const std::string ext = path.extension().string();
|
||||||
|
if (ext.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string lower = ext;
|
||||||
|
for (char& ch : lower) {
|
||||||
|
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||||
|
}
|
||||||
|
return lower == ".eu7";
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool probeSceneBinaryMagic(const std::filesystem::path& path) {
|
||||||
|
std::ifstream in(path, std::ios::binary);
|
||||||
|
if (!in) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::array<char, 4> magic{};
|
||||||
|
in.read(magic.data(), 4);
|
||||||
|
return in.good() && magic == kMagic;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isSceneBinaryInput(const std::filesystem::path& path) {
|
||||||
|
return isSceneBinaryPath(path) || probeSceneBinaryMagic(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint32_t peekSceneBinaryVersion(const std::filesystem::path& path) {
|
||||||
|
std::ifstream in(path, std::ios::binary);
|
||||||
|
if (!in) {
|
||||||
|
throw std::runtime_error("Nie mozna otworzyc: " + path.string());
|
||||||
|
}
|
||||||
|
std::array<char, 4> magic{};
|
||||||
|
in.read(magic.data(), 4);
|
||||||
|
if (!in || magic != kMagic) {
|
||||||
|
throw std::runtime_error("EU7B: zly magic");
|
||||||
|
}
|
||||||
|
return io::readU32(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::binary
|
||||||
717
eu07-parser/include/eu07/scene/binary/detokenize.hpp
Normal file
717
eu07-parser/include/eu07/scene/binary/detokenize.hpp
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Runtime → strumień SourceToken (jak po eu07::parseFile) z odtworzonym stosem origin/scale/rotate.
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
#include <eu07/scene/bake/module.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
#include <eu07/scene/runtime/transform.hpp>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <format>
|
||||||
|
#include <limits>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::binary::detokenize {
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string formatDouble(const double value) {
|
||||||
|
std::string text = std::format("{:.4f}", value);
|
||||||
|
if (const std::size_t 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 TokenWriter {
|
||||||
|
public:
|
||||||
|
std::vector<SourceToken> tokens;
|
||||||
|
|
||||||
|
void newline() { ++line_; }
|
||||||
|
|
||||||
|
void push(std::string value) { tokens.push_back(SourceToken{std::move(value), line_}); }
|
||||||
|
|
||||||
|
void push(std::string_view value) { push(std::string(value)); }
|
||||||
|
|
||||||
|
void push(const char* value) { push(std::string_view(value)); }
|
||||||
|
|
||||||
|
void push(const double value) { push(formatDouble(value)); }
|
||||||
|
|
||||||
|
void push(const float value) { push(static_cast<double>(value)); }
|
||||||
|
|
||||||
|
void push(const int value) { push(std::format("{}", value)); }
|
||||||
|
|
||||||
|
void pushVec3(const runtime::Vec3& v) {
|
||||||
|
push(v.x);
|
||||||
|
push(v.y);
|
||||||
|
push(v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::size_t line_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string includeTextPath(const bake::ModuleInclude& include) {
|
||||||
|
if (!include.sourcePath.empty()) {
|
||||||
|
return include.sourcePath;
|
||||||
|
}
|
||||||
|
std::filesystem::path path{include.binaryPath};
|
||||||
|
if (path.extension() == ".eu7") {
|
||||||
|
path.replace_extension(".inc");
|
||||||
|
}
|
||||||
|
return path.generic_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline double rangeMaxFromNode(const runtime::BasicNode& node) {
|
||||||
|
if (node.rangeSquaredMax >= std::numeric_limits<double>::max() * 0.5) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
return std::sqrt(node.rangeSquaredMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline double rangeMinFromNode(const runtime::BasicNode& node) {
|
||||||
|
return std::sqrt(node.rangeSquaredMin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string nodeDisplayName(const runtime::BasicNode& node) {
|
||||||
|
return node.name.empty() ? "none" : node.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void pushNodeHeader(TokenWriter& w, const runtime::BasicNode& node) {
|
||||||
|
w.push("node");
|
||||||
|
w.push(rangeMaxFromNode(node));
|
||||||
|
w.push(rangeMinFromNode(node));
|
||||||
|
w.push(nodeDisplayName(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransformEmitState {
|
||||||
|
std::vector<runtime::Vec3> originStack;
|
||||||
|
std::vector<runtime::Vec3> scaleStack;
|
||||||
|
runtime::Vec3 rotation{};
|
||||||
|
std::size_t groupDepth = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::Vec3 originPushDelta(
|
||||||
|
const std::vector<runtime::Vec3>& stack,
|
||||||
|
const std::size_t index) {
|
||||||
|
const runtime::Vec3& cumulative = stack[index];
|
||||||
|
if (index == 0) {
|
||||||
|
return cumulative;
|
||||||
|
}
|
||||||
|
const runtime::Vec3& parent = stack[index - 1];
|
||||||
|
return {cumulative.x - parent.x, cumulative.y - parent.y, cumulative.z - parent.z};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::Vec3 scalePushFactor(
|
||||||
|
const std::vector<runtime::Vec3>& stack,
|
||||||
|
const std::size_t index) {
|
||||||
|
const runtime::Vec3& cumulative = stack[index];
|
||||||
|
const runtime::Vec3 parent = index == 0 ? runtime::Vec3{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};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void syncTransformStack(TokenWriter& w, TransformEmitState& state, const runtime::TransformContext& target) {
|
||||||
|
while (state.groupDepth > target.groupStackDepth) {
|
||||||
|
w.push("endgroup");
|
||||||
|
--state.groupDepth;
|
||||||
|
}
|
||||||
|
while (state.scaleStack.size() > target.scaleStack.size()) {
|
||||||
|
w.push("endscale");
|
||||||
|
state.scaleStack.pop_back();
|
||||||
|
}
|
||||||
|
while (state.originStack.size() > target.originStack.size()) {
|
||||||
|
w.push("endorigin");
|
||||||
|
state.originStack.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (state.originStack.size() < target.originStack.size()) {
|
||||||
|
const std::size_t index = state.originStack.size();
|
||||||
|
const runtime::Vec3 delta = originPushDelta(target.originStack, index);
|
||||||
|
w.push("origin");
|
||||||
|
w.pushVec3(delta);
|
||||||
|
state.originStack.push_back(target.originStack[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (state.scaleStack.size() < target.scaleStack.size()) {
|
||||||
|
const std::size_t index = state.scaleStack.size();
|
||||||
|
const runtime::Vec3 factor = scalePushFactor(target.scaleStack, index);
|
||||||
|
w.push("scale");
|
||||||
|
w.pushVec3(factor);
|
||||||
|
state.scaleStack.push_back(target.scaleStack[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (state.groupDepth < target.groupStackDepth) {
|
||||||
|
w.push("group");
|
||||||
|
++state.groupDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.rotation.x != target.rotation.x || state.rotation.y != target.rotation.y ||
|
||||||
|
state.rotation.z != target.rotation.z) {
|
||||||
|
w.push("rotate");
|
||||||
|
w.pushVec3(target.rotation);
|
||||||
|
state.rotation = target.rotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitNodeTransform(TokenWriter& w, TransformEmitState& state, const runtime::BasicNode& node) {
|
||||||
|
syncTransformStack(w, state, node.transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string_view trackEnvironmentName(const runtime::TrackEnvironment env) {
|
||||||
|
switch (env) {
|
||||||
|
case runtime::TrackEnvironment::Flat:
|
||||||
|
return "flat";
|
||||||
|
case runtime::TrackEnvironment::Mountains:
|
||||||
|
return "mountains";
|
||||||
|
case runtime::TrackEnvironment::Canyon:
|
||||||
|
return "canyon";
|
||||||
|
case runtime::TrackEnvironment::Tunnel:
|
||||||
|
return "tunnel";
|
||||||
|
case runtime::TrackEnvironment::Bridge:
|
||||||
|
return "bridge";
|
||||||
|
case runtime::TrackEnvironment::Bank:
|
||||||
|
return "bank";
|
||||||
|
default:
|
||||||
|
return "flat";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string_view trackKindToken(const runtime::RuntimeTrack& track) {
|
||||||
|
if (track.category == runtime::TrackCategory::Road) {
|
||||||
|
return "road";
|
||||||
|
}
|
||||||
|
if (track.category == runtime::TrackCategory::Water) {
|
||||||
|
return "river";
|
||||||
|
}
|
||||||
|
switch (track.trackType) {
|
||||||
|
case runtime::TrackType::Switch:
|
||||||
|
return "switch";
|
||||||
|
case runtime::TrackType::Cross:
|
||||||
|
return "cross";
|
||||||
|
case runtime::TrackType::Table:
|
||||||
|
return "turn";
|
||||||
|
default:
|
||||||
|
return "normal";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void pushTrackCore(TokenWriter& w, const runtime::RuntimeTrack& track) {
|
||||||
|
w.push(track.length);
|
||||||
|
w.push(track.trackWidth);
|
||||||
|
w.push(track.friction);
|
||||||
|
w.push(track.soundDistance);
|
||||||
|
w.push(track.qualityFlag);
|
||||||
|
w.push(track.damageFlag);
|
||||||
|
w.push(trackEnvironmentName(track.environment));
|
||||||
|
if (track.visibility) {
|
||||||
|
w.push("vis");
|
||||||
|
w.push(track.visibility->material1);
|
||||||
|
w.push(track.visibility->texLength);
|
||||||
|
w.push(track.visibility->material2);
|
||||||
|
w.push(track.visibility->texHeight1);
|
||||||
|
w.push(track.visibility->texWidth);
|
||||||
|
w.push(track.visibility->texSlope);
|
||||||
|
} else {
|
||||||
|
w.push("unvis");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void pushTrackSegments(
|
||||||
|
TokenWriter& w,
|
||||||
|
const runtime::RuntimeTrack& track,
|
||||||
|
const runtime::TransformContext& transform) {
|
||||||
|
for (const runtime::SegmentPath& seg : track.paths) {
|
||||||
|
w.pushVec3(runtime::subtractOriginOffset(seg.pStart, transform));
|
||||||
|
w.push(seg.rollStart);
|
||||||
|
w.pushVec3(seg.cpOut);
|
||||||
|
w.pushVec3(seg.cpIn);
|
||||||
|
w.pushVec3(runtime::subtractOriginOffset(seg.pEnd, transform));
|
||||||
|
w.push(seg.rollEnd);
|
||||||
|
w.push(seg.radius);
|
||||||
|
}
|
||||||
|
for (const auto& [key, value] : track.tailKeywords) {
|
||||||
|
w.push(key);
|
||||||
|
if (key == "sleepermodel") {
|
||||||
|
std::size_t start = 0;
|
||||||
|
while (start < value.size()) {
|
||||||
|
const std::size_t 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitTrack(TokenWriter& w, TransformEmitState& state, const runtime::RuntimeTrack& track) {
|
||||||
|
emitNodeTransform(w, state, track.node);
|
||||||
|
pushNodeHeader(w, track.node);
|
||||||
|
w.push("track");
|
||||||
|
w.push(trackKindToken(track));
|
||||||
|
pushTrackCore(w, track);
|
||||||
|
pushTrackSegments(w, track, track.node.transform);
|
||||||
|
w.push("endtrack");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string tractionResistivityToken(const runtime::RuntimeTraction& traction) {
|
||||||
|
if (traction.resistivityLegacy != 0.0) {
|
||||||
|
return formatDouble(traction.resistivityLegacy);
|
||||||
|
}
|
||||||
|
return formatDouble(traction.resistivityOhmPerM / 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string tractionMaterialToken(const runtime::RuntimeTraction& traction) {
|
||||||
|
if (!traction.materialRaw.empty()) {
|
||||||
|
return traction.materialRaw;
|
||||||
|
}
|
||||||
|
switch (traction.material) {
|
||||||
|
case runtime::TractionWireMaterial::Aluminium:
|
||||||
|
return "al";
|
||||||
|
case runtime::TractionWireMaterial::None:
|
||||||
|
return "none";
|
||||||
|
default:
|
||||||
|
return "cu";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitTraction(TokenWriter& w, TransformEmitState& state, const runtime::RuntimeTraction& traction) {
|
||||||
|
emitNodeTransform(w, state, traction.node);
|
||||||
|
const runtime::TransformContext& transform = traction.node.transform;
|
||||||
|
pushNodeHeader(w, traction.node);
|
||||||
|
w.push("traction");
|
||||||
|
w.push(traction.powerSupplyName);
|
||||||
|
w.push(traction.nominalVoltage);
|
||||||
|
w.push(traction.maxCurrent);
|
||||||
|
w.push(tractionResistivityToken(traction));
|
||||||
|
w.push(tractionMaterialToken(traction));
|
||||||
|
w.push(traction.wireThickness);
|
||||||
|
w.push(traction.damageFlag);
|
||||||
|
w.pushVec3(runtime::subtractOriginOffset(traction.wireP1, transform));
|
||||||
|
w.pushVec3(runtime::subtractOriginOffset(traction.wireP2, transform));
|
||||||
|
w.pushVec3(runtime::subtractOriginOffset(traction.wireP3, transform));
|
||||||
|
w.pushVec3(runtime::subtractOriginOffset(traction.wireP4, transform));
|
||||||
|
w.push(traction.minHeight);
|
||||||
|
w.push(traction.segmentLength);
|
||||||
|
w.push(traction.wireCount);
|
||||||
|
w.push(traction.wireOffset);
|
||||||
|
w.push(traction.node.visible ? "vis" : "unvis");
|
||||||
|
if (traction.parallelName) {
|
||||||
|
w.push("parallel");
|
||||||
|
w.push(*traction.parallelName);
|
||||||
|
}
|
||||||
|
w.push("endtraction");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitPowerSource(
|
||||||
|
TokenWriter& w,
|
||||||
|
TransformEmitState& state,
|
||||||
|
const runtime::RuntimeTractionPowerSource& source) {
|
||||||
|
emitNodeTransform(w, state, source.node);
|
||||||
|
pushNodeHeader(w, source.node);
|
||||||
|
w.push("tractionpowersource");
|
||||||
|
w.pushVec3(runtime::inverseTransformPoint(source.position, source.node.transform));
|
||||||
|
w.push(source.nominalVoltage);
|
||||||
|
w.push(source.voltageFrequency);
|
||||||
|
w.push(source.internalResistanceLegacy);
|
||||||
|
w.push(source.maxOutputCurrent);
|
||||||
|
w.push(source.fastFuseTimeout);
|
||||||
|
w.push(source.fastFuseRepetition);
|
||||||
|
w.push(source.slowFuseTimeout);
|
||||||
|
if (source.modifier == runtime::PowerSourceModifier::Recuperation) {
|
||||||
|
w.push("recuperation");
|
||||||
|
} else if (source.modifier == runtime::PowerSourceModifier::Section) {
|
||||||
|
w.push("section");
|
||||||
|
}
|
||||||
|
w.push("end");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitShape(TokenWriter& w, TransformEmitState& state, const runtime::RuntimeShapeNode& shape) {
|
||||||
|
emitNodeTransform(w, state, shape.node);
|
||||||
|
std::vector<runtime::WorldVertex> localVertices = shape.vertices;
|
||||||
|
runtime::inverseTransformShapeVertices(localVertices, shape.node.transform);
|
||||||
|
pushNodeHeader(w, shape.node);
|
||||||
|
const std::string_view shapeType =
|
||||||
|
shape.node.nodeType.empty() ? std::string_view{"triangles"} : std::string_view{shape.node.nodeType};
|
||||||
|
w.push(shapeType);
|
||||||
|
w.push(shape.materialPath);
|
||||||
|
const bool triangleList = shapeType == "triangles";
|
||||||
|
for (std::size_t i = 0; i < localVertices.size(); ++i) {
|
||||||
|
const runtime::WorldVertex& vert = localVertices[i];
|
||||||
|
w.pushVec3(vert.position);
|
||||||
|
w.pushVec3(vert.normal);
|
||||||
|
w.push(vert.u);
|
||||||
|
w.push(vert.v);
|
||||||
|
// MaSzyna shape_node::import: po kazdym wierzcholku getToken() — oczekuje
|
||||||
|
// "end" po 1. i 2. rogu kazdego trojkata (nie po 3.).
|
||||||
|
if (triangleList && i % 3 != 2) {
|
||||||
|
w.push("end");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.push("endtri");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitLines(TokenWriter& w, TransformEmitState& state, const runtime::RuntimeLinesNode& lines) {
|
||||||
|
emitNodeTransform(w, state, lines.node);
|
||||||
|
std::vector<runtime::WorldVertex> localVertices = lines.vertices;
|
||||||
|
runtime::inverseTransformShapeVertices(localVertices, lines.node.transform);
|
||||||
|
pushNodeHeader(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.lineWidth);
|
||||||
|
for (std::size_t i = 0; i + 1 < localVertices.size(); i += 2) {
|
||||||
|
w.pushVec3(localVertices[i].position);
|
||||||
|
w.pushVec3(localVertices[i + 1].position);
|
||||||
|
}
|
||||||
|
w.push("endlines");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitModel(TokenWriter& w, TransformEmitState& state, const runtime::RuntimeModelInstance& model) {
|
||||||
|
emitNodeTransform(w, state, model.node);
|
||||||
|
const runtime::Vec3 localLocation =
|
||||||
|
runtime::inverseTransformPoint(model.location, model.node.transform);
|
||||||
|
const double localRotationY = model.angles.y - model.node.transform.rotation.y;
|
||||||
|
pushNodeHeader(w, model.node);
|
||||||
|
w.push("model");
|
||||||
|
w.pushVec3(localLocation);
|
||||||
|
w.push(localRotationY);
|
||||||
|
w.push(model.modelFile);
|
||||||
|
w.push(model.textureFile);
|
||||||
|
w.push("endmodel");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitMemcell(TokenWriter& w, TransformEmitState& state, const runtime::RuntimeMemCell& cell) {
|
||||||
|
emitNodeTransform(w, state, cell.node);
|
||||||
|
pushNodeHeader(w, cell.node);
|
||||||
|
w.push("memcell");
|
||||||
|
w.pushVec3(runtime::inverseTransformPoint(cell.node.area.center, cell.node.transform));
|
||||||
|
w.push(cell.text);
|
||||||
|
w.push(cell.value1);
|
||||||
|
w.push(cell.value2);
|
||||||
|
w.push(cell.trackName.value_or("none"));
|
||||||
|
w.push("endmemcell");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string launcherTimeToken(const runtime::RuntimeEventLauncher& launcher) {
|
||||||
|
if (launcher.launchHour != 0 || launcher.launchMinute != 0) {
|
||||||
|
return std::format("{}", launcher.launchHour * 100 + launcher.launchMinute);
|
||||||
|
}
|
||||||
|
if (launcher.deltaTime != 0.0) {
|
||||||
|
return formatDouble(-launcher.deltaTime);
|
||||||
|
}
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitLauncher(
|
||||||
|
TokenWriter& w,
|
||||||
|
TransformEmitState& state,
|
||||||
|
const runtime::RuntimeEventLauncher& launcher) {
|
||||||
|
emitNodeTransform(w, state, launcher.node);
|
||||||
|
pushNodeHeader(w, launcher.node);
|
||||||
|
w.push("eventlauncher");
|
||||||
|
w.pushVec3(runtime::inverseTransformPoint(launcher.location, launcher.node.transform));
|
||||||
|
w.push(std::sqrt(launcher.radiusSquared));
|
||||||
|
w.push(launcher.activationKeyRaw);
|
||||||
|
w.push(launcherTimeToken(launcher));
|
||||||
|
w.push(launcher.event1Name);
|
||||||
|
if (!launcher.event2Name.empty() && launcher.event2Name != "none") {
|
||||||
|
w.push(launcher.event2Name);
|
||||||
|
}
|
||||||
|
if (launcher.condition) {
|
||||||
|
w.push("condition");
|
||||||
|
w.push(launcher.condition->memcellName);
|
||||||
|
w.push(launcher.condition->compareText);
|
||||||
|
if (launcher.condition->checkMask & 2) {
|
||||||
|
w.push(launcher.condition->compareValue1);
|
||||||
|
} else {
|
||||||
|
w.push("*");
|
||||||
|
}
|
||||||
|
if (launcher.condition->checkMask & 4) {
|
||||||
|
w.push(launcher.condition->compareValue2);
|
||||||
|
} else {
|
||||||
|
w.push("*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (launcher.trainTriggered) {
|
||||||
|
w.push("traintriggered");
|
||||||
|
}
|
||||||
|
w.push("endeventlauncher");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitDynamic(
|
||||||
|
TokenWriter& w,
|
||||||
|
TransformEmitState& state,
|
||||||
|
const runtime::RuntimeDynamicObject& vehicle,
|
||||||
|
const bool inTrainset) {
|
||||||
|
emitNodeTransform(w, state, vehicle.node);
|
||||||
|
pushNodeHeader(w, vehicle.node);
|
||||||
|
w.push("dynamic");
|
||||||
|
w.push(vehicle.dataFolder);
|
||||||
|
w.push(vehicle.skinFile);
|
||||||
|
w.push(vehicle.mmdFile);
|
||||||
|
if (!inTrainset) {
|
||||||
|
w.push(vehicle.trackName);
|
||||||
|
}
|
||||||
|
w.push(vehicle.offset);
|
||||||
|
w.push(vehicle.driverType);
|
||||||
|
if (inTrainset) {
|
||||||
|
w.push(vehicle.couplingRaw.empty() ? std::to_string(vehicle.coupling) : vehicle.couplingRaw);
|
||||||
|
}
|
||||||
|
if (!inTrainset) {
|
||||||
|
w.push(vehicle.velocity);
|
||||||
|
}
|
||||||
|
w.push(vehicle.loadCount);
|
||||||
|
if (!vehicle.loadType.empty()) {
|
||||||
|
w.push(vehicle.loadType);
|
||||||
|
}
|
||||||
|
if (vehicle.destination) {
|
||||||
|
w.push(*vehicle.destination);
|
||||||
|
}
|
||||||
|
w.push("enddynamic");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitSound(TokenWriter& w, TransformEmitState& state, const runtime::RuntimeSoundSource& sound) {
|
||||||
|
emitNodeTransform(w, state, sound.node);
|
||||||
|
pushNodeHeader(w, sound.node);
|
||||||
|
w.push("sound");
|
||||||
|
w.pushVec3(runtime::inverseTransformPoint(sound.location, sound.node.transform));
|
||||||
|
w.push(sound.wavFile);
|
||||||
|
w.push("endsound");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string_view eventTypeName(const runtime::EventType type) {
|
||||||
|
switch (type) {
|
||||||
|
case runtime::EventType::AddValues:
|
||||||
|
return "addvalues";
|
||||||
|
case runtime::EventType::UpdateValues:
|
||||||
|
return "updatevalues";
|
||||||
|
case runtime::EventType::CopyValues:
|
||||||
|
return "copyvalues";
|
||||||
|
case runtime::EventType::GetValues:
|
||||||
|
return "getvalues";
|
||||||
|
case runtime::EventType::PutValues:
|
||||||
|
return "putvalues";
|
||||||
|
case runtime::EventType::Whois:
|
||||||
|
return "whois";
|
||||||
|
case runtime::EventType::LogValues:
|
||||||
|
return "logvalues";
|
||||||
|
case runtime::EventType::Multiple:
|
||||||
|
return "multiple";
|
||||||
|
case runtime::EventType::Switch:
|
||||||
|
return "switch";
|
||||||
|
case runtime::EventType::TrackVel:
|
||||||
|
return "trackvel";
|
||||||
|
case runtime::EventType::Sound:
|
||||||
|
return "sound";
|
||||||
|
case runtime::EventType::Texture:
|
||||||
|
return "texture";
|
||||||
|
case runtime::EventType::Animation:
|
||||||
|
return "animation";
|
||||||
|
case runtime::EventType::Lights:
|
||||||
|
return "lights";
|
||||||
|
case runtime::EventType::Voltage:
|
||||||
|
return "voltage";
|
||||||
|
case runtime::EventType::Visible:
|
||||||
|
return "visible";
|
||||||
|
case runtime::EventType::Friction:
|
||||||
|
return "friction";
|
||||||
|
case runtime::EventType::Message:
|
||||||
|
return "message";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string joinTargets(const std::vector<std::string>& 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitEvent(TokenWriter& w, const runtime::RuntimeEvent& event) {
|
||||||
|
w.push("event");
|
||||||
|
w.push(event.name);
|
||||||
|
w.push(eventTypeName(event.type));
|
||||||
|
w.push(event.delay);
|
||||||
|
w.push(joinTargets(event.targets));
|
||||||
|
for (const auto& [key, value] : event.payload) {
|
||||||
|
if (!key.empty()) {
|
||||||
|
w.push(key);
|
||||||
|
}
|
||||||
|
if (!value.empty()) {
|
||||||
|
w.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.delayRandom != 0.0) {
|
||||||
|
w.push("randomdelay");
|
||||||
|
w.push(event.delayRandom);
|
||||||
|
}
|
||||||
|
if (event.delayDeparture != 0.0) {
|
||||||
|
w.push("departuredelay");
|
||||||
|
w.push(event.delayDeparture);
|
||||||
|
}
|
||||||
|
if (event.passive) {
|
||||||
|
w.push("passive");
|
||||||
|
}
|
||||||
|
w.push("endevent");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitInclude(TokenWriter& w, const bake::ModuleInclude& include) {
|
||||||
|
w.push("include");
|
||||||
|
w.push(includeTextPath(include));
|
||||||
|
for (const std::string& param : include.parameters) {
|
||||||
|
w.push(param);
|
||||||
|
}
|
||||||
|
w.push("end");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void emitTrainsetBlock(
|
||||||
|
TokenWriter& w,
|
||||||
|
TransformEmitState& state,
|
||||||
|
const runtime::RuntimeTrainset& trainset,
|
||||||
|
const std::vector<runtime::RuntimeDynamicObject>& dynamics) {
|
||||||
|
w.push("trainset");
|
||||||
|
w.push(trainset.name);
|
||||||
|
w.push(trainset.track);
|
||||||
|
w.push(trainset.offset);
|
||||||
|
w.push(trainset.velocity);
|
||||||
|
for (const std::size_t index : trainset.vehicleIndices) {
|
||||||
|
if (index < dynamics.size()) {
|
||||||
|
emitDynamic(w, state, dynamics[index], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.push("endtrainset");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<SourceToken> runtimeModuleToTokens(const bake::RuntimeModule& module) {
|
||||||
|
TokenWriter w;
|
||||||
|
TransformEmitState transformState;
|
||||||
|
|
||||||
|
for (const bake::ModuleInclude& include : module.includes) {
|
||||||
|
emitInclude(w, include);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const runtime::RuntimeTrack& track : module.scene.tracks) {
|
||||||
|
emitTrack(w, transformState, track);
|
||||||
|
}
|
||||||
|
for (const runtime::RuntimeTraction& traction : module.scene.traction) {
|
||||||
|
emitTraction(w, transformState, traction);
|
||||||
|
}
|
||||||
|
for (const runtime::RuntimeTractionPowerSource& source : module.scene.powerSources) {
|
||||||
|
emitPowerSource(w, transformState, source);
|
||||||
|
}
|
||||||
|
for (const runtime::RuntimeShapeNode& shape : module.scene.shapes) {
|
||||||
|
emitShape(w, transformState, shape);
|
||||||
|
}
|
||||||
|
for (const runtime::RuntimeLinesNode& lines : module.scene.lines) {
|
||||||
|
emitLines(w, transformState, lines);
|
||||||
|
}
|
||||||
|
for (const runtime::RuntimeModelInstance& model : module.scene.models) {
|
||||||
|
emitModel(w, transformState, model);
|
||||||
|
}
|
||||||
|
for (const runtime::RuntimeMemCell& cell : module.scene.memcells) {
|
||||||
|
emitMemcell(w, transformState, cell);
|
||||||
|
}
|
||||||
|
for (const runtime::RuntimeEventLauncher& launcher : module.scene.eventLaunchers) {
|
||||||
|
emitLauncher(w, transformState, launcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> emittedInTrainset(module.scene.dynamics.size(), false);
|
||||||
|
for (const runtime::RuntimeTrainset& trainset : module.scene.trainsets) {
|
||||||
|
for (const std::size_t index : trainset.vehicleIndices) {
|
||||||
|
if (index < emittedInTrainset.size()) {
|
||||||
|
emittedInTrainset[index] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < module.scene.dynamics.size(); ++i) {
|
||||||
|
if (!emittedInTrainset[i]) {
|
||||||
|
emitDynamic(w, transformState, module.scene.dynamics[i], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const runtime::RuntimeSoundSource& sound : module.scene.sounds) {
|
||||||
|
emitSound(w, transformState, sound);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const runtime::RuntimeEvent& event : module.scene.events) {
|
||||||
|
emitEvent(w, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::uint32_t i = 0; i < module.scene.firstInitCount; ++i) {
|
||||||
|
w.push("FirstInit");
|
||||||
|
w.newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const runtime::RuntimeTrainset& trainset : module.scene.trainsets) {
|
||||||
|
emitTrainsetBlock(w, transformState, trainset, module.scene.dynamics);
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool endsDetokenizedLine(std::string_view token) noexcept {
|
||||||
|
return token == "end" || token == "endtrack" || token == "endtraction" ||
|
||||||
|
token == "endtri" || token == "endtriangles" || token == "endlines" || token == "endmodel" ||
|
||||||
|
token == "endmemcell" || token == "endeventlauncher" || token == "enddynamic" ||
|
||||||
|
token == "endsound" || token == "endtrainset" || token == "endevent" ||
|
||||||
|
token == "endorigin" || token == "endscale" || token == "endgroup" ||
|
||||||
|
token == "FirstInit";
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string tokensToText(const std::vector<SourceToken>& tokens) {
|
||||||
|
std::string text;
|
||||||
|
text.reserve(tokens.size() * 12);
|
||||||
|
for (std::size_t i = 0; i < tokens.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
if (tokens[i].sourceLine != tokens[i - 1].sourceLine) {
|
||||||
|
text.push_back('\n');
|
||||||
|
} else {
|
||||||
|
text.push_back(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += tokens[i].value;
|
||||||
|
if (endsDetokenizedLine(tokens[i].value)) {
|
||||||
|
if (i + 1 >= tokens.size() || tokens[i + 1].sourceLine == tokens[i].sourceLine) {
|
||||||
|
text.push_back('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::binary::detokenize
|
||||||
141
eu07-parser/include/eu07/scene/binary/io.hpp
Normal file
141
eu07-parser/include/eu07/scene/binary/io.hpp
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <istream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace eu07::scene::binary::io {
|
||||||
|
|
||||||
|
inline void writeU8(std::ostream& out, const std::uint8_t v) {
|
||||||
|
out.put(static_cast<char>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeU16(std::ostream& out, const std::uint16_t v) {
|
||||||
|
const auto b = std::array<std::uint8_t, 2>{
|
||||||
|
static_cast<std::uint8_t>(v),
|
||||||
|
static_cast<std::uint8_t>(v >> 8),
|
||||||
|
};
|
||||||
|
out.write(reinterpret_cast<const char*>(b.data()), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeU32(std::ostream& out, const std::uint32_t v) {
|
||||||
|
const auto b = std::array<std::uint8_t, 4>{
|
||||||
|
static_cast<std::uint8_t>(v),
|
||||||
|
static_cast<std::uint8_t>(v >> 8),
|
||||||
|
static_cast<std::uint8_t>(v >> 16),
|
||||||
|
static_cast<std::uint8_t>(v >> 24),
|
||||||
|
};
|
||||||
|
out.write(reinterpret_cast<const char*>(b.data()), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeU64(std::ostream& out, const std::uint64_t v) {
|
||||||
|
writeU32(out, static_cast<std::uint32_t>(v));
|
||||||
|
writeU32(out, static_cast<std::uint32_t>(v >> 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeI32(std::ostream& out, const std::int32_t v) {
|
||||||
|
writeU32(out, static_cast<std::uint32_t>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeI16(std::ostream& out, const std::int16_t v) {
|
||||||
|
writeU16(out, static_cast<std::uint16_t>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeF32(std::ostream& out, const float v) {
|
||||||
|
static_assert(sizeof(float) == 4);
|
||||||
|
std::uint32_t bits = 0;
|
||||||
|
std::memcpy(&bits, &v, 4);
|
||||||
|
writeU32(out, bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skalary i Vec3 w EU7B v3+ — float32 na dysku (runtime w pamieci nadal double).
|
||||||
|
inline void writeF64(std::ostream& out, const double v) {
|
||||||
|
writeF32(out, static_cast<float>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeVec3(std::ostream& out, const Vec3& v) {
|
||||||
|
writeF64(out, v.x);
|
||||||
|
writeF64(out, v.y);
|
||||||
|
writeF64(out, v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeChunkHeader(std::ostream& out, const std::array<char, 4>& id, const std::uint32_t size) {
|
||||||
|
out.write(id.data(), 4);
|
||||||
|
writeU32(out, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint8_t readU8(std::istream& in) {
|
||||||
|
const int ch = in.get();
|
||||||
|
if (ch == EOF) {
|
||||||
|
throw std::runtime_error("EU7B: nieoczekiwany koniec pliku");
|
||||||
|
}
|
||||||
|
return static_cast<std::uint8_t>(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint32_t readU32(std::istream& in) {
|
||||||
|
std::array<std::uint8_t, 4> b{};
|
||||||
|
in.read(reinterpret_cast<char*>(b.data()), 4);
|
||||||
|
if (!in) {
|
||||||
|
throw std::runtime_error("EU7B: nieoczekiwany koniec pliku");
|
||||||
|
}
|
||||||
|
return static_cast<std::uint32_t>(b[0]) | (static_cast<std::uint32_t>(b[1]) << 8) |
|
||||||
|
(static_cast<std::uint32_t>(b[2]) << 16) | (static_cast<std::uint32_t>(b[3]) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint64_t readU64(std::istream& in) {
|
||||||
|
const std::uint64_t lo = readU32(in);
|
||||||
|
const std::uint64_t hi = readU32(in);
|
||||||
|
return lo | (hi << 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::int32_t readI32(std::istream& in) {
|
||||||
|
return static_cast<std::int32_t>(readU32(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint16_t readU16(std::istream& in) {
|
||||||
|
std::array<std::uint8_t, 2> b{};
|
||||||
|
in.read(reinterpret_cast<char*>(b.data()), 2);
|
||||||
|
if (!in) {
|
||||||
|
throw std::runtime_error("EU7B: nieoczekiwany koniec pliku");
|
||||||
|
}
|
||||||
|
return static_cast<std::uint16_t>(b[0]) | (static_cast<std::uint16_t>(b[1]) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::int16_t readI16(std::istream& in) {
|
||||||
|
return static_cast<std::int16_t>(readU16(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline float readF32(std::istream& in) {
|
||||||
|
const std::uint32_t bits = readU32(in);
|
||||||
|
float v = 0.f;
|
||||||
|
std::memcpy(&v, &bits, 4);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline double readF64(std::istream& in) {
|
||||||
|
return static_cast<double>(readF32(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vec3 readVec3(std::istream& in) {
|
||||||
|
return {readF64(in), readF64(in), readF64(in)};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string readString(std::istream& in) {
|
||||||
|
const std::uint32_t len = readU32(in);
|
||||||
|
std::string text(len, '\0');
|
||||||
|
if (len > 0) {
|
||||||
|
in.read(text.data(), static_cast<std::streamsize>(len));
|
||||||
|
if (!in) {
|
||||||
|
throw std::runtime_error("EU7B: nieoczekiwany koniec pliku (string)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::binary::io
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// EU7B v2 — chunki TRSET, EVNT (include wewnątrz binary::detail).
|
||||||
|
|
||||||
|
#include <eu07/scene/binary/io.hpp>
|
||||||
|
#include <eu07/scene/binary/runtime_codec.hpp>
|
||||||
|
#include <eu07/scene/binary/string_table.hpp>
|
||||||
|
#include <eu07/scene/bake/module.hpp>
|
||||||
|
#include <eu07/scene/runtime/directives.hpp>
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
inline void writeRuntimeTrainset(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::RuntimeTrainset& trainset) {
|
||||||
|
codec::writeStringId(out, table, trainset.name);
|
||||||
|
codec::writeStringId(out, table, trainset.track);
|
||||||
|
io::writeF32(out, trainset.offset);
|
||||||
|
io::writeF32(out, trainset.velocity);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(trainset.assignment.size()));
|
||||||
|
for (const auto& [key, value] : trainset.assignment) {
|
||||||
|
codec::writeStringId(out, table, key);
|
||||||
|
codec::writeStringId(out, table, value);
|
||||||
|
}
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(trainset.vehicleIndices.size()));
|
||||||
|
for (const std::size_t index : trainset.vehicleIndices) {
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(index));
|
||||||
|
}
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(trainset.couplings.size()));
|
||||||
|
for (const int coupling : trainset.couplings) {
|
||||||
|
io::writeI32(out, coupling);
|
||||||
|
}
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(trainset.driverIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeRuntimeEvent(std::ostream& out, StringTable& table, const runtime::RuntimeEvent& event) {
|
||||||
|
codec::writeStringId(out, table, event.name);
|
||||||
|
io::writeU8(out, static_cast<std::uint8_t>(event.type));
|
||||||
|
io::writeF64(out, event.delay);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(event.targets.size()));
|
||||||
|
for (const std::string& target : event.targets) {
|
||||||
|
codec::writeStringId(out, table, target);
|
||||||
|
}
|
||||||
|
io::writeF64(out, event.delayRandom);
|
||||||
|
io::writeF64(out, event.delayDeparture);
|
||||||
|
io::writeU8(out, event.ignored ? 1 : 0);
|
||||||
|
io::writeU8(out, event.passive ? 1 : 0);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(event.payload.size()));
|
||||||
|
for (const auto& [key, value] : event.payload) {
|
||||||
|
codec::writeStringId(out, table, key);
|
||||||
|
codec::writeStringId(out, table, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectTrainsetStrings(StringTable& table, const runtime::RuntimeTrainset& trainset) {
|
||||||
|
table.intern(trainset.name);
|
||||||
|
table.intern(trainset.track);
|
||||||
|
for (const auto& [key, value] : trainset.assignment) {
|
||||||
|
table.intern(key);
|
||||||
|
table.intern(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectEventStrings(StringTable& table, const runtime::RuntimeEvent& event) {
|
||||||
|
table.intern(event.name);
|
||||||
|
for (const std::string& target : event.targets) {
|
||||||
|
table.intern(target);
|
||||||
|
}
|
||||||
|
for (const auto& [key, value] : event.payload) {
|
||||||
|
table.intern(key);
|
||||||
|
table.intern(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildTrsetPayloadV2(
|
||||||
|
StringTable& table,
|
||||||
|
const std::vector<runtime::RuntimeTrainset>& trainsets) {
|
||||||
|
std::ostringstream out(std::ios::binary);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(trainsets.size()));
|
||||||
|
for (const runtime::RuntimeTrainset& trainset : trainsets) {
|
||||||
|
writeRuntimeTrainset(out, table, trainset);
|
||||||
|
}
|
||||||
|
const std::string blob = out.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildEvntPayloadV2(
|
||||||
|
StringTable& table,
|
||||||
|
const std::vector<runtime::RuntimeEvent>& events) {
|
||||||
|
std::ostringstream out(std::ios::binary);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(events.size()));
|
||||||
|
for (const runtime::RuntimeEvent& event : events) {
|
||||||
|
writeRuntimeEvent(out, table, event);
|
||||||
|
}
|
||||||
|
const std::string blob = out.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::RuntimeTrainset readRuntimeTrainset(std::istream& in, const StringTable& table) {
|
||||||
|
runtime::RuntimeTrainset trainset;
|
||||||
|
trainset.name = table.resolve(io::readU32(in));
|
||||||
|
trainset.track = table.resolve(io::readU32(in));
|
||||||
|
trainset.offset = io::readF32(in);
|
||||||
|
trainset.velocity = io::readF32(in);
|
||||||
|
const std::uint32_t assignmentCount = io::readU32(in);
|
||||||
|
for (std::uint32_t i = 0; i < assignmentCount; ++i) {
|
||||||
|
const std::string key = table.resolve(io::readU32(in));
|
||||||
|
const std::string value = table.resolve(io::readU32(in));
|
||||||
|
trainset.assignment.emplace(std::move(key), std::move(value));
|
||||||
|
}
|
||||||
|
const std::uint32_t vehicleCount = io::readU32(in);
|
||||||
|
trainset.vehicleIndices.reserve(vehicleCount);
|
||||||
|
for (std::uint32_t i = 0; i < vehicleCount; ++i) {
|
||||||
|
trainset.vehicleIndices.push_back(io::readU32(in));
|
||||||
|
}
|
||||||
|
const std::uint32_t couplingCount = io::readU32(in);
|
||||||
|
trainset.couplings.reserve(couplingCount);
|
||||||
|
for (std::uint32_t i = 0; i < couplingCount; ++i) {
|
||||||
|
trainset.couplings.push_back(io::readI32(in));
|
||||||
|
}
|
||||||
|
trainset.driverIndex = io::readU32(in);
|
||||||
|
return trainset;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::RuntimeEvent readRuntimeEvent(std::istream& in, const StringTable& table) {
|
||||||
|
runtime::RuntimeEvent event;
|
||||||
|
event.name = table.resolve(io::readU32(in));
|
||||||
|
event.type = static_cast<runtime::EventType>(io::readU8(in));
|
||||||
|
event.delay = io::readF64(in);
|
||||||
|
const std::uint32_t targetCount = io::readU32(in);
|
||||||
|
event.targets.reserve(targetCount);
|
||||||
|
for (std::uint32_t i = 0; i < targetCount; ++i) {
|
||||||
|
event.targets.push_back(table.resolve(io::readU32(in)));
|
||||||
|
}
|
||||||
|
event.delayRandom = io::readF64(in);
|
||||||
|
event.delayDeparture = io::readF64(in);
|
||||||
|
event.ignored = io::readU8(in) != 0;
|
||||||
|
event.passive = io::readU8(in) != 0;
|
||||||
|
const std::uint32_t payloadCount = io::readU32(in);
|
||||||
|
event.payload.reserve(payloadCount);
|
||||||
|
for (std::uint32_t i = 0; i < payloadCount; ++i) {
|
||||||
|
event.payload.emplace_back(table.resolve(io::readU32(in)), table.resolve(io::readU32(in)));
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readTrsetChunkV2(std::istream& in, const StringTable& table, bake::RuntimeModule& module) {
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
module.scene.trainsets.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
module.scene.trainsets.push_back(readRuntimeTrainset(in, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readEvntChunkV2(std::istream& in, const StringTable& table, bake::RuntimeModule& module) {
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
module.scene.events.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
module.scene.events.push_back(readRuntimeEvent(in, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
429
eu07-parser/include/eu07/scene/binary/runtime_chunks_sim.hpp
Normal file
429
eu07-parser/include/eu07/scene/binary/runtime_chunks_sim.hpp
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// EU7B v2 — chunki TRAC, PWRS, MEMC, LAUN, DYNM, SOND (include wewnątrz binary::detail).
|
||||||
|
|
||||||
|
#include <eu07/scene/binary/io.hpp>
|
||||||
|
#include <eu07/scene/binary/runtime_codec.hpp>
|
||||||
|
#include <eu07/scene/binary/string_table.hpp>
|
||||||
|
#include <eu07/scene/bake/module.hpp>
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
inline void writeRuntimeTraction(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::RuntimeTraction& traction) {
|
||||||
|
codec::writeSlimNode(out, table, traction.node, "traction");
|
||||||
|
codec::writeStringId(out, table, traction.powerSupplyName);
|
||||||
|
io::writeU8(out, static_cast<std::uint8_t>(traction.material));
|
||||||
|
io::writeF32(out, traction.nominalVoltage);
|
||||||
|
io::writeF32(out, traction.maxCurrent);
|
||||||
|
io::writeF32(out, traction.resistivityOhmPerM);
|
||||||
|
io::writeF64(out, traction.resistivityLegacy);
|
||||||
|
codec::writeStringId(out, table, traction.materialRaw);
|
||||||
|
io::writeF32(out, traction.wireThickness);
|
||||||
|
io::writeI32(out, traction.damageFlag);
|
||||||
|
io::writeVec3(out, traction.wireP1);
|
||||||
|
io::writeVec3(out, traction.wireP2);
|
||||||
|
io::writeVec3(out, traction.wireP3);
|
||||||
|
io::writeVec3(out, traction.wireP4);
|
||||||
|
io::writeF64(out, traction.minHeight);
|
||||||
|
io::writeF64(out, traction.segmentLength);
|
||||||
|
io::writeI32(out, traction.wireCount);
|
||||||
|
io::writeF32(out, traction.wireOffset);
|
||||||
|
io::writeU8(out, traction.parallelName.has_value() ? 1 : 0);
|
||||||
|
if (traction.parallelName) {
|
||||||
|
codec::writeStringId(out, table, *traction.parallelName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeRuntimeMemcell(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::RuntimeMemCell& cell) {
|
||||||
|
codec::writeSlimNode(out, table, cell.node, "memcell");
|
||||||
|
codec::writeStringId(out, table, cell.text);
|
||||||
|
io::writeF64(out, cell.value1);
|
||||||
|
io::writeF64(out, cell.value2);
|
||||||
|
codec::writeStringId(out, table, cell.trackName.value_or(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeRuntimeLauncher(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::RuntimeEventLauncher& launcher) {
|
||||||
|
codec::writeSlimNode(out, table, launcher.node, "eventlauncher");
|
||||||
|
io::writeVec3(out, launcher.location);
|
||||||
|
io::writeF64(out, launcher.radiusSquared);
|
||||||
|
codec::writeStringId(out, table, launcher.activationKeyRaw);
|
||||||
|
io::writeI32(out, launcher.activationKey);
|
||||||
|
io::writeF64(out, launcher.deltaTime);
|
||||||
|
codec::writeStringId(out, table, launcher.event1Name);
|
||||||
|
codec::writeStringId(out, table, launcher.event2Name);
|
||||||
|
io::writeI32(out, launcher.launchHour);
|
||||||
|
io::writeI32(out, launcher.launchMinute);
|
||||||
|
io::writeU8(out, launcher.condition.has_value() ? 1 : 0);
|
||||||
|
if (launcher.condition) {
|
||||||
|
codec::writeStringId(out, table, launcher.condition->memcellName);
|
||||||
|
codec::writeStringId(out, table, launcher.condition->compareText);
|
||||||
|
io::writeF64(out, launcher.condition->compareValue1);
|
||||||
|
io::writeF64(out, launcher.condition->compareValue2);
|
||||||
|
io::writeI32(out, launcher.condition->checkMask);
|
||||||
|
}
|
||||||
|
io::writeU8(out, launcher.trainTriggered ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeRuntimeDynamic(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::RuntimeDynamicObject& vehicle) {
|
||||||
|
codec::writeSlimNode(out, table, vehicle.node, "dynamic");
|
||||||
|
codec::writeStringId(out, table, vehicle.dataFolder);
|
||||||
|
codec::writeStringId(out, table, vehicle.skinFile);
|
||||||
|
codec::writeStringId(out, table, vehicle.mmdFile);
|
||||||
|
codec::writeStringId(out, table, vehicle.trackName);
|
||||||
|
codec::writeStringId(out, table, vehicle.driverType);
|
||||||
|
codec::writeStringId(out, table, vehicle.loadType);
|
||||||
|
codec::writeStringId(out, table, vehicle.couplingParams);
|
||||||
|
codec::writeStringId(out, table, vehicle.couplingRaw);
|
||||||
|
io::writeF64(out, vehicle.offset);
|
||||||
|
io::writeI32(out, vehicle.coupling);
|
||||||
|
io::writeI32(out, vehicle.loadCount);
|
||||||
|
io::writeF32(out, vehicle.velocity);
|
||||||
|
io::writeU8(out, vehicle.destination.has_value() ? 1 : 0);
|
||||||
|
if (vehicle.destination) {
|
||||||
|
codec::writeStringId(out, table, *vehicle.destination);
|
||||||
|
}
|
||||||
|
io::writeU8(out, vehicle.trainsetIndex.has_value() ? 1 : 0);
|
||||||
|
if (vehicle.trainsetIndex) {
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(*vehicle.trainsetIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeRuntimeSound(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::RuntimeSoundSource& sound) {
|
||||||
|
codec::writeSlimNode(out, table, sound.node, "sound");
|
||||||
|
io::writeVec3(out, sound.location);
|
||||||
|
codec::writeStringId(out, table, sound.wavFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectTractionStrings(StringTable& table, const runtime::RuntimeTraction& traction) {
|
||||||
|
table.intern(traction.node.name);
|
||||||
|
table.intern(traction.powerSupplyName);
|
||||||
|
table.intern(traction.materialRaw);
|
||||||
|
if (traction.parallelName) {
|
||||||
|
table.intern(*traction.parallelName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectMemcellStrings(StringTable& table, const runtime::RuntimeMemCell& cell) {
|
||||||
|
table.intern(cell.node.name);
|
||||||
|
table.intern(cell.text);
|
||||||
|
if (cell.trackName) {
|
||||||
|
table.intern(*cell.trackName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectLauncherStrings(StringTable& table, const runtime::RuntimeEventLauncher& launcher) {
|
||||||
|
table.intern(launcher.node.name);
|
||||||
|
table.intern(launcher.activationKeyRaw);
|
||||||
|
table.intern(launcher.event1Name);
|
||||||
|
table.intern(launcher.event2Name);
|
||||||
|
if (launcher.condition) {
|
||||||
|
table.intern(launcher.condition->memcellName);
|
||||||
|
table.intern(launcher.condition->compareText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectDynamicStrings(StringTable& table, const runtime::RuntimeDynamicObject& vehicle) {
|
||||||
|
table.intern(vehicle.node.name);
|
||||||
|
table.intern(vehicle.dataFolder);
|
||||||
|
table.intern(vehicle.skinFile);
|
||||||
|
table.intern(vehicle.mmdFile);
|
||||||
|
table.intern(vehicle.trackName);
|
||||||
|
table.intern(vehicle.driverType);
|
||||||
|
table.intern(vehicle.loadType);
|
||||||
|
table.intern(vehicle.couplingParams);
|
||||||
|
table.intern(vehicle.couplingRaw);
|
||||||
|
if (vehicle.destination) {
|
||||||
|
table.intern(*vehicle.destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectSoundStrings(StringTable& table, const runtime::RuntimeSoundSource& sound) {
|
||||||
|
table.intern(sound.node.name);
|
||||||
|
table.intern(sound.wavFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildTracPayloadV2(
|
||||||
|
StringTable& table,
|
||||||
|
const std::vector<runtime::RuntimeTraction>& traction) {
|
||||||
|
std::ostringstream out(std::ios::binary);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(traction.size()));
|
||||||
|
for (const runtime::RuntimeTraction& item : traction) {
|
||||||
|
writeRuntimeTraction(out, table, item);
|
||||||
|
}
|
||||||
|
const std::string blob = out.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildMemcPayloadV2(
|
||||||
|
StringTable& table,
|
||||||
|
const std::vector<runtime::RuntimeMemCell>& cells) {
|
||||||
|
std::ostringstream out(std::ios::binary);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(cells.size()));
|
||||||
|
for (const runtime::RuntimeMemCell& cell : cells) {
|
||||||
|
writeRuntimeMemcell(out, table, cell);
|
||||||
|
}
|
||||||
|
const std::string blob = out.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildLaunPayloadV2(
|
||||||
|
StringTable& table,
|
||||||
|
const std::vector<runtime::RuntimeEventLauncher>& launchers) {
|
||||||
|
std::ostringstream out(std::ios::binary);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(launchers.size()));
|
||||||
|
for (const runtime::RuntimeEventLauncher& launcher : launchers) {
|
||||||
|
writeRuntimeLauncher(out, table, launcher);
|
||||||
|
}
|
||||||
|
const std::string blob = out.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildDynmPayloadV2(
|
||||||
|
StringTable& table,
|
||||||
|
const std::vector<runtime::RuntimeDynamicObject>& dynamics) {
|
||||||
|
std::ostringstream out(std::ios::binary);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(dynamics.size()));
|
||||||
|
for (const runtime::RuntimeDynamicObject& vehicle : dynamics) {
|
||||||
|
writeRuntimeDynamic(out, table, vehicle);
|
||||||
|
}
|
||||||
|
const std::string blob = out.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildSondPayloadV2(
|
||||||
|
StringTable& table,
|
||||||
|
const std::vector<runtime::RuntimeSoundSource>& sounds) {
|
||||||
|
std::ostringstream out(std::ios::binary);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(sounds.size()));
|
||||||
|
for (const runtime::RuntimeSoundSource& sound : sounds) {
|
||||||
|
writeRuntimeSound(out, table, sound);
|
||||||
|
}
|
||||||
|
const std::string blob = out.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::RuntimeTraction readRuntimeTraction(std::istream& in, const StringTable& table) {
|
||||||
|
runtime::RuntimeTraction traction;
|
||||||
|
traction.node = codec::readSlimNode(in, table, "traction");
|
||||||
|
traction.powerSupplyName = table.resolve(io::readU32(in));
|
||||||
|
traction.material = static_cast<runtime::TractionWireMaterial>(io::readU8(in));
|
||||||
|
traction.nominalVoltage = io::readF32(in);
|
||||||
|
traction.maxCurrent = io::readF32(in);
|
||||||
|
traction.resistivityOhmPerM = io::readF32(in);
|
||||||
|
traction.resistivityLegacy = io::readF64(in);
|
||||||
|
traction.materialRaw = table.resolve(io::readU32(in));
|
||||||
|
traction.wireThickness = io::readF32(in);
|
||||||
|
traction.damageFlag = io::readI32(in);
|
||||||
|
traction.wireP1 = io::readVec3(in);
|
||||||
|
traction.wireP2 = io::readVec3(in);
|
||||||
|
traction.wireP3 = io::readVec3(in);
|
||||||
|
traction.wireP4 = io::readVec3(in);
|
||||||
|
traction.minHeight = io::readF64(in);
|
||||||
|
traction.segmentLength = io::readF64(in);
|
||||||
|
traction.wireCount = io::readI32(in);
|
||||||
|
traction.wireOffset = io::readF32(in);
|
||||||
|
if (io::readU8(in) != 0) {
|
||||||
|
const std::string parallel = table.resolve(io::readU32(in));
|
||||||
|
if (!parallel.empty()) {
|
||||||
|
traction.parallelName = parallel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return traction;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::RuntimeMemCell readRuntimeMemcell(std::istream& in, const StringTable& table) {
|
||||||
|
runtime::RuntimeMemCell cell;
|
||||||
|
cell.node = codec::readSlimNode(in, table, "memcell");
|
||||||
|
cell.text = table.resolve(io::readU32(in));
|
||||||
|
cell.value1 = io::readF64(in);
|
||||||
|
cell.value2 = io::readF64(in);
|
||||||
|
const std::string track = table.resolve(io::readU32(in));
|
||||||
|
if (!track.empty()) {
|
||||||
|
cell.trackName = track;
|
||||||
|
}
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::RuntimeEventLauncher readRuntimeLauncher(std::istream& in, const StringTable& table) {
|
||||||
|
runtime::RuntimeEventLauncher launcher;
|
||||||
|
launcher.node = codec::readSlimNode(in, table, "eventlauncher");
|
||||||
|
launcher.location = io::readVec3(in);
|
||||||
|
launcher.radiusSquared = io::readF64(in);
|
||||||
|
launcher.activationKeyRaw = table.resolve(io::readU32(in));
|
||||||
|
launcher.activationKey = io::readI32(in);
|
||||||
|
launcher.deltaTime = io::readF64(in);
|
||||||
|
launcher.event1Name = table.resolve(io::readU32(in));
|
||||||
|
launcher.event2Name = table.resolve(io::readU32(in));
|
||||||
|
launcher.launchHour = io::readI32(in);
|
||||||
|
launcher.launchMinute = io::readI32(in);
|
||||||
|
if (io::readU8(in) != 0) {
|
||||||
|
runtime::EventLauncherCondition cond;
|
||||||
|
cond.memcellName = table.resolve(io::readU32(in));
|
||||||
|
cond.compareText = table.resolve(io::readU32(in));
|
||||||
|
cond.compareValue1 = io::readF64(in);
|
||||||
|
cond.compareValue2 = io::readF64(in);
|
||||||
|
cond.checkMask = io::readI32(in);
|
||||||
|
launcher.condition = cond;
|
||||||
|
}
|
||||||
|
launcher.trainTriggered = io::readU8(in) != 0;
|
||||||
|
return launcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::RuntimeDynamicObject readRuntimeDynamic(std::istream& in, const StringTable& table) {
|
||||||
|
runtime::RuntimeDynamicObject vehicle;
|
||||||
|
vehicle.node = codec::readSlimNode(in, table, "dynamic");
|
||||||
|
vehicle.dataFolder = table.resolve(io::readU32(in));
|
||||||
|
vehicle.skinFile = table.resolve(io::readU32(in));
|
||||||
|
vehicle.mmdFile = table.resolve(io::readU32(in));
|
||||||
|
vehicle.trackName = table.resolve(io::readU32(in));
|
||||||
|
vehicle.driverType = table.resolve(io::readU32(in));
|
||||||
|
vehicle.loadType = table.resolve(io::readU32(in));
|
||||||
|
vehicle.couplingParams = table.resolve(io::readU32(in));
|
||||||
|
vehicle.couplingRaw = table.resolve(io::readU32(in));
|
||||||
|
vehicle.offset = io::readF64(in);
|
||||||
|
vehicle.coupling = io::readI32(in);
|
||||||
|
if (vehicle.couplingRaw.empty()) {
|
||||||
|
if (!vehicle.couplingParams.empty()) {
|
||||||
|
vehicle.couplingRaw = std::to_string(vehicle.coupling) + "." + vehicle.couplingParams;
|
||||||
|
} else {
|
||||||
|
vehicle.couplingRaw = std::to_string(vehicle.coupling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vehicle.loadCount = io::readI32(in);
|
||||||
|
vehicle.velocity = io::readF32(in);
|
||||||
|
if (io::readU8(in) != 0) {
|
||||||
|
vehicle.destination = table.resolve(io::readU32(in));
|
||||||
|
}
|
||||||
|
if (io::readU8(in) != 0) {
|
||||||
|
vehicle.trainsetIndex = io::readU32(in);
|
||||||
|
}
|
||||||
|
return vehicle;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::RuntimeSoundSource readRuntimeSound(std::istream& in, const StringTable& table) {
|
||||||
|
runtime::RuntimeSoundSource sound;
|
||||||
|
sound.node = codec::readSlimNode(in, table, "sound");
|
||||||
|
sound.location = io::readVec3(in);
|
||||||
|
sound.wavFile = table.resolve(io::readU32(in));
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readTracChunkV2(std::istream& in, const StringTable& table, bake::RuntimeModule& module) {
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
module.scene.traction.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
module.scene.traction.push_back(readRuntimeTraction(in, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readMemcChunkV2(std::istream& in, const StringTable& table, bake::RuntimeModule& module) {
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
module.scene.memcells.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
module.scene.memcells.push_back(readRuntimeMemcell(in, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readLaunChunkV2(std::istream& in, const StringTable& table, bake::RuntimeModule& module) {
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
module.scene.eventLaunchers.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
module.scene.eventLaunchers.push_back(readRuntimeLauncher(in, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readDynmChunkV2(std::istream& in, const StringTable& table, bake::RuntimeModule& module) {
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
module.scene.dynamics.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
module.scene.dynamics.push_back(readRuntimeDynamic(in, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readSondChunkV2(std::istream& in, const StringTable& table, bake::RuntimeModule& module) {
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
module.scene.sounds.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
module.scene.sounds.push_back(readRuntimeSound(in, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeRuntimePowerSource(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::RuntimeTractionPowerSource& source) {
|
||||||
|
codec::writeSlimNode(out, table, source.node, "tractionpowersource");
|
||||||
|
io::writeVec3(out, source.position);
|
||||||
|
io::writeF32(out, source.nominalVoltage);
|
||||||
|
io::writeF32(out, source.voltageFrequency);
|
||||||
|
io::writeF64(out, source.internalResistanceLegacy);
|
||||||
|
io::writeF32(out, source.internalResistance);
|
||||||
|
io::writeF32(out, source.maxOutputCurrent);
|
||||||
|
io::writeF32(out, source.fastFuseTimeout);
|
||||||
|
io::writeF32(out, source.fastFuseRepetition);
|
||||||
|
io::writeF32(out, source.slowFuseTimeout);
|
||||||
|
io::writeU8(out, static_cast<std::uint8_t>(source.modifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void collectPowerSourceStrings(
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::RuntimeTractionPowerSource& source) {
|
||||||
|
table.intern(source.node.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<char> buildPwrsPayloadV2(
|
||||||
|
StringTable& table,
|
||||||
|
const std::vector<runtime::RuntimeTractionPowerSource>& sources) {
|
||||||
|
std::ostringstream out(std::ios::binary);
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(sources.size()));
|
||||||
|
for (const runtime::RuntimeTractionPowerSource& source : sources) {
|
||||||
|
writeRuntimePowerSource(out, table, source);
|
||||||
|
}
|
||||||
|
const std::string blob = out.str();
|
||||||
|
return {blob.begin(), blob.end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::RuntimeTractionPowerSource readRuntimePowerSource(
|
||||||
|
std::istream& in,
|
||||||
|
const StringTable& table) {
|
||||||
|
runtime::RuntimeTractionPowerSource source;
|
||||||
|
source.node = codec::readSlimNode(in, table, "tractionpowersource");
|
||||||
|
source.position = io::readVec3(in);
|
||||||
|
source.node.area.center = source.position;
|
||||||
|
source.nominalVoltage = io::readF32(in);
|
||||||
|
source.voltageFrequency = io::readF32(in);
|
||||||
|
source.internalResistanceLegacy = io::readF64(in);
|
||||||
|
source.internalResistance = io::readF32(in);
|
||||||
|
source.maxOutputCurrent = io::readF32(in);
|
||||||
|
source.fastFuseTimeout = io::readF32(in);
|
||||||
|
source.fastFuseRepetition = io::readF32(in);
|
||||||
|
source.slowFuseTimeout = io::readF32(in);
|
||||||
|
source.modifier = static_cast<runtime::PowerSourceModifier>(io::readU8(in));
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readPwrsChunkV2(std::istream& in, const StringTable& table, bake::RuntimeModule& module) {
|
||||||
|
const std::uint32_t count = io::readU32(in);
|
||||||
|
module.scene.powerSources.reserve(count);
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
module.scene.powerSources.push_back(readRuntimePowerSource(in, table));
|
||||||
|
}
|
||||||
|
}
|
||||||
737
eu07-parser/include/eu07/scene/binary/runtime_codec.hpp
Normal file
737
eu07-parser/include/eu07/scene/binary/runtime_codec.hpp
Normal file
@@ -0,0 +1,737 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// EU7B v5 — slim node, TERR terrain-lite, packed vertices, track tail enums.
|
||||||
|
|
||||||
|
#include <eu07/scene/binary/io.hpp>
|
||||||
|
#include <eu07/scene/binary/string_table.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/runtime/basic_node.hpp>
|
||||||
|
#include <eu07/scene/runtime/nodes.hpp>
|
||||||
|
#include <eu07/scene/runtime/types.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <istream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::binary::codec {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
inline constexpr float kLightingEps = 1e-4f;
|
||||||
|
|
||||||
|
inline constexpr std::uint8_t kNodeFlagHasName = 1u << 0;
|
||||||
|
inline constexpr std::uint8_t kNodeFlagHasRangeMin = 1u << 1;
|
||||||
|
inline constexpr std::uint8_t kNodeFlagHasRangeMax = 1u << 2;
|
||||||
|
inline constexpr std::uint8_t kNodeFlagHasBounds = 1u << 3;
|
||||||
|
inline constexpr std::uint8_t kNodeFlagHasGroup = 1u << 4;
|
||||||
|
inline constexpr std::uint8_t kNodeFlagHasTransform = 1u << 5;
|
||||||
|
inline constexpr std::uint8_t kNodeFlagNotVisible = 1u << 6;
|
||||||
|
|
||||||
|
inline constexpr std::uint8_t kTrackTailCustom = 255;
|
||||||
|
|
||||||
|
inline bool hasNonDefaultRangeMax(const double rangeSquaredMax) noexcept {
|
||||||
|
return rangeSquaredMax < std::numeric_limits<double>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool hasNonEmptyTransform(const runtime::TransformContext& transform) noexcept {
|
||||||
|
if (!transform.originStack.empty() || !transform.scaleStack.empty() || transform.groupStackDepth != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return transform.rotation.x != 0.0 || transform.rotation.y != 0.0 || transform.rotation.z != 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeTransformContext(std::ostream& out, const runtime::TransformContext& transform) {
|
||||||
|
if (transform.originStack.size() > 255 || transform.scaleStack.size() > 255) {
|
||||||
|
throw std::runtime_error("EU7B: zbyt gleboki stos transformacji");
|
||||||
|
}
|
||||||
|
io::writeU8(out, static_cast<std::uint8_t>(transform.originStack.size()));
|
||||||
|
for (const runtime::Vec3& offset : transform.originStack) {
|
||||||
|
io::writeVec3(out, offset);
|
||||||
|
}
|
||||||
|
io::writeU8(out, static_cast<std::uint8_t>(transform.scaleStack.size()));
|
||||||
|
for (const runtime::Vec3& scale : transform.scaleStack) {
|
||||||
|
io::writeVec3(out, scale);
|
||||||
|
}
|
||||||
|
io::writeVec3(out, transform.rotation);
|
||||||
|
io::writeU8(out, static_cast<std::uint8_t>(transform.groupStackDepth));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::TransformContext readTransformContext(std::istream& in) {
|
||||||
|
runtime::TransformContext transform;
|
||||||
|
const std::uint8_t originCount = io::readU8(in);
|
||||||
|
transform.originStack.reserve(originCount);
|
||||||
|
for (std::uint8_t i = 0; i < originCount; ++i) {
|
||||||
|
transform.originStack.push_back(io::readVec3(in));
|
||||||
|
}
|
||||||
|
const std::uint8_t scaleCount = io::readU8(in);
|
||||||
|
transform.scaleStack.reserve(scaleCount);
|
||||||
|
for (std::uint8_t i = 0; i < scaleCount; ++i) {
|
||||||
|
transform.scaleStack.push_back(io::readVec3(in));
|
||||||
|
}
|
||||||
|
transform.rotation = io::readVec3(in);
|
||||||
|
transform.groupStackDepth = io::readU8(in);
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeStringId(std::ostream& out, StringTable& table, const std::string& text) {
|
||||||
|
io::writeU32(out, table.intern(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::int16_t floatToSnorm16(const float value) {
|
||||||
|
const float clamped = std::clamp(value, -1.f, 1.f);
|
||||||
|
return static_cast<std::int16_t>(std::lround(clamped * 32767.f));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float snorm16ToFloat(const std::int16_t value) {
|
||||||
|
return static_cast<float>(value) * (1.f / 32767.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::uint16_t floatToHalf(const float value) {
|
||||||
|
std::uint32_t bits = 0;
|
||||||
|
std::memcpy(&bits, &value, sizeof(bits));
|
||||||
|
|
||||||
|
const std::uint32_t sign = (bits >> 16u) & 0x8000u;
|
||||||
|
const std::uint32_t fExp = (bits >> 23u) & 0xFFu;
|
||||||
|
std::uint32_t mantissa = bits & 0x7FFFFFu;
|
||||||
|
|
||||||
|
if (fExp == 0xFFu) {
|
||||||
|
return static_cast<std::uint16_t>(sign | 0x7C00u | (mantissa ? 0x0200u : 0u));
|
||||||
|
}
|
||||||
|
if (fExp == 0) {
|
||||||
|
return static_cast<std::uint16_t>(sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t halfExp = static_cast<std::int32_t>(fExp) - 127 + 15;
|
||||||
|
if (halfExp >= 31) {
|
||||||
|
return static_cast<std::uint16_t>(sign | 0x7C00u);
|
||||||
|
}
|
||||||
|
if (halfExp <= 0) {
|
||||||
|
if (halfExp < -10) {
|
||||||
|
return static_cast<std::uint16_t>(sign);
|
||||||
|
}
|
||||||
|
mantissa = (mantissa | 0x800000u) >> static_cast<std::uint32_t>(1 - halfExp);
|
||||||
|
return static_cast<std::uint16_t>(sign | (mantissa >> 13u));
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<std::uint16_t>(
|
||||||
|
sign | (static_cast<std::uint32_t>(halfExp) << 10u) | ((mantissa + 0x1000u) >> 13u));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float halfToFloat(const std::uint16_t value) {
|
||||||
|
const std::uint32_t sign = static_cast<std::uint32_t>(value & 0x8000u) << 16u;
|
||||||
|
const std::uint32_t exponent = (value & 0x7C00u) >> 10u;
|
||||||
|
std::uint32_t mantissa = value & 0x03FFu;
|
||||||
|
|
||||||
|
std::uint32_t bits = 0;
|
||||||
|
if (exponent == 0) {
|
||||||
|
if (mantissa == 0) {
|
||||||
|
bits = sign;
|
||||||
|
} else {
|
||||||
|
std::uint32_t exp = 127 - 15 - 1;
|
||||||
|
while ((mantissa & 0x400u) == 0) {
|
||||||
|
mantissa <<= 1u;
|
||||||
|
--exp;
|
||||||
|
}
|
||||||
|
mantissa &= 0x3FFu;
|
||||||
|
bits = sign | (exp << 23u) | (mantissa << 13u);
|
||||||
|
}
|
||||||
|
} else if (exponent == 31) {
|
||||||
|
bits = sign | 0x7F800000u | (mantissa << 13u);
|
||||||
|
} else {
|
||||||
|
bits = sign | ((exponent + 127 - 15) << 23u) | (mantissa << 13u);
|
||||||
|
}
|
||||||
|
|
||||||
|
float result = 0.f;
|
||||||
|
std::memcpy(&result, &bits, sizeof(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeHalf(std::ostream& out, const float value) {
|
||||||
|
io::writeU16(out, floatToHalf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float readHalf(std::istream& in) {
|
||||||
|
return halfToFloat(io::readU16(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
inline void writeStringId(std::ostream& out, StringTable& table, const std::string& text) {
|
||||||
|
detail::writeStringId(out, table, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeSlimNode(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const runtime::BasicNode& node,
|
||||||
|
const std::string_view impliedType) {
|
||||||
|
std::uint8_t flags = 0;
|
||||||
|
if (!node.name.empty()) {
|
||||||
|
flags |= detail::kNodeFlagHasName;
|
||||||
|
}
|
||||||
|
if (node.rangeSquaredMin != 0.0) {
|
||||||
|
flags |= detail::kNodeFlagHasRangeMin;
|
||||||
|
}
|
||||||
|
if (detail::hasNonDefaultRangeMax(node.rangeSquaredMax)) {
|
||||||
|
flags |= detail::kNodeFlagHasRangeMax;
|
||||||
|
}
|
||||||
|
if (node.area.radius >= 0.f) {
|
||||||
|
flags |= detail::kNodeFlagHasBounds;
|
||||||
|
}
|
||||||
|
if (node.groupValid) {
|
||||||
|
flags |= detail::kNodeFlagHasGroup;
|
||||||
|
}
|
||||||
|
if (detail::hasNonEmptyTransform(node.transform)) {
|
||||||
|
flags |= detail::kNodeFlagHasTransform;
|
||||||
|
}
|
||||||
|
if (!node.visible) {
|
||||||
|
flags |= detail::kNodeFlagNotVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
io::writeU8(out, flags);
|
||||||
|
if ((flags & detail::kNodeFlagHasName) != 0) {
|
||||||
|
detail::writeStringId(out, table, node.name);
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasRangeMin) != 0) {
|
||||||
|
io::writeF64(out, node.rangeSquaredMin);
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasRangeMax) != 0) {
|
||||||
|
io::writeF64(out, node.rangeSquaredMax);
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasBounds) != 0) {
|
||||||
|
io::writeVec3(out, node.area.center);
|
||||||
|
io::writeF32(out, node.area.radius);
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasGroup) != 0) {
|
||||||
|
io::writeU32(out, static_cast<std::uint32_t>(node.groupHandle));
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasTransform) != 0) {
|
||||||
|
detail::writeTransformContext(out, node.transform);
|
||||||
|
}
|
||||||
|
(void)impliedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::BasicNode readSlimNode(std::istream& in, const StringTable& table, const std::string_view impliedType) {
|
||||||
|
runtime::BasicNode node;
|
||||||
|
node.nodeType = std::string(impliedType);
|
||||||
|
const std::uint8_t flags = io::readU8(in);
|
||||||
|
if ((flags & detail::kNodeFlagHasName) != 0) {
|
||||||
|
node.name = table.resolve(io::readU32(in));
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasRangeMin) != 0) {
|
||||||
|
node.rangeSquaredMin = io::readF64(in);
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasRangeMax) != 0) {
|
||||||
|
node.rangeSquaredMax = io::readF64(in);
|
||||||
|
} else {
|
||||||
|
node.rangeSquaredMax = std::numeric_limits<double>::max();
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasBounds) != 0) {
|
||||||
|
node.area.center = io::readVec3(in);
|
||||||
|
node.area.radius = io::readF32(in);
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasGroup) != 0) {
|
||||||
|
node.groupValid = true;
|
||||||
|
node.groupHandle = io::readU32(in);
|
||||||
|
}
|
||||||
|
if ((flags & detail::kNodeFlagHasTransform) != 0) {
|
||||||
|
node.transform = detail::readTransformContext(in);
|
||||||
|
}
|
||||||
|
node.visible = (flags & detail::kNodeFlagNotVisible) == 0;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isDefaultLighting(const runtime::LightingData& lighting) noexcept {
|
||||||
|
const auto near = [&](const float a, const float b) noexcept {
|
||||||
|
return std::abs(a - b) <= detail::kLightingEps;
|
||||||
|
};
|
||||||
|
return near(lighting.diffuse.x, 0.8f) && near(lighting.diffuse.y, 0.8f) &&
|
||||||
|
near(lighting.diffuse.z, 0.8f) && near(lighting.diffuse.w, 1.f) &&
|
||||||
|
near(lighting.ambient.x, 0.2f) && near(lighting.ambient.y, 0.2f) &&
|
||||||
|
near(lighting.ambient.z, 0.2f) && near(lighting.ambient.w, 1.f) &&
|
||||||
|
near(lighting.specular.x, 0.f) && near(lighting.specular.y, 0.f) &&
|
||||||
|
near(lighting.specular.z, 0.f) && near(lighting.specular.w, 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeLightingBlock(std::ostream& out, const runtime::LightingData& lighting) {
|
||||||
|
io::writeF32(out, lighting.diffuse.x);
|
||||||
|
io::writeF32(out, lighting.diffuse.y);
|
||||||
|
io::writeF32(out, lighting.diffuse.z);
|
||||||
|
io::writeF32(out, lighting.diffuse.w);
|
||||||
|
io::writeF32(out, lighting.ambient.x);
|
||||||
|
io::writeF32(out, lighting.ambient.y);
|
||||||
|
io::writeF32(out, lighting.ambient.z);
|
||||||
|
io::writeF32(out, lighting.ambient.w);
|
||||||
|
io::writeF32(out, lighting.specular.x);
|
||||||
|
io::writeF32(out, lighting.specular.y);
|
||||||
|
io::writeF32(out, lighting.specular.z);
|
||||||
|
io::writeF32(out, lighting.specular.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::LightingData readLightingBlock(std::istream& in) {
|
||||||
|
runtime::LightingData lighting;
|
||||||
|
lighting.diffuse.x = io::readF32(in);
|
||||||
|
lighting.diffuse.y = io::readF32(in);
|
||||||
|
lighting.diffuse.z = io::readF32(in);
|
||||||
|
lighting.diffuse.w = io::readF32(in);
|
||||||
|
lighting.ambient.x = io::readF32(in);
|
||||||
|
lighting.ambient.y = io::readF32(in);
|
||||||
|
lighting.ambient.z = io::readF32(in);
|
||||||
|
lighting.ambient.w = io::readF32(in);
|
||||||
|
lighting.specular.x = io::readF32(in);
|
||||||
|
lighting.specular.y = io::readF32(in);
|
||||||
|
lighting.specular.z = io::readF32(in);
|
||||||
|
lighting.specular.w = io::readF32(in);
|
||||||
|
return lighting;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeLightingTagged(std::ostream& out, const runtime::LightingData& lighting) {
|
||||||
|
io::writeU8(out, isDefaultLighting(lighting) ? 0 : 1);
|
||||||
|
if (!isDefaultLighting(lighting)) {
|
||||||
|
writeLightingBlock(out, lighting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::LightingData readLightingTagged(std::istream& in) {
|
||||||
|
if (io::readU8(in) == 0) {
|
||||||
|
return runtime::LightingData{};
|
||||||
|
}
|
||||||
|
return readLightingBlock(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writePackedWorldVertex(std::ostream& out, const runtime::WorldVertex& vertex) {
|
||||||
|
io::writeVec3(out, vertex.position);
|
||||||
|
io::writeI16(out, detail::floatToSnorm16(static_cast<float>(vertex.normal.x)));
|
||||||
|
io::writeI16(out, detail::floatToSnorm16(static_cast<float>(vertex.normal.y)));
|
||||||
|
io::writeI16(out, detail::floatToSnorm16(static_cast<float>(vertex.normal.z)));
|
||||||
|
detail::writeHalf(out, static_cast<float>(vertex.u));
|
||||||
|
detail::writeHalf(out, static_cast<float>(vertex.v));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline runtime::WorldVertex readPackedWorldVertex(std::istream& in) {
|
||||||
|
runtime::WorldVertex vertex;
|
||||||
|
vertex.position = io::readVec3(in);
|
||||||
|
vertex.normal.x = detail::snorm16ToFloat(io::readI16(in));
|
||||||
|
vertex.normal.y = detail::snorm16ToFloat(io::readI16(in));
|
||||||
|
vertex.normal.z = detail::snorm16ToFloat(io::readI16(in));
|
||||||
|
vertex.u = detail::readHalf(in);
|
||||||
|
vertex.v = detail::readHalf(in);
|
||||||
|
return vertex;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint8_t trackTailKeywordCode(const std::string_view key) noexcept {
|
||||||
|
if (isKeyword(key, "event0")) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "eventall0")) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "event1")) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "eventall1")) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "event2")) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "eventall2")) {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "velocity")) {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "isolated")) {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "overhead")) {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "vradius")) {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "railprofile")) {
|
||||||
|
return 11;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "trackbed")) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "friction")) {
|
||||||
|
return 13;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "fouling1")) {
|
||||||
|
return 14;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "fouling2")) {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "sleepermodel")) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "angle1")) {
|
||||||
|
return 17;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "angle2")) {
|
||||||
|
return 18;
|
||||||
|
}
|
||||||
|
return detail::kTrackTailCustom;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string trackTailKeywordName(const std::uint8_t code) {
|
||||||
|
switch (code) {
|
||||||
|
case 1:
|
||||||
|
return "event0";
|
||||||
|
case 2:
|
||||||
|
return "eventall0";
|
||||||
|
case 3:
|
||||||
|
return "event1";
|
||||||
|
case 4:
|
||||||
|
return "eventall1";
|
||||||
|
case 5:
|
||||||
|
return "event2";
|
||||||
|
case 6:
|
||||||
|
return "eventall2";
|
||||||
|
case 7:
|
||||||
|
return "velocity";
|
||||||
|
case 8:
|
||||||
|
return "isolated";
|
||||||
|
case 9:
|
||||||
|
return "overhead";
|
||||||
|
case 10:
|
||||||
|
return "vradius";
|
||||||
|
case 11:
|
||||||
|
return "railprofile";
|
||||||
|
case 12:
|
||||||
|
return "trackbed";
|
||||||
|
case 13:
|
||||||
|
return "friction";
|
||||||
|
case 14:
|
||||||
|
return "fouling1";
|
||||||
|
case 15:
|
||||||
|
return "fouling2";
|
||||||
|
case 16:
|
||||||
|
return "sleepermodel";
|
||||||
|
case 17:
|
||||||
|
return "angle1";
|
||||||
|
case 18:
|
||||||
|
return "angle2";
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void writeTrackTailEntry(
|
||||||
|
std::ostream& out,
|
||||||
|
StringTable& table,
|
||||||
|
const std::string& key,
|
||||||
|
const std::string& value) {
|
||||||
|
const std::uint8_t code = trackTailKeywordCode(key);
|
||||||
|
io::writeU8(out, code);
|
||||||
|
if (code == detail::kTrackTailCustom) {
|
||||||
|
detail::writeStringId(out, table, key);
|
||||||
|
}
|
||||||
|
detail::writeStringId(out, table, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint8_t meshSubtypeCode(const std::string_view type) noexcept {
|
||||||
|
if (isKeyword(type, "triangle_strip")) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (isKeyword(type, "triangle_fan")) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string_view meshSubtypeName(const std::uint8_t code) noexcept {
|
||||||
|
switch (code) {
|
||||||
|
case 1:
|
||||||
|
return "triangle_strip";
|
||||||
|
case 2:
|
||||||
|
return "triangle_fan";
|
||||||
|
default:
|
||||||
|
return "triangles";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint8_t lineSubtypeCode(const std::string_view type) noexcept {
|
||||||
|
if (isKeyword(type, "line_strip")) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (isKeyword(type, "line_loop")) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string_view lineSubtypeName(const std::uint8_t code) noexcept {
|
||||||
|
switch (code) {
|
||||||
|
case 1:
|
||||||
|
return "line_strip";
|
||||||
|
case 2:
|
||||||
|
return "line_loop";
|
||||||
|
default:
|
||||||
|
return "lines";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::pair<std::string, std::string> readTrackTailEntry(std::istream& in, const StringTable& table) {
|
||||||
|
const std::uint8_t code = io::readU8(in);
|
||||||
|
std::string key;
|
||||||
|
if (code == detail::kTrackTailCustom) {
|
||||||
|
key = table.resolve(io::readU32(in));
|
||||||
|
} else {
|
||||||
|
key = trackTailKeywordName(code);
|
||||||
|
}
|
||||||
|
return {std::move(key), table.resolve(io::readU32(in))};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TERR: jednorodny teren trójkątowy (materiał/lighting w nagłówku chunka) ---
|
||||||
|
|
||||||
|
inline constexpr std::uint8_t kTerrFlagTranslucent = 1u << 0;
|
||||||
|
inline constexpr std::uint8_t kTerrFlagNonDefaultLighting = 1u << 1;
|
||||||
|
inline constexpr std::uint8_t kTerrFlagBatched = 1u << 2;
|
||||||
|
inline constexpr std::size_t kTerrVertsPerRecord = 3;
|
||||||
|
|
||||||
|
// basic_region::section() w maszyna-fresh/scene/scene.cpp
|
||||||
|
inline constexpr std::int32_t kEu07SectionSize = 1000;
|
||||||
|
inline constexpr std::int32_t kEu07RegionSideSectionCount = 500;
|
||||||
|
|
||||||
|
struct TerrSectionKey {
|
||||||
|
std::int32_t x = 0;
|
||||||
|
std::int32_t z = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator==(const TerrSectionKey& other) const noexcept {
|
||||||
|
return x == other.x && z == other.z;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TerrSectionKeyHash {
|
||||||
|
[[nodiscard]] std::size_t operator()(const TerrSectionKey& key) const noexcept {
|
||||||
|
return static_cast<std::size_t>(key.x) * 0x9E3779B9u ^
|
||||||
|
static_cast<std::size_t>(key.z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TerrSectionBatch {
|
||||||
|
TerrSectionKey section{};
|
||||||
|
std::vector<runtime::WorldVertex> vertices;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline runtime::Vec3 terrTriangleCentroid(
|
||||||
|
const std::array<runtime::WorldVertex, kTerrVertsPerRecord>& triangle) noexcept {
|
||||||
|
runtime::Vec3 center{};
|
||||||
|
for (const runtime::WorldVertex& vertex : triangle) {
|
||||||
|
center.x += vertex.position.x;
|
||||||
|
center.y += vertex.position.y;
|
||||||
|
center.z += vertex.position.z;
|
||||||
|
}
|
||||||
|
const double inv = 1.0 / static_cast<double>(kTerrVertsPerRecord);
|
||||||
|
center.x *= inv;
|
||||||
|
center.y *= inv;
|
||||||
|
center.z *= inv;
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TerrSectionKey terrSectionKey(const runtime::Vec3& centroid) noexcept {
|
||||||
|
TerrSectionKey key;
|
||||||
|
key.x = static_cast<std::int32_t>(std::floor(
|
||||||
|
centroid.x / static_cast<double>(kEu07SectionSize) +
|
||||||
|
static_cast<double>(kEu07RegionSideSectionCount) / 2.0));
|
||||||
|
key.z = static_cast<std::int32_t>(std::floor(
|
||||||
|
centroid.z / static_cast<double>(kEu07SectionSize) +
|
||||||
|
static_cast<double>(kEu07RegionSideSectionCount) / 2.0));
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool nearZeroVec3(const runtime::Vec3& v, const double eps = 1e-9) noexcept {
|
||||||
|
return std::abs(v.x) <= eps && std::abs(v.y) <= eps && std::abs(v.z) <= eps;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool lightingEqual(
|
||||||
|
const runtime::LightingData& a,
|
||||||
|
const runtime::LightingData& b) noexcept {
|
||||||
|
const auto near = [&](const float x, const float y) noexcept {
|
||||||
|
return std::abs(x - y) <= detail::kLightingEps;
|
||||||
|
};
|
||||||
|
return near(a.diffuse.x, b.diffuse.x) && near(a.diffuse.y, b.diffuse.y) &&
|
||||||
|
near(a.diffuse.z, b.diffuse.z) && near(a.diffuse.w, b.diffuse.w) &&
|
||||||
|
near(a.ambient.x, b.ambient.x) && near(a.ambient.y, b.ambient.y) &&
|
||||||
|
near(a.ambient.z, b.ambient.z) && near(a.ambient.w, b.ambient.w) &&
|
||||||
|
near(a.specular.x, b.specular.x) && near(a.specular.y, b.specular.y) &&
|
||||||
|
near(a.specular.z, b.specular.z) && near(a.specular.w, b.specular.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool terrMetadataMatches(
|
||||||
|
const runtime::RuntimeShapeNode& a,
|
||||||
|
const runtime::RuntimeShapeNode& b) noexcept {
|
||||||
|
return a.materialPath == b.materialPath && a.translucent == b.translucent &&
|
||||||
|
lightingEqual(a.lighting, b.lighting);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isTerrTriangleLite(const runtime::RuntimeShapeNode& shape) noexcept {
|
||||||
|
const std::string_view nodeType =
|
||||||
|
shape.node.nodeType.empty() ? std::string_view{"triangles"} : std::string_view{shape.node.nodeType};
|
||||||
|
if (meshSubtypeCode(nodeType) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (shape.vertices.size() != kTerrVertsPerRecord) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!nearZeroVec3(shape.origin)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!shape.node.name.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (shape.node.rangeSquaredMin != 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (detail::hasNonDefaultRangeMax(shape.node.rangeSquaredMax)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!shape.node.visible) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (shape.node.groupValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (detail::hasNonEmptyTransform(shape.node.transform)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool canUseTerrEncoding(
|
||||||
|
const std::vector<runtime::RuntimeShapeNode>& shapes) noexcept {
|
||||||
|
if (shapes.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isTerrTriangleLite(shapes.front())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (std::size_t i = 1; i < shapes.size(); ++i) {
|
||||||
|
if (!isTerrTriangleLite(shapes[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!terrMetadataMatches(shapes.front(), shapes[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint8_t terrFlagsForShape(
|
||||||
|
const runtime::RuntimeShapeNode& shape,
|
||||||
|
const bool batched = false) noexcept {
|
||||||
|
std::uint8_t flags = 0;
|
||||||
|
if (shape.translucent) {
|
||||||
|
flags |= kTerrFlagTranslucent;
|
||||||
|
}
|
||||||
|
if (!isDefaultLighting(shape.lighting)) {
|
||||||
|
flags |= kTerrFlagNonDefaultLighting;
|
||||||
|
}
|
||||||
|
if (batched) {
|
||||||
|
flags |= kTerrFlagBatched;
|
||||||
|
}
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<TerrSectionBatch> groupTerrShapesBySection(
|
||||||
|
const std::vector<runtime::RuntimeShapeNode>& shapes) {
|
||||||
|
std::unordered_map<TerrSectionKey, std::size_t, TerrSectionKeyHash> batchIndex;
|
||||||
|
std::vector<TerrSectionBatch> batches;
|
||||||
|
batches.reserve(256);
|
||||||
|
|
||||||
|
for (const runtime::RuntimeShapeNode& shape : shapes) {
|
||||||
|
if (shape.vertices.size() != kTerrVertsPerRecord) {
|
||||||
|
throw std::runtime_error("EU7B TERR: oczekiwano 3 wierzcholkow na trojkat");
|
||||||
|
}
|
||||||
|
std::array<runtime::WorldVertex, kTerrVertsPerRecord> triangle{};
|
||||||
|
for (std::size_t i = 0; i < kTerrVertsPerRecord; ++i) {
|
||||||
|
triangle[i] = shape.vertices[i];
|
||||||
|
}
|
||||||
|
const TerrSectionKey key = terrSectionKey(terrTriangleCentroid(triangle));
|
||||||
|
const auto [it, inserted] = batchIndex.emplace(key, batches.size());
|
||||||
|
if (inserted) {
|
||||||
|
TerrSectionBatch batch;
|
||||||
|
batch.section = key;
|
||||||
|
batches.push_back(std::move(batch));
|
||||||
|
}
|
||||||
|
TerrSectionBatch& batch = batches[it->second];
|
||||||
|
batch.vertices.insert(
|
||||||
|
batch.vertices.end(),
|
||||||
|
shape.vertices.begin(),
|
||||||
|
shape.vertices.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(
|
||||||
|
batches.begin(),
|
||||||
|
batches.end(),
|
||||||
|
[](const TerrSectionBatch& a, const TerrSectionBatch& b) noexcept {
|
||||||
|
if (a.section.z != b.section.z) {
|
||||||
|
return a.section.z < b.section.z;
|
||||||
|
}
|
||||||
|
return a.section.x < b.section.x;
|
||||||
|
});
|
||||||
|
return batches;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ModelSectionBatch {
|
||||||
|
TerrSectionKey section{};
|
||||||
|
std::vector<runtime::RuntimeModelInstance> models;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline TerrSectionKey clampTerrSectionKey(const TerrSectionKey key) noexcept {
|
||||||
|
TerrSectionKey clamped = key;
|
||||||
|
clamped.x = std::clamp(
|
||||||
|
clamped.x,
|
||||||
|
0,
|
||||||
|
static_cast<std::int32_t>(kEu07RegionSideSectionCount) - 1);
|
||||||
|
clamped.z = std::clamp(
|
||||||
|
clamped.z,
|
||||||
|
0,
|
||||||
|
static_cast<std::int32_t>(kEu07RegionSideSectionCount) - 1);
|
||||||
|
return clamped;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<ModelSectionBatch> groupModelsBySection(
|
||||||
|
const std::vector<runtime::RuntimeModelInstance>& models) {
|
||||||
|
std::unordered_map<TerrSectionKey, std::size_t, TerrSectionKeyHash> batchIndex;
|
||||||
|
std::vector<ModelSectionBatch> batches;
|
||||||
|
batches.reserve(256);
|
||||||
|
|
||||||
|
for (const runtime::RuntimeModelInstance& model : models) {
|
||||||
|
const TerrSectionKey key = clampTerrSectionKey(terrSectionKey(model.location));
|
||||||
|
const auto [it, inserted] = batchIndex.emplace(key, batches.size());
|
||||||
|
if (inserted) {
|
||||||
|
ModelSectionBatch batch;
|
||||||
|
batch.section = key;
|
||||||
|
batches.push_back(std::move(batch));
|
||||||
|
}
|
||||||
|
batches[it->second].models.push_back(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(
|
||||||
|
batches.begin(),
|
||||||
|
batches.end(),
|
||||||
|
[](const ModelSectionBatch& a, const ModelSectionBatch& b) noexcept {
|
||||||
|
if (a.section.z != b.section.z) {
|
||||||
|
return a.section.z < b.section.z;
|
||||||
|
}
|
||||||
|
return a.section.x < b.section.x;
|
||||||
|
});
|
||||||
|
return batches;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::binary::codec
|
||||||
1058
eu07-parser/include/eu07/scene/binary/runtime_module.hpp
Normal file
1058
eu07-parser/include/eu07/scene/binary/runtime_module.hpp
Normal file
File diff suppressed because it is too large
Load Diff
133
eu07-parser/include/eu07/scene/binary/selftest.hpp
Normal file
133
eu07-parser/include/eu07/scene/binary/selftest.hpp
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Self-test: RuntimeModule → tokeny → processScene → bake → porównanie liczników.
|
||||||
|
|
||||||
|
#include <eu07/scene/bake/module.hpp>
|
||||||
|
#include <eu07/scene/binary/detokenize.hpp>
|
||||||
|
#include <eu07/scene/processor.hpp>
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::binary::selftest {
|
||||||
|
|
||||||
|
struct ModuleSnapshot {
|
||||||
|
std::size_t includes = 0;
|
||||||
|
std::size_t tracks = 0;
|
||||||
|
std::size_t traction = 0;
|
||||||
|
std::size_t powerSources = 0;
|
||||||
|
std::size_t shapes = 0;
|
||||||
|
std::size_t lines = 0;
|
||||||
|
std::size_t models = 0;
|
||||||
|
std::size_t memcells = 0;
|
||||||
|
std::size_t eventLaunchers = 0;
|
||||||
|
std::size_t dynamics = 0;
|
||||||
|
std::size_t sounds = 0;
|
||||||
|
std::size_t trainsets = 0;
|
||||||
|
std::size_t events = 0;
|
||||||
|
std::uint32_t firstInit = 0;
|
||||||
|
std::size_t meshVertices = 0;
|
||||||
|
std::size_t lineVertices = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline ModuleSnapshot snapshotModule(const bake::RuntimeModule& module) {
|
||||||
|
ModuleSnapshot snap;
|
||||||
|
snap.includes = module.includes.size();
|
||||||
|
snap.tracks = module.scene.tracks.size();
|
||||||
|
snap.traction = module.scene.traction.size();
|
||||||
|
snap.powerSources = module.scene.powerSources.size();
|
||||||
|
snap.shapes = module.scene.shapes.size();
|
||||||
|
snap.lines = module.scene.lines.size();
|
||||||
|
snap.models = module.scene.models.size();
|
||||||
|
snap.memcells = module.scene.memcells.size();
|
||||||
|
snap.eventLaunchers = module.scene.eventLaunchers.size();
|
||||||
|
snap.dynamics = module.scene.dynamics.size();
|
||||||
|
snap.sounds = module.scene.sounds.size();
|
||||||
|
snap.trainsets = module.scene.trainsets.size();
|
||||||
|
snap.events = module.scene.events.size();
|
||||||
|
snap.firstInit = module.scene.firstInitCount;
|
||||||
|
for (const runtime::RuntimeShapeNode& shape : module.scene.shapes) {
|
||||||
|
snap.meshVertices += shape.vertices.size();
|
||||||
|
}
|
||||||
|
for (const runtime::RuntimeLinesNode& line : module.scene.lines) {
|
||||||
|
snap.lineVertices += line.vertices.size();
|
||||||
|
}
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string diffSnapshots(
|
||||||
|
const ModuleSnapshot& expected,
|
||||||
|
const ModuleSnapshot& actual) {
|
||||||
|
std::string report;
|
||||||
|
const auto field = [&](const char* label, const std::size_t a, const std::size_t b) {
|
||||||
|
if (a != b) {
|
||||||
|
report += std::format(" {}: {} -> {}\n", label, a, b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
field("include", expected.includes, actual.includes);
|
||||||
|
field("track", expected.tracks, actual.tracks);
|
||||||
|
field("traction", expected.traction, actual.traction);
|
||||||
|
field("power", expected.powerSources, actual.powerSources);
|
||||||
|
field("mesh", expected.shapes, actual.shapes);
|
||||||
|
field("mesh_vert", expected.meshVertices, actual.meshVertices);
|
||||||
|
field("line", expected.lines, actual.lines);
|
||||||
|
field("line_vert", expected.lineVertices, actual.lineVertices);
|
||||||
|
field("model", expected.models, actual.models);
|
||||||
|
field("memcell", expected.memcells, actual.memcells);
|
||||||
|
field("launcher", expected.eventLaunchers, actual.eventLaunchers);
|
||||||
|
field("dynamic", expected.dynamics, actual.dynamics);
|
||||||
|
field("sound", expected.sounds, actual.sounds);
|
||||||
|
field("trainset", expected.trainsets, actual.trainsets);
|
||||||
|
field("event", expected.events, actual.events);
|
||||||
|
field("firstInit", expected.firstInit, actual.firstInit);
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool snapshotsEqual(
|
||||||
|
const ModuleSnapshot& expected,
|
||||||
|
const ModuleSnapshot& actual) {
|
||||||
|
return expected.includes == actual.includes && expected.tracks == actual.tracks &&
|
||||||
|
expected.traction == actual.traction && expected.powerSources == actual.powerSources &&
|
||||||
|
expected.shapes == actual.shapes && expected.meshVertices == actual.meshVertices &&
|
||||||
|
expected.lines == actual.lines && expected.lineVertices == actual.lineVertices &&
|
||||||
|
expected.models == actual.models && expected.memcells == actual.memcells &&
|
||||||
|
expected.eventLaunchers == actual.eventLaunchers &&
|
||||||
|
expected.dynamics == actual.dynamics && expected.sounds == actual.sounds &&
|
||||||
|
expected.trainsets == actual.trainsets && expected.events == actual.events &&
|
||||||
|
expected.firstInit == actual.firstInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RoundTripResult {
|
||||||
|
bool passed = false;
|
||||||
|
ModuleSnapshot original;
|
||||||
|
ModuleSnapshot roundtrip;
|
||||||
|
std::size_t tokenCount = 0;
|
||||||
|
std::size_t unknownTokens = 0;
|
||||||
|
std::string diff;
|
||||||
|
std::vector<SourceToken> tokens;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline RoundTripResult runRoundTrip(const bake::RuntimeModule& module) {
|
||||||
|
RoundTripResult result;
|
||||||
|
result.original = snapshotModule(module);
|
||||||
|
result.tokens = detokenize::runtimeModuleToTokens(module);
|
||||||
|
result.tokenCount = result.tokens.size();
|
||||||
|
|
||||||
|
eu07::ParseResult parsed;
|
||||||
|
parsed.tokens = result.tokens;
|
||||||
|
|
||||||
|
SceneProcessOptions options;
|
||||||
|
options.expandIncludes = false;
|
||||||
|
const SceneProcessResult processed = processScene(parsed, {}, options);
|
||||||
|
|
||||||
|
result.unknownTokens = processed.document.unknown.size();
|
||||||
|
const bake::RuntimeModule rebaked = bake::bakeModule(processed.document);
|
||||||
|
result.roundtrip = snapshotModule(rebaked);
|
||||||
|
|
||||||
|
result.diff = diffSnapshots(result.original, result.roundtrip);
|
||||||
|
result.passed = snapshotsEqual(result.original, result.roundtrip) && result.unknownTokens == 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::binary::selftest
|
||||||
75
eu07-parser/include/eu07/scene/binary/string_table.hpp
Normal file
75
eu07-parser/include/eu07/scene/binary/string_table.hpp
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::binary {
|
||||||
|
|
||||||
|
class StringTable {
|
||||||
|
public:
|
||||||
|
static constexpr std::uint32_t kEmpty = 0xFFFFFFFFu;
|
||||||
|
|
||||||
|
StringTable() = default;
|
||||||
|
StringTable(const StringTable&) = delete;
|
||||||
|
StringTable& operator=(const StringTable&) = delete;
|
||||||
|
StringTable(StringTable&&) noexcept = default;
|
||||||
|
StringTable& operator=(StringTable&&) noexcept = default;
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint32_t intern(const std::string& text) {
|
||||||
|
if (text.empty()) {
|
||||||
|
return kEmpty;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::shared_lock read_lock(*mutex_);
|
||||||
|
if (const auto found = index_.find(text); found != index_.end()) {
|
||||||
|
return found->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::unique_lock write_lock(*mutex_);
|
||||||
|
if (const auto found = index_.find(text); found != index_.end()) {
|
||||||
|
return found->second;
|
||||||
|
}
|
||||||
|
const std::uint32_t id = static_cast<std::uint32_t>(strings_.size());
|
||||||
|
strings_.push_back(text);
|
||||||
|
index_.emplace(strings_.back(), id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::string& resolve(const std::uint32_t id) const {
|
||||||
|
static const std::string empty;
|
||||||
|
if (id == kEmpty || id >= strings_.size()) {
|
||||||
|
return empty;
|
||||||
|
}
|
||||||
|
return strings_[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<std::string>& strings() const noexcept {
|
||||||
|
return strings_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(std::vector<std::string> strings) {
|
||||||
|
strings_ = std::move(strings);
|
||||||
|
index_.clear();
|
||||||
|
index_.reserve(strings_.size());
|
||||||
|
for (std::uint32_t i = 0; i < strings_.size(); ++i) {
|
||||||
|
index_.emplace(strings_[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mergeFrom(const StringTable& other) {
|
||||||
|
for (const std::string& text : other.strings()) {
|
||||||
|
intern(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<std::shared_mutex> mutex_ { std::make_unique<std::shared_mutex>() };
|
||||||
|
std::vector<std::string> strings_;
|
||||||
|
std::unordered_map<std::string, std::uint32_t> index_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::scene::binary
|
||||||
112
eu07-parser/include/eu07/scene/boundaries.hpp
Normal file
112
eu07-parser/include/eu07/scene/boundaries.hpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::endgroup {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "endgroup")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SourceToken endToken = stream.consume();
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = endToken.sourceLine;
|
||||||
|
block.tokens.push_back(endToken);
|
||||||
|
ctx.scratch.closeGroup();
|
||||||
|
ctx.document.group.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::endgroup
|
||||||
|
|
||||||
|
namespace eu07::scene::endorigin {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "endorigin")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SourceToken endToken = stream.consume();
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = endToken.sourceLine;
|
||||||
|
block.tokens.push_back(endToken);
|
||||||
|
ctx.scratch.popOrigin();
|
||||||
|
ctx.document.origin.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::endorigin
|
||||||
|
|
||||||
|
namespace eu07::scene::endscale {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "endscale")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SourceToken endToken = stream.consume();
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = endToken.sourceLine;
|
||||||
|
block.tokens.push_back(endToken);
|
||||||
|
ctx.scratch.popScale();
|
||||||
|
ctx.document.scale.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::endscale
|
||||||
|
|
||||||
|
namespace eu07::scene::assignment {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "assignment")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.consume();
|
||||||
|
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
while (!stream.empty() && !isKeyword(stream.peek().value, "endassignment")) {
|
||||||
|
std::string key;
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, key) || !node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ctx.scratch.trainset.isOpen) {
|
||||||
|
ctx.scratch.trainset.assignment.emplace_back(std::move(key), std::move(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream.empty() && isKeyword(stream.peek().value, "endassignment")) {
|
||||||
|
stream.consume();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::assignment
|
||||||
|
|
||||||
|
namespace eu07::scene::endtrainset {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "endtrainset")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SourceToken endToken = stream.consume();
|
||||||
|
|
||||||
|
if (ctx.scratch.trainset.isOpen && ctx.scratch.trainset.docIndex) {
|
||||||
|
ctx.document.trainset[*ctx.scratch.trainset.docIndex].tokens.push_back(endToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.scratch.closeTrainset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::endtrainset
|
||||||
35
eu07-parser/include/eu07/scene/camera.hpp
Normal file
35
eu07-parser/include/eu07/scene/camera.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_camera
|
||||||
|
// camera position rotation index endcamera
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::camera {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "camera")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
while (!session.empty()) {
|
||||||
|
if (session.at("endcamera")) {
|
||||||
|
session.take();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.document.camera.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::camera
|
||||||
35
eu07-parser/include/eu07/scene/config.hpp
Normal file
35
eu07-parser/include/eu07/scene/config.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_config
|
||||||
|
// config settings endconfig
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::config {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "config")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
while (!session.empty()) {
|
||||||
|
if (session.at("endconfig")) {
|
||||||
|
session.take();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.document.config.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::config
|
||||||
35
eu07-parser/include/eu07/scene/context.hpp
Normal file
35
eu07-parser/include/eu07/scene/context.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/scratch.hpp>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParseContext {
|
||||||
|
SceneDocument& document;
|
||||||
|
SceneScratchpad scratch;
|
||||||
|
std::filesystem::path baseDirectory;
|
||||||
|
std::vector<std::filesystem::path> includeStack;
|
||||||
|
std::vector<std::vector<std::string>> activeIncludeParameters;
|
||||||
|
bool expandIncludes = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
using DirectiveParser = bool (*)(TokenStream& stream, ParseContext& context);
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
struct IncludeExpansionRequest {
|
||||||
|
std::size_t entryIndex = 0;
|
||||||
|
std::string file;
|
||||||
|
std::vector<std::string> parameters;
|
||||||
|
};
|
||||||
|
|
||||||
|
void expandInclude(ParseContext& context, const IncludeExpansionRequest& request);
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
58
eu07-parser/include/eu07/scene/cursor.hpp
Normal file
58
eu07-parser/include/eu07/scene/cursor.hpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
class TokenStream {
|
||||||
|
public:
|
||||||
|
explicit TokenStream(const std::vector<SourceToken>& tokens) noexcept
|
||||||
|
: tokens_(&tokens) {}
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const noexcept { return index_ >= tokens_->size(); }
|
||||||
|
|
||||||
|
[[nodiscard]] const SourceToken& peek() const {
|
||||||
|
if (empty()) {
|
||||||
|
throw std::runtime_error("TokenStream: koniec strumienia");
|
||||||
|
}
|
||||||
|
return (*tokens_)[index_];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] SourceToken consume() {
|
||||||
|
SourceToken token = peek();
|
||||||
|
++index_;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
void skip(const std::size_t count = 1) {
|
||||||
|
if (index_ + count > tokens_->size()) {
|
||||||
|
throw std::runtime_error("TokenStream: przekroczenie konca");
|
||||||
|
}
|
||||||
|
index_ += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t index() const noexcept { return index_; }
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t remaining() const noexcept {
|
||||||
|
return tokens_->size() - index_;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool peekIs(const std::string_view keyword) const {
|
||||||
|
return !empty() && peek().value == keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t checkpoint() const noexcept { return index_; }
|
||||||
|
|
||||||
|
void rewind(const std::size_t position) { index_ = position; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::vector<SourceToken>* tokens_;
|
||||||
|
std::size_t index_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
35
eu07-parser/include/eu07/scene/description.hpp
Normal file
35
eu07-parser/include/eu07/scene/description.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_description
|
||||||
|
// description text enddescription
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::description {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "description")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
while (!session.empty()) {
|
||||||
|
if (session.at("enddescription")) {
|
||||||
|
session.take();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.document.description.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::description
|
||||||
25
eu07-parser/include/eu07/scene/detail/boundary.hpp
Normal file
25
eu07-parser/include/eu07/scene/detail/boundary.hpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace eu07::scene::detail {
|
||||||
|
|
||||||
|
// Token w srodku wpisu node (np. isolated NazwaToru, velocity, endtrack).
|
||||||
|
[[nodiscard]] inline bool isEmbeddedInNode(const std::string_view token) noexcept {
|
||||||
|
return isKeyword(token, "velocity") || isKeyword(token, "rail_screw_used1") ||
|
||||||
|
isKeyword(token, "endtrack") || isKeyword(token, "endtraction") ||
|
||||||
|
isKeyword(token, "endtri") || isKeyword(token, "endtriangles") ||
|
||||||
|
isKeyword(token, "enddynamic") || isKeyword(token, "endmodel") ||
|
||||||
|
isKeyword(token, "vis") || isKeyword(token, "flat");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isTopLevelStarter(const std::string_view token) noexcept {
|
||||||
|
return isKeyword(token, "node") || isKeyword(token, "include") ||
|
||||||
|
isKeyword(token, "trainset") || isKeyword(token, "event") ||
|
||||||
|
isKeyword(token, "rotate") || isKeyword(token, "origin") ||
|
||||||
|
isKeyword(token, "scale") || isKeyword(token, "group");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::detail
|
||||||
70
eu07-parser/include/eu07/scene/detail/parse.hpp
Normal file
70
eu07-parser/include/eu07/scene/detail/parse.hpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Pomocniki: po trafieniu dyrektywy kazdy token jawnie (peek -> decyzja -> consume).
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::detail {
|
||||||
|
|
||||||
|
class ParseSession {
|
||||||
|
public:
|
||||||
|
ParseSession(TokenStream& stream, DirectiveBlock& block, const std::size_t anchor) noexcept
|
||||||
|
: stream_(stream)
|
||||||
|
, block_(block)
|
||||||
|
, anchor_(anchor) {}
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const noexcept { return stream_.empty(); }
|
||||||
|
|
||||||
|
[[nodiscard]] const SourceToken& peek() const { return stream_.peek(); }
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t line() const noexcept { return block_.line; }
|
||||||
|
|
||||||
|
[[nodiscard]] bool at(const std::string_view keyword) const noexcept {
|
||||||
|
return !empty() && isKeyword(peek().value, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool atIgnoreCase(const std::string_view keyword) const noexcept {
|
||||||
|
return !empty() && isKeywordIgnoreCase(peek().value, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool onSameLine() const noexcept {
|
||||||
|
return !empty() && peek().sourceLine == block_.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceToken take() {
|
||||||
|
SourceToken token = stream_.consume();
|
||||||
|
block_.tokens.push_back(token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool takeIf(const std::string_view keyword) {
|
||||||
|
if (!at(keyword)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
take();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool takeIfIgnoreCase(const std::string_view keyword) {
|
||||||
|
if (!atIgnoreCase(keyword)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
take();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail() noexcept {
|
||||||
|
stream_.rewind(anchor_);
|
||||||
|
block_.tokens.clear();
|
||||||
|
block_.line = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TokenStream& stream_;
|
||||||
|
DirectiveBlock& block_;
|
||||||
|
std::size_t anchor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::scene::detail
|
||||||
35
eu07-parser/include/eu07/scene/detail/subtype.hpp
Normal file
35
eu07-parser/include/eu07/scene/detail/subtype.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace eu07::scene::detail {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isNodeSubtype(const std::string_view token) noexcept {
|
||||||
|
static constexpr std::array<std::string_view, 14> kSubtypes{{
|
||||||
|
"dynamic",
|
||||||
|
"model",
|
||||||
|
"track",
|
||||||
|
"traction",
|
||||||
|
"tractionpowersource",
|
||||||
|
"triangles",
|
||||||
|
"triangle_strip",
|
||||||
|
"triangle_fan",
|
||||||
|
"lines",
|
||||||
|
"line_strip",
|
||||||
|
"line_loop",
|
||||||
|
"memcell",
|
||||||
|
"eventlauncher",
|
||||||
|
"sound",
|
||||||
|
}};
|
||||||
|
for (const std::string_view subtype : kSubtypes) {
|
||||||
|
if (isKeyword(token, subtype)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::detail
|
||||||
110
eu07-parser/include/eu07/scene/dispatch_table.hpp
Normal file
110
eu07-parser/include/eu07/scene/dispatch_table.hpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Jedna tabela: płaski dispatch jak w simulationstateserializer.cpp (bez processNested).
|
||||||
|
|
||||||
|
#include <eu07/scene/area.hpp>
|
||||||
|
#include <eu07/scene/atmo.hpp>
|
||||||
|
#include <eu07/scene/boundaries.hpp>
|
||||||
|
#include <eu07/scene/camera.hpp>
|
||||||
|
#include <eu07/scene/config.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/description.hpp>
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/event.hpp>
|
||||||
|
#include <eu07/scene/first_init.hpp>
|
||||||
|
#include <eu07/scene/group.hpp>
|
||||||
|
#include <eu07/scene/include.hpp>
|
||||||
|
#include <eu07/scene/isolated.hpp>
|
||||||
|
#include <eu07/scene/light.hpp>
|
||||||
|
#include <eu07/scene/lua.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node.hpp>
|
||||||
|
#include <eu07/scene/origin.hpp>
|
||||||
|
#include <eu07/scene/rotate.hpp>
|
||||||
|
#include <eu07/scene/scale.hpp>
|
||||||
|
#include <eu07/scene/sky.hpp>
|
||||||
|
#include <eu07/scene/terrain.hpp>
|
||||||
|
#include <eu07/scene/test.hpp>
|
||||||
|
#include <eu07/scene/time.hpp>
|
||||||
|
#include <eu07/scene/trainset.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct DirectiveEntry {
|
||||||
|
std::string_view keyword;
|
||||||
|
DirectiveParser parse;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool matchOrigin(const std::string_view token) noexcept {
|
||||||
|
return isKeyword(token, "origin") || isKeyword(token, "orgin");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool matchFirstInit(const std::string_view token) noexcept {
|
||||||
|
return isKeywordIgnoreCase(token, "FirstInit");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool matchesEntry(const DirectiveEntry& entry, const std::string_view token) noexcept {
|
||||||
|
if (entry.keyword == "origin") {
|
||||||
|
return matchOrigin(token);
|
||||||
|
}
|
||||||
|
if (entry.keyword == "FirstInit") {
|
||||||
|
return matchFirstInit(token);
|
||||||
|
}
|
||||||
|
return isKeyword(token, entry.keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr std::array<DirectiveEntry, 27> kDirectiveTable{{
|
||||||
|
{"trainset", trainset::parse},
|
||||||
|
{"endtrainset", endtrainset::parse},
|
||||||
|
{"assignment", assignment::parse},
|
||||||
|
{"include", scn_include::parse},
|
||||||
|
{"FirstInit", first_init::parse},
|
||||||
|
{"origin", origin::parse},
|
||||||
|
{"endorigin", endorigin::parse},
|
||||||
|
{"scale", scale::parse},
|
||||||
|
{"endscale", endscale::parse},
|
||||||
|
{"rotate", rotate::parse},
|
||||||
|
{"group", group::parse},
|
||||||
|
{"endgroup", endgroup::parse},
|
||||||
|
{"test", test::parse},
|
||||||
|
{"node", node::parse},
|
||||||
|
{"event", event::parse},
|
||||||
|
{"atmo", atmo::parse},
|
||||||
|
{"sky", sky::parse},
|
||||||
|
{"time", time::parse},
|
||||||
|
{"camera", camera::parse},
|
||||||
|
{"config", config::parse},
|
||||||
|
{"lua", lua::parse},
|
||||||
|
{"isolated", isolated::parse},
|
||||||
|
{"area", area::parse},
|
||||||
|
{"terrain", terrain::parse},
|
||||||
|
{"description", description::parse},
|
||||||
|
{"light", light::parse},
|
||||||
|
}};
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool dispatchDirective(TokenStream& stream, ParseContext& context) {
|
||||||
|
if (stream.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string_view head = stream.peek().value;
|
||||||
|
for (const DirectiveEntry& entry : kDirectiveTable) {
|
||||||
|
if (!matchesEntry(entry, head)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry.parse(stream, context)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
109
eu07-parser/include/eu07/scene/document.hpp
Normal file
109
eu07-parser/include/eu07/scene/document.hpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
#include <eu07/scene/include_types.hpp>
|
||||||
|
#include <eu07/scene/node/dynamic.hpp>
|
||||||
|
#include <eu07/scene/node/eventlauncher.hpp>
|
||||||
|
#include <eu07/scene/node/line_loop.hpp>
|
||||||
|
#include <eu07/scene/node/line_strip.hpp>
|
||||||
|
#include <eu07/scene/node/lines.hpp>
|
||||||
|
#include <eu07/scene/node/memcell.hpp>
|
||||||
|
#include <eu07/scene/node/model.hpp>
|
||||||
|
#include <eu07/scene/node/sound.hpp>
|
||||||
|
#include <eu07/scene/node/track.hpp>
|
||||||
|
#include <eu07/scene/node/traction.hpp>
|
||||||
|
#include <eu07/scene/node/traction_power.hpp>
|
||||||
|
#include <eu07/scene/node/triangle_fan.hpp>
|
||||||
|
#include <eu07/scene/node/triangle_strip.hpp>
|
||||||
|
#include <eu07/scene/node/triangles.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct DirectiveBlock {
|
||||||
|
std::size_t line = 0;
|
||||||
|
std::vector<SourceToken> tokens;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnknownEntry {
|
||||||
|
std::size_t line = 0;
|
||||||
|
std::string token;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SceneDocument {
|
||||||
|
std::vector<DirectiveBlock> atmo;
|
||||||
|
std::vector<DirectiveBlock> sky;
|
||||||
|
std::vector<DirectiveBlock> time;
|
||||||
|
std::vector<DirectiveBlock> firstInit;
|
||||||
|
std::vector<DirectiveBlock> trainset;
|
||||||
|
std::vector<DirectiveBlock> event;
|
||||||
|
std::vector<ParsedInclude> include;
|
||||||
|
std::vector<DirectiveBlock> camera;
|
||||||
|
std::vector<DirectiveBlock> config;
|
||||||
|
std::vector<DirectiveBlock> lua;
|
||||||
|
|
||||||
|
std::vector<ParsedNodeDynamic> nodeDynamic;
|
||||||
|
std::vector<ParsedNodeModel> nodeModel;
|
||||||
|
std::vector<ParsedTrackNormal> nodeTrackNormal;
|
||||||
|
std::vector<ParsedTrackSwitch> nodeTrackSwitch;
|
||||||
|
std::vector<ParsedTrackRoad> nodeTrackRoad;
|
||||||
|
std::vector<ParsedTrackCross> nodeTrackCross;
|
||||||
|
std::vector<ParsedTrackOther> nodeTrackOther;
|
||||||
|
std::vector<ParsedNodeTraction> nodeTraction;
|
||||||
|
std::vector<ParsedNodeTractionPowerSource> nodeTractionPower;
|
||||||
|
std::vector<ParsedNodeTriangles> nodeTriangles;
|
||||||
|
std::vector<ParsedNodeTriangleStrip> nodeTriangleStrip;
|
||||||
|
std::vector<ParsedNodeTriangleFan> nodeTriangleFan;
|
||||||
|
std::vector<ParsedNodeLines> nodeLines;
|
||||||
|
std::vector<ParsedNodeLineStrip> nodeLineStrip;
|
||||||
|
std::vector<ParsedNodeLineLoop> nodeLineLoop;
|
||||||
|
std::vector<ParsedNodeMemcell> nodeMemcell;
|
||||||
|
std::vector<ParsedNodeEventlauncher> nodeEventlauncher;
|
||||||
|
std::vector<ParsedNodeSound> nodeSound;
|
||||||
|
|
||||||
|
std::vector<DirectiveBlock> origin;
|
||||||
|
std::vector<DirectiveBlock> scale;
|
||||||
|
std::vector<DirectiveBlock> rotate;
|
||||||
|
std::vector<DirectiveBlock> group;
|
||||||
|
std::vector<DirectiveBlock> isolated;
|
||||||
|
std::vector<DirectiveBlock> area;
|
||||||
|
std::vector<DirectiveBlock> terrain;
|
||||||
|
|
||||||
|
std::vector<DirectiveBlock> description;
|
||||||
|
std::vector<DirectiveBlock> light;
|
||||||
|
std::vector<DirectiveBlock> test;
|
||||||
|
|
||||||
|
std::vector<UnknownEntry> unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SceneProcessResult {
|
||||||
|
SceneDocument document;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::size_t countTrackInstances(const SceneDocument& doc) noexcept {
|
||||||
|
return doc.nodeTrackNormal.size() + doc.nodeTrackSwitch.size() + doc.nodeTrackRoad.size() +
|
||||||
|
doc.nodeTrackCross.size() + doc.nodeTrackOther.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::size_t countNodeInstances(const SceneDocument& doc) noexcept {
|
||||||
|
return doc.nodeDynamic.size() + doc.nodeModel.size() + countTrackInstances(doc) +
|
||||||
|
doc.nodeTraction.size() + doc.nodeTractionPower.size() + doc.nodeTriangles.size() +
|
||||||
|
doc.nodeTriangleStrip.size() +
|
||||||
|
doc.nodeTriangleFan.size() + doc.nodeLines.size() + doc.nodeLineStrip.size() +
|
||||||
|
doc.nodeLineLoop.size() + doc.nodeMemcell.size() + doc.nodeEventlauncher.size() +
|
||||||
|
doc.nodeSound.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::size_t countDirectiveInstances(const SceneDocument& doc) noexcept {
|
||||||
|
return doc.atmo.size() + doc.sky.size() + doc.time.size() + doc.firstInit.size() +
|
||||||
|
doc.trainset.size() + doc.event.size() + doc.include.size() + doc.camera.size() +
|
||||||
|
doc.config.size() + doc.lua.size() + countNodeInstances(doc) + doc.origin.size() +
|
||||||
|
doc.scale.size() + doc.rotate.size() + doc.group.size() + doc.isolated.size() +
|
||||||
|
doc.area.size() + doc.terrain.size() + doc.description.size() + doc.light.size() +
|
||||||
|
doc.test.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
37
eu07-parser/include/eu07/scene/event.hpp
Normal file
37
eu07-parser/include/eu07/scene/event.hpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_event
|
||||||
|
// event name type delay … endevent
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::event {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "event")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
SourceToken head = stream.consume();
|
||||||
|
block.line = head.sourceLine;
|
||||||
|
block.tokens.push_back(std::move(head));
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
while (!session.empty()) {
|
||||||
|
if (session.at("endevent")) {
|
||||||
|
session.take();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.document.event.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::event
|
||||||
23
eu07-parser/include/eu07/scene/first_init.hpp
Normal file
23
eu07-parser/include/eu07/scene/first_init.hpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_FirstInit
|
||||||
|
// FirstInit — samo slowo kluczowe, bez end*
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::first_init {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeywordIgnoreCase(stream.peek().value, "FirstInit")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
ctx.document.firstInit.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::first_init
|
||||||
24
eu07-parser/include/eu07/scene/group.hpp
Normal file
24
eu07-parser/include/eu07/scene/group.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_group
|
||||||
|
// group … endgroup — jak oryginal: tylko push/pop grupy, bez połykania tokenów.
|
||||||
|
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::group {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "group")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
ctx.scratch.openGroup();
|
||||||
|
ctx.document.group.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::group
|
||||||
73
eu07-parser/include/eu07/scene/include.hpp
Normal file
73
eu07-parser/include/eu07/scene/include.hpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_include
|
||||||
|
// include file parameters end — ekspansja rekurencyjna (wariant 2)
|
||||||
|
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/include_resolve.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::scn_include {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "include")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedInclude entry;
|
||||||
|
SourceToken head = stream.consume();
|
||||||
|
entry.line = head.sourceLine;
|
||||||
|
entry.raw.push_back(head);
|
||||||
|
|
||||||
|
if (stream.empty()) {
|
||||||
|
entry.error = "brak sciezki pliku";
|
||||||
|
ctx.document.include.push_back(std::move(entry));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceToken fileToken;
|
||||||
|
if (!node::io::takeToken(stream, entry.raw, fileToken)) {
|
||||||
|
entry.error = "brak sciezki pliku";
|
||||||
|
ctx.document.include.push_back(std::move(entry));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
entry.file = fileToken.value;
|
||||||
|
|
||||||
|
while (!stream.empty()) {
|
||||||
|
if (isKeyword(stream.peek().value, "end")) {
|
||||||
|
entry.raw.push_back(stream.consume());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SourceToken param;
|
||||||
|
if (!node::io::takeToken(stream, entry.raw, param)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::string value = param.value;
|
||||||
|
if (!ctx.activeIncludeParameters.empty()) {
|
||||||
|
value = detail::applyIncludeParameters(value, ctx.activeIncludeParameters.back());
|
||||||
|
}
|
||||||
|
entry.parameters.push_back(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.siteTransform.originStack = ctx.scratch.location.originStack;
|
||||||
|
entry.siteTransform.scaleStack = ctx.scratch.location.scaleStack;
|
||||||
|
entry.siteTransform.rotation = ctx.scratch.location.rotation;
|
||||||
|
entry.siteTransform.groupStackDepth = ctx.scratch.group.activeStack.size();
|
||||||
|
|
||||||
|
const std::size_t includeIndex = ctx.document.include.size();
|
||||||
|
ctx.document.include.push_back(std::move(entry));
|
||||||
|
|
||||||
|
if (ctx.expandIncludes) {
|
||||||
|
detail::IncludeExpansionRequest request;
|
||||||
|
request.entryIndex = includeIndex;
|
||||||
|
request.file = ctx.document.include[includeIndex].file;
|
||||||
|
request.parameters = ctx.document.include[includeIndex].parameters;
|
||||||
|
detail::expandInclude(ctx, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::scn_include
|
||||||
66
eu07-parser/include/eu07/scene/include_placement.hpp
Normal file
66
eu07-parser/include/eu07/scene/include_placement.hpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Mapowanie origin/rotate z .inc na indeksy parametrow INCL — bez heurystyki w runtime.
|
||||||
|
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/include_resolve.hpp>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct IncludePlacement {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::uint8_t placementParamFromToken(const std::string_view text) {
|
||||||
|
std::uint8_t index = 0;
|
||||||
|
if (parseIncludeParameterIndex(text, index)) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<IncludePlacement> extractIncludePlacement(const SceneDocument& document) {
|
||||||
|
if (document.origin.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DirectiveBlock& originBlock = document.origin.front();
|
||||||
|
if (originBlock.tokens.size() < 3) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
IncludePlacement placement;
|
||||||
|
placement.origin_x_param = detail::placementParamFromToken(originBlock.tokens[0].value);
|
||||||
|
placement.origin_y_param = detail::placementParamFromToken(originBlock.tokens[1].value);
|
||||||
|
placement.origin_z_param = detail::placementParamFromToken(originBlock.tokens[2].value);
|
||||||
|
|
||||||
|
if (!document.rotate.empty()) {
|
||||||
|
const DirectiveBlock& rotateBlock = document.rotate.front();
|
||||||
|
if (rotateBlock.tokens.size() >= 2) {
|
||||||
|
placement.rotation_y_param = detail::placementParamFromToken(rotateBlock.tokens[1].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (placement.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return placement;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
619
eu07-parser/include/eu07/scene/include_resolve.hpp
Normal file
619
eu07-parser/include/eu07/scene/include_resolve.hpp
Normal file
@@ -0,0 +1,619 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace eu07::scene::detail {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enum class IncludeScanMode { Code, LineComment, BlockComment, QuotedString };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string normalizeIncludePathToken(std::string path) {
|
||||||
|
|
||||||
|
std::replace(path.begin(), path.end(), '\\', '/');
|
||||||
|
|
||||||
|
return path;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::filesystem::path resolveIncludePath(
|
||||||
|
|
||||||
|
const std::filesystem::path& baseDirectory,
|
||||||
|
|
||||||
|
const std::string& fileToken) {
|
||||||
|
|
||||||
|
const std::filesystem::path relative(normalizeIncludePathToken(fileToken));
|
||||||
|
|
||||||
|
if (relative.is_absolute()) {
|
||||||
|
|
||||||
|
return relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseDirectory / relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isIncFile(const std::string& fileToken) {
|
||||||
|
|
||||||
|
if (fileToken.size() < 4) {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string_view tail(fileToken.data() + fileToken.size() - 4, 4);
|
||||||
|
|
||||||
|
std::string lower(tail);
|
||||||
|
|
||||||
|
for (char& ch : lower) {
|
||||||
|
|
||||||
|
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return lower == ".inc";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool tryParseIncludeParameterRef(
|
||||||
|
|
||||||
|
const std::string_view text,
|
||||||
|
|
||||||
|
const std::size_t pos,
|
||||||
|
|
||||||
|
std::size_t& endOut) {
|
||||||
|
|
||||||
|
if (pos + 3 >= text.size() || text[pos] != '(' || text[pos + 1] != 'p') {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::size_t i = pos + 2;
|
||||||
|
|
||||||
|
if (i >= text.size() || !std::isdigit(static_cast<unsigned char>(text[i]))) {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
while (i < text.size() && std::isdigit(static_cast<unsigned char>(text[i]))) {
|
||||||
|
|
||||||
|
++i;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (i >= text.size() || text[i] != ')') {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
endOut = i;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseIncludeParameterIndex(
|
||||||
|
const std::string_view text,
|
||||||
|
std::uint8_t& indexOut) {
|
||||||
|
std::size_t end = 0;
|
||||||
|
if (!tryParseIncludeParameterRef(text, 0, end) || end + 1 != text.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t value = 0;
|
||||||
|
for (std::size_t i = 2; i < text.size() && std::isdigit(static_cast<unsigned char>(text[i])); ++i) {
|
||||||
|
value = value * 10 + static_cast<std::size_t>(text[i] - '0');
|
||||||
|
}
|
||||||
|
if (value == 0 || value > 255) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOut = static_cast<std::uint8_t>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string resolveIncludeParameter(
|
||||||
|
|
||||||
|
const std::string_view indexText,
|
||||||
|
|
||||||
|
const std::span<const std::string> parameters) {
|
||||||
|
|
||||||
|
std::size_t index = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
index = std::stoul(std::string(indexText));
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (index >= 1 && index <= parameters.size()) {
|
||||||
|
|
||||||
|
return parameters[index - 1];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "none";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string applyIncludeParameters(
|
||||||
|
|
||||||
|
std::string text,
|
||||||
|
|
||||||
|
const std::span<const std::string> parameters) {
|
||||||
|
|
||||||
|
if (parameters.empty()) {
|
||||||
|
|
||||||
|
return text;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::string out;
|
||||||
|
|
||||||
|
out.reserve(text.size());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IncludeScanMode mode = IncludeScanMode::Code;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < text.size(); ) {
|
||||||
|
|
||||||
|
const char ch = text[i];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (mode == IncludeScanMode::QuotedString) {
|
||||||
|
|
||||||
|
out.push_back(ch);
|
||||||
|
|
||||||
|
if (ch == '\\' && i + 1 < text.size()) {
|
||||||
|
|
||||||
|
out.push_back(text[i + 1]);
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '"') {
|
||||||
|
|
||||||
|
mode = IncludeScanMode::Code;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (mode == IncludeScanMode::Code) {
|
||||||
|
|
||||||
|
if (ch == '"') {
|
||||||
|
|
||||||
|
out.push_back(ch);
|
||||||
|
|
||||||
|
mode = IncludeScanMode::QuotedString;
|
||||||
|
|
||||||
|
++i;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '/' && i + 1 < text.size()) {
|
||||||
|
|
||||||
|
if (text[i + 1] == '/') {
|
||||||
|
|
||||||
|
out.push_back(ch);
|
||||||
|
|
||||||
|
out.push_back(text[i + 1]);
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
|
||||||
|
mode = IncludeScanMode::LineComment;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text[i + 1] == '*') {
|
||||||
|
|
||||||
|
out.push_back(ch);
|
||||||
|
|
||||||
|
out.push_back(text[i + 1]);
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
|
||||||
|
mode = IncludeScanMode::BlockComment;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (mode == IncludeScanMode::BlockComment) {
|
||||||
|
|
||||||
|
if (ch == '*' && i + 1 < text.size() && text[i + 1] == '/') {
|
||||||
|
|
||||||
|
out.push_back(ch);
|
||||||
|
|
||||||
|
out.push_back(text[i + 1]);
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
|
||||||
|
mode = IncludeScanMode::Code;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::size_t refEnd = 0;
|
||||||
|
|
||||||
|
if (tryParseIncludeParameterRef(text, i, refEnd)) {
|
||||||
|
|
||||||
|
const std::string replacement = resolveIncludeParameter(
|
||||||
|
|
||||||
|
text.substr(i + 2, refEnd - (i + 2)),
|
||||||
|
|
||||||
|
parameters);
|
||||||
|
|
||||||
|
out.append(replacement);
|
||||||
|
|
||||||
|
i = refEnd + 1;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
out.push_back(ch);
|
||||||
|
|
||||||
|
if (ch == '\n' && mode == IncludeScanMode::LineComment) {
|
||||||
|
|
||||||
|
mode = IncludeScanMode::Code;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return out;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline eu07::ParseResult parseIncludedFile(
|
||||||
|
|
||||||
|
const std::filesystem::path& path,
|
||||||
|
|
||||||
|
const std::vector<std::string>& parameters) {
|
||||||
|
|
||||||
|
const eu07::RawFile raw = eu07::readRawFile(path);
|
||||||
|
|
||||||
|
const std::string expanded = applyIncludeParameters(raw.data, parameters);
|
||||||
|
|
||||||
|
return eu07::parseText(expanded);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isIncludeCycle(
|
||||||
|
|
||||||
|
const std::filesystem::path& resolved,
|
||||||
|
|
||||||
|
const std::vector<std::filesystem::path>& includeStack) {
|
||||||
|
|
||||||
|
const std::filesystem::path normalized = resolved.lexically_normal();
|
||||||
|
|
||||||
|
for (const std::filesystem::path& entry : includeStack) {
|
||||||
|
|
||||||
|
if (entry.lexically_normal() == normalized) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Jak cParser / eu7_loader::resolve_parser_include_path — wzgledem scenery/, nie parent pliku.
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<std::filesystem::path> findSceneryRootInPath(
|
||||||
|
|
||||||
|
std::filesystem::path inputPath) {
|
||||||
|
|
||||||
|
inputPath = inputPath.lexically_normal();
|
||||||
|
|
||||||
|
while (!inputPath.empty()) {
|
||||||
|
|
||||||
|
if (inputPath.filename() == "scenery") {
|
||||||
|
|
||||||
|
return inputPath;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path parent = inputPath.parent_path();
|
||||||
|
|
||||||
|
if (parent == inputPath) {
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath = parent;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::string relativeSceneryFile(
|
||||||
|
|
||||||
|
const std::filesystem::path& sceneryRoot,
|
||||||
|
|
||||||
|
const std::filesystem::path& absolutePath) {
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
|
||||||
|
const std::filesystem::path relative =
|
||||||
|
|
||||||
|
std::filesystem::relative(absolutePath.lexically_normal(), sceneryRoot.lexically_normal(), ec);
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
|
||||||
|
return absolutePath.filename().generic_string();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return relative.generic_string();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::filesystem::path resolveParserIncludePath(
|
||||||
|
|
||||||
|
const std::filesystem::path& sceneryRoot,
|
||||||
|
|
||||||
|
const std::string& currentRelativeFile,
|
||||||
|
|
||||||
|
const std::string& fileToken) {
|
||||||
|
|
||||||
|
std::string reference = normalizeIncludePathToken(fileToken);
|
||||||
|
|
||||||
|
while (!reference.empty() && reference[0] == '$') {
|
||||||
|
|
||||||
|
reference.erase(0, 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const std::filesystem::path root = sceneryRoot.lexically_normal();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (reference.find('/') != std::string::npos) {
|
||||||
|
|
||||||
|
return root / std::filesystem::path(reference);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentRelativeFile.empty()) {
|
||||||
|
|
||||||
|
const std::size_t slash = currentRelativeFile.find_last_of('/');
|
||||||
|
|
||||||
|
if (slash != std::string::npos) {
|
||||||
|
|
||||||
|
return root / std::filesystem::path(currentRelativeFile.substr(0, slash + 1) + reference);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return root / std::filesystem::path(reference);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::filesystem::path resolveIncludeSourcePath(
|
||||||
|
|
||||||
|
const std::filesystem::path& sceneryRoot,
|
||||||
|
|
||||||
|
const std::string& currentRelativeFile,
|
||||||
|
|
||||||
|
const std::string& fileToken) {
|
||||||
|
|
||||||
|
const std::filesystem::path primary =
|
||||||
|
|
||||||
|
resolveParserIncludePath(sceneryRoot, currentRelativeFile, fileToken);
|
||||||
|
|
||||||
|
if (std::filesystem::exists(primary)) {
|
||||||
|
|
||||||
|
return primary;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::filesystem::path stem = primary;
|
||||||
|
|
||||||
|
stem.replace_extension();
|
||||||
|
|
||||||
|
for (const std::string_view ext : {".scm", ".inc", ".scn"}) {
|
||||||
|
|
||||||
|
std::filesystem::path candidate = stem;
|
||||||
|
|
||||||
|
candidate.replace_extension(ext);
|
||||||
|
|
||||||
|
if (std::filesystem::exists(candidate)) {
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Include bez katalogu (tree.inc, grass.inc) — plik w korzeniu scenery/, nie w podfolderze parenta.
|
||||||
|
|
||||||
|
std::string reference = normalizeIncludePathToken(fileToken);
|
||||||
|
|
||||||
|
while (!reference.empty() && reference[0] == '$') {
|
||||||
|
|
||||||
|
reference.erase(0, 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reference.find('/') == std::string::npos && primary != sceneryRoot / reference) {
|
||||||
|
|
||||||
|
const std::filesystem::path rootCandidate =
|
||||||
|
|
||||||
|
sceneryRoot.lexically_normal() / std::filesystem::path(reference);
|
||||||
|
|
||||||
|
if (std::filesystem::exists(rootCandidate)) {
|
||||||
|
|
||||||
|
return rootCandidate;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path rootStem = rootCandidate;
|
||||||
|
|
||||||
|
rootStem.replace_extension();
|
||||||
|
|
||||||
|
for (const std::string_view ext : {".scm", ".inc", ".scn"}) {
|
||||||
|
|
||||||
|
std::filesystem::path candidate = rootStem;
|
||||||
|
|
||||||
|
candidate.replace_extension(ext);
|
||||||
|
|
||||||
|
if (std::filesystem::exists(candidate)) {
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return primary;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::filesystem::path resolveExpandedIncludePath(
|
||||||
|
|
||||||
|
const std::filesystem::path& baseDirectory,
|
||||||
|
|
||||||
|
const std::vector<std::filesystem::path>& includeStack,
|
||||||
|
|
||||||
|
const std::string& fileToken) {
|
||||||
|
|
||||||
|
const std::filesystem::path anchor =
|
||||||
|
|
||||||
|
baseDirectory.empty() ? std::filesystem::current_path() : baseDirectory;
|
||||||
|
|
||||||
|
if (const std::optional<std::filesystem::path> sceneryRoot = findSceneryRootInPath(anchor)) {
|
||||||
|
|
||||||
|
std::string currentRelative;
|
||||||
|
|
||||||
|
if (!includeStack.empty()) {
|
||||||
|
|
||||||
|
currentRelative = relativeSceneryFile(*sceneryRoot, includeStack.back());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveIncludeSourcePath(*sceneryRoot, currentRelative, fileToken);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveIncludePath(baseDirectory, fileToken);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace eu07::scene::detail
|
||||||
|
|
||||||
|
|
||||||
47
eu07-parser/include/eu07/scene/include_scan.hpp
Normal file
47
eu07-parser/include/eu07/scene/include_scan.hpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Szybkie wyciagniecie sciezek z dyrektyw include — bez processScene / bake.
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<std::string> scanIncludeFilePaths(
|
||||||
|
const std::span<const SourceToken> tokens) {
|
||||||
|
std::vector<std::string> files;
|
||||||
|
files.reserve(8);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < tokens.size(); ++i) {
|
||||||
|
if (!isKeyword(tokens[i].value, "include")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i + 1 >= tokens.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
files.push_back(tokens[i + 1].value);
|
||||||
|
|
||||||
|
for (std::size_t j = i + 2; j < tokens.size(); ++j) {
|
||||||
|
if (isKeyword(tokens[j].value, "end")) {
|
||||||
|
i = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::vector<std::string> scanIncludeFilePaths(
|
||||||
|
const std::filesystem::path& sourcePath) {
|
||||||
|
const ParseResult parsed = parseFile(sourcePath);
|
||||||
|
return scanIncludeFilePaths(parsed.tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
23
eu07-parser/include/eu07/scene/include_types.hpp
Normal file
23
eu07-parser/include/eu07/scene/include_types.hpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
#include <eu07/scene/runtime/basic_node.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedInclude {
|
||||||
|
std::size_t line = 0;
|
||||||
|
std::string file;
|
||||||
|
std::vector<std::string> parameters;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
runtime::TransformContext siteTransform;
|
||||||
|
bool expanded = false;
|
||||||
|
std::optional<std::string> error;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
48
eu07-parser/include/eu07/scene/isolated.hpp
Normal file
48
eu07-parser/include/eu07/scene/isolated.hpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_isolated
|
||||||
|
// isolated name tracks endisolated
|
||||||
|
// (nie mylic z isolated Nazwa wewnatrz node::track)
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/boundary.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::isolated {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "isolated")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
while (!session.empty()) {
|
||||||
|
if (session.at("endisolated")) {
|
||||||
|
session.take();
|
||||||
|
ctx.document.isolated.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string_view value = session.peek().value;
|
||||||
|
if (detail::isTopLevelStarter(value) || detail::isEmbeddedInNode(value) ||
|
||||||
|
isKeyword(value, "isolated")) {
|
||||||
|
session.fail();
|
||||||
|
stream.rewind(anchor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
session.fail();
|
||||||
|
stream.rewind(anchor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::isolated
|
||||||
57
eu07-parser/include/eu07/scene/keywords.hpp
Normal file
57
eu07-parser/include/eu07/scene/keywords.hpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
inline constexpr std::array<std::string_view, 30> kScenarioDirectiveKeywords{{
|
||||||
|
"atmo",
|
||||||
|
"sky",
|
||||||
|
"time",
|
||||||
|
"FirstInit",
|
||||||
|
"trainset",
|
||||||
|
"endtrainset",
|
||||||
|
"assignment",
|
||||||
|
"endassignment",
|
||||||
|
"event",
|
||||||
|
"include",
|
||||||
|
"camera",
|
||||||
|
"config",
|
||||||
|
"lua",
|
||||||
|
"node",
|
||||||
|
"origin",
|
||||||
|
"orgin",
|
||||||
|
"endorigin",
|
||||||
|
"scale",
|
||||||
|
"endscale",
|
||||||
|
"rotate",
|
||||||
|
"group",
|
||||||
|
"endgroup",
|
||||||
|
"isolated",
|
||||||
|
"area",
|
||||||
|
"terrain",
|
||||||
|
"description",
|
||||||
|
"light",
|
||||||
|
"test",
|
||||||
|
"endtest",
|
||||||
|
}};
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isScenarioDirectiveKeyword(const std::string_view token) noexcept {
|
||||||
|
for (const std::string_view keyword : kScenarioDirectiveKeywords) {
|
||||||
|
if (keyword == "FirstInit") {
|
||||||
|
if (isKeywordIgnoreCase(token, keyword)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isKeyword(token, keyword)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
35
eu07-parser/include/eu07/scene/light.hpp
Normal file
35
eu07-parser/include/eu07/scene/light.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_light
|
||||||
|
// light location ambient diffuse specular endlight
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::light {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "light")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
while (!session.empty()) {
|
||||||
|
if (session.at("endlight")) {
|
||||||
|
session.take();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.document.light.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::light
|
||||||
31
eu07-parser/include/eu07/scene/lua.hpp
Normal file
31
eu07-parser/include/eu07/scene/lua.hpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_lua
|
||||||
|
// lua file
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/parse.hpp>
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::lua {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "lua")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
detail::ParseSession session(stream, block, anchor);
|
||||||
|
|
||||||
|
if (!session.empty()) {
|
||||||
|
session.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.document.lua.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::lua
|
||||||
33
eu07-parser/include/eu07/scene/match.hpp
Normal file
33
eu07-parser/include/eu07/scene/match.hpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool asciiEquals(std::string_view a, std::string_view b) noexcept {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool asciiEqualsIgnoreCase(std::string_view a, std::string_view b) noexcept {
|
||||||
|
if (a.size() != b.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < a.size(); ++i) {
|
||||||
|
if (std::tolower(static_cast<unsigned char>(a[i])) !=
|
||||||
|
std::tolower(static_cast<unsigned char>(b[i]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isKeyword(std::string_view token, std::string_view keyword) noexcept {
|
||||||
|
return asciiEquals(token, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isKeywordIgnoreCase(std::string_view token, std::string_view keyword) noexcept {
|
||||||
|
return asciiEqualsIgnoreCase(token, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
66
eu07-parser/include/eu07/scene/node.hpp
Normal file
66
eu07-parser/include/eu07/scene/node.hpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node
|
||||||
|
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/node/common.hpp>
|
||||||
|
#include <eu07/scene/node/kinds.hpp>
|
||||||
|
#include <eu07/scene/node/shared.hpp>
|
||||||
|
#include <eu07/scene/scratch.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::node {
|
||||||
|
|
||||||
|
inline void applyScratchContext(NodeHeader& header, const SceneScratchpad& scratch) {
|
||||||
|
header.originOffset = scratch.currentOrigin();
|
||||||
|
header.scaleFactor = scratch.currentScale();
|
||||||
|
header.rotation = scratch.location.rotation;
|
||||||
|
header.originStack = scratch.location.originStack;
|
||||||
|
header.scaleStack = scratch.location.scaleStack;
|
||||||
|
header.groupStackDepth = scratch.group.activeStack.size();
|
||||||
|
header.groupHandle = scratch.activeGroupHandle();
|
||||||
|
if (scratch.trainset.isOpen && scratch.trainset.docIndex) {
|
||||||
|
header.trainsetIndex = *scratch.trainset.docIndex;
|
||||||
|
header.trainset = TrainsetContext{
|
||||||
|
scratch.trainset.name,
|
||||||
|
scratch.trainset.track,
|
||||||
|
scratch.trainset.offset,
|
||||||
|
scratch.trainset.velocity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseInto(TokenStream& stream, ParseContext& context) {
|
||||||
|
const std::size_t anchor = stream.checkpoint();
|
||||||
|
|
||||||
|
NodeHeader header;
|
||||||
|
std::string subtype;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
if (!io::consumeHeader(stream, header, subtype, raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyScratchContext(header, context.scratch);
|
||||||
|
|
||||||
|
const io::NodeKindDesc* const kind = io::findKind(subtype, io::kAllKinds);
|
||||||
|
if (kind == nullptr) {
|
||||||
|
stream.rewind(anchor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kind->parseAndStore(stream, context.document, header, nullptr)) {
|
||||||
|
stream.rewind(anchor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& context) {
|
||||||
|
return parseInto(stream, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::node
|
||||||
60
eu07-parser/include/eu07/scene/node/common.hpp
Normal file
60
eu07-parser/include/eu07/scene/node/common.hpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/detail/subtype.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct SceneDocument;
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
|
|
||||||
|
namespace eu07::scene::node::io {
|
||||||
|
|
||||||
|
struct NodeKindDesc {
|
||||||
|
std::string_view subtype;
|
||||||
|
std::string_view endMarker;
|
||||||
|
bool (*parseAndStore)(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw);
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool consumeHeader(
|
||||||
|
TokenStream& stream,
|
||||||
|
NodeHeader& header,
|
||||||
|
std::string& subtypeOut,
|
||||||
|
std::vector<SourceToken>& raw) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "node")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceToken head;
|
||||||
|
if (!takeToken(stream, raw, head)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
header.line = head.sourceLine;
|
||||||
|
|
||||||
|
if (!takeDouble(stream, raw, header.rangeMax) || !takeDouble(stream, raw, header.rangeMin) ||
|
||||||
|
!takeString(stream, raw, header.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream.empty() || !eu07::scene::detail::isNodeSubtype(stream.peek().value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!takeString(stream, raw, subtypeOut)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::node::io
|
||||||
128
eu07-parser/include/eu07/scene/node/dynamic.hpp
Normal file
128
eu07-parser/include/eu07/scene/node/dynamic.hpp
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node::dynamic
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/shared.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeDynamic {
|
||||||
|
NodeHeader header;
|
||||||
|
std::string datafolder;
|
||||||
|
std::string skinfile;
|
||||||
|
std::string mmdfile;
|
||||||
|
std::string pathname;
|
||||||
|
double vehicleOffset = -1.0;
|
||||||
|
std::string driverType;
|
||||||
|
CouplingSpec coupling;
|
||||||
|
double velocity = 0.0;
|
||||||
|
int loadcount = 0;
|
||||||
|
std::optional<std::string> loadtype;
|
||||||
|
std::optional<std::string> loadModifier;
|
||||||
|
std::optional<std::string> destination;
|
||||||
|
bool inTrainset = false;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_dynamic {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "dynamic";
|
||||||
|
inline constexpr std::string_view kEndMarker = "enddynamic";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeDynamic& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
out.inTrainset = header.trainset.has_value();
|
||||||
|
|
||||||
|
if (!node::io::takeString(stream, raw, out.datafolder) ||
|
||||||
|
!node::io::takeString(stream, raw, out.skinfile) ||
|
||||||
|
!node::io::takeString(stream, raw, out.mmdfile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out.inTrainset) {
|
||||||
|
out.pathname = header.trainset->track;
|
||||||
|
} else if (!node::io::takeString(stream, raw, out.pathname)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node::io::takeDouble(stream, raw, out.vehicleOffset) ||
|
||||||
|
!node::io::takeString(stream, raw, out.driverType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string couplingRaw;
|
||||||
|
if (out.inTrainset) {
|
||||||
|
if (!node::io::takeString(stream, raw, couplingRaw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
couplingRaw = "3";
|
||||||
|
}
|
||||||
|
out.coupling = parseCouplingSpec(couplingRaw);
|
||||||
|
|
||||||
|
if (out.inTrainset) {
|
||||||
|
out.velocity = header.trainset->velocity;
|
||||||
|
} else if (!node::io::takeDouble(stream, raw, out.velocity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double loadcount = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, loadcount)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.loadcount = static_cast<int>(loadcount);
|
||||||
|
|
||||||
|
if (out.loadcount > 0) {
|
||||||
|
if (stream.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isKeyword(stream.peek().value, kEndMarker)) {
|
||||||
|
out.loadcount = 0;
|
||||||
|
} else {
|
||||||
|
std::string loadtype;
|
||||||
|
if (!node::io::takeString(stream, raw, loadtype)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isKeyword(loadtype, kEndMarker)) {
|
||||||
|
out.loadcount = 0;
|
||||||
|
} else {
|
||||||
|
out.loadtype = std::move(loadtype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
std::string token;
|
||||||
|
if (!node::io::takeString(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isKeyword(token, kEndMarker)) {
|
||||||
|
std::string dest;
|
||||||
|
if (!node::io::takeString(stream, raw, dest)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isKeyword(dest, kEndMarker)) {
|
||||||
|
out.destination = std::move(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_dynamic
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
169
eu07-parser/include/eu07/scene/node/eventlauncher.hpp
Normal file
169
eu07-parser/include/eu07/scene/node/eventlauncher.hpp
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node::eventlauncher
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct EventLauncherCondition {
|
||||||
|
std::string memcell;
|
||||||
|
std::string parameter;
|
||||||
|
std::optional<double> value1;
|
||||||
|
std::optional<double> value2;
|
||||||
|
int checkMask = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedNodeEventlauncher {
|
||||||
|
NodeHeader header;
|
||||||
|
Vec3 position;
|
||||||
|
double radius = 0.0;
|
||||||
|
std::string key;
|
||||||
|
std::string time;
|
||||||
|
std::string event1;
|
||||||
|
std::optional<std::string> event2;
|
||||||
|
std::optional<EventLauncherCondition> condition;
|
||||||
|
bool trainTriggered = false;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_eventlauncher {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "eventlauncher";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endeventlauncher";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeConditionField(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const int flag,
|
||||||
|
std::optional<double>& out,
|
||||||
|
int& checkMask) {
|
||||||
|
std::string token;
|
||||||
|
if (!node::io::takeString(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (token == "*") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
checkMask |= flag;
|
||||||
|
const std::optional<double> value = node::io::parseDouble(token);
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = *value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool consumeLauncherEnd(TokenStream& stream, std::vector<SourceToken>& raw) {
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeEventlauncher& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
if (!node::io::takeVec3(stream, raw, out.position) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.radius) || !node::io::takeString(stream, raw, out.key) ||
|
||||||
|
!node::io::takeString(stream, raw, out.time) || !node::io::takeString(stream, raw, out.event1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream.empty() || node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
return consumeLauncherEnd(stream, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string token;
|
||||||
|
if (!node::io::takeString(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaSzyna (EvLaunch.cpp): "end" zamiast drugiego eventu lub endeventlauncher.
|
||||||
|
if (isKeyword(token, "end") || isKeyword(token, "endeventlauncher")) {
|
||||||
|
if (isKeyword(token, "endeventlauncher")) {
|
||||||
|
return consumeLauncherEnd(stream, raw);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isKeyword(token, "condition") && !isKeyword(token, "traintriggered")) {
|
||||||
|
out.event2 = token;
|
||||||
|
if (stream.empty() || node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
return consumeLauncherEnd(stream, raw);
|
||||||
|
}
|
||||||
|
if (!node::io::takeString(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isKeyword(token, "end") || isKeyword(token, "endeventlauncher")) {
|
||||||
|
if (isKeyword(token, "endeventlauncher")) {
|
||||||
|
return consumeLauncherEnd(stream, raw);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyword(token, "condition")) {
|
||||||
|
EventLauncherCondition cond;
|
||||||
|
if (!node::io::takeString(stream, raw, cond.memcell) ||
|
||||||
|
!node::io::takeString(stream, raw, cond.parameter)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cond.parameter != "*") {
|
||||||
|
cond.checkMask |= 1;
|
||||||
|
}
|
||||||
|
if (!takeConditionField(stream, raw, 2, cond.value1, cond.checkMask)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!takeConditionField(stream, raw, 4, cond.value2, cond.checkMask)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
std::string closing;
|
||||||
|
if (!node::io::takeString(stream, raw, closing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
token = std::move(closing);
|
||||||
|
}
|
||||||
|
out.condition = std::move(cond);
|
||||||
|
if (isKeyword(token, "end")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyword(token, "traintriggered")) {
|
||||||
|
out.trainTriggered = true;
|
||||||
|
if (stream.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
return consumeLauncherEnd(stream, raw);
|
||||||
|
}
|
||||||
|
if (!node::io::takeString(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isKeyword(token, "end")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumeLauncherEnd(stream, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_eventlauncher
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
31
eu07-parser/include/eu07/scene/node/header.hpp
Normal file
31
eu07-parser/include/eu07/scene/node/header.hpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <eu07/scene/node/shared.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct NodeHeader {
|
||||||
|
std::size_t line = 0;
|
||||||
|
double rangeMax;
|
||||||
|
double rangeMin;
|
||||||
|
std::string name;
|
||||||
|
Vec3 originOffset{};
|
||||||
|
Vec3 scaleFactor{1.0, 1.0, 1.0};
|
||||||
|
Vec3 rotation{};
|
||||||
|
std::vector<Vec3> originStack;
|
||||||
|
std::vector<Vec3> scaleStack;
|
||||||
|
std::size_t groupStackDepth = 0;
|
||||||
|
std::optional<std::size_t> groupHandle;
|
||||||
|
std::optional<std::size_t> trainsetIndex;
|
||||||
|
std::optional<TrainsetContext> trainset;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
295
eu07-parser/include/eu07/scene/node/kinds.hpp
Normal file
295
eu07-parser/include/eu07/scene/node/kinds.hpp
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/document.hpp>
|
||||||
|
#include <eu07/scene/node/common.hpp>
|
||||||
|
#include <eu07/scene/node/dynamic.hpp>
|
||||||
|
#include <eu07/scene/node/eventlauncher.hpp>
|
||||||
|
#include <eu07/scene/node/line_loop.hpp>
|
||||||
|
#include <eu07/scene/node/line_strip.hpp>
|
||||||
|
#include <eu07/scene/node/lines.hpp>
|
||||||
|
#include <eu07/scene/node/memcell.hpp>
|
||||||
|
#include <eu07/scene/node/model.hpp>
|
||||||
|
#include <eu07/scene/node/sound.hpp>
|
||||||
|
#include <eu07/scene/node/track.hpp>
|
||||||
|
#include <eu07/scene/node/traction.hpp>
|
||||||
|
#include <eu07/scene/node/traction_power.hpp>
|
||||||
|
#include <eu07/scene/node/triangle_fan.hpp>
|
||||||
|
#include <eu07/scene/node/triangle_strip.hpp>
|
||||||
|
#include <eu07/scene/node/triangles.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::node::io {
|
||||||
|
|
||||||
|
inline void storeDynamic(SceneDocument& document, ParsedNodeDynamic&& node) {
|
||||||
|
document.nodeDynamic.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeModel(SceneDocument& document, ParsedNodeModel&& node) {
|
||||||
|
document.nodeModel.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseTrack(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
std::string kindToken;
|
||||||
|
if (!node::io::takeString(stream, raw, kindToken)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto finalize = [&](auto& node) {
|
||||||
|
node.raw = std::move(raw);
|
||||||
|
if (embedRaw != nullptr) {
|
||||||
|
embedRaw->insert(embedRaw->end(), node.raw.begin(), node.raw.end());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (node_track::isNormalLike(kindToken)) {
|
||||||
|
ParsedTrackNormal node;
|
||||||
|
if (!node_track::parseNormalBody(stream, raw, header, kindToken, node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finalize(node);
|
||||||
|
document.nodeTrackNormal.push_back(std::move(node));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(kindToken, "switch")) {
|
||||||
|
ParsedTrackSwitch node;
|
||||||
|
if (!node_track::parseSwitchBody(stream, raw, header, node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finalize(node);
|
||||||
|
document.nodeTrackSwitch.push_back(std::move(node));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(kindToken, "road")) {
|
||||||
|
ParsedTrackRoad node;
|
||||||
|
if (!node_track::parseRoadBody(stream, raw, header, node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finalize(node);
|
||||||
|
document.nodeTrackRoad.push_back(std::move(node));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(kindToken, "cross")) {
|
||||||
|
ParsedTrackCross node;
|
||||||
|
if (!node_track::parseCrossBody(stream, raw, header, node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finalize(node);
|
||||||
|
document.nodeTrackCross.push_back(std::move(node));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedTrackOther node;
|
||||||
|
if (!node_track::parseOtherBody(
|
||||||
|
stream, raw, header, kindToken, node, node_track::isRiverLike(kindToken))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finalize(node);
|
||||||
|
document.nodeTrackOther.push_back(std::move(node));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
inline void storeTraction(SceneDocument& document, ParsedNodeTraction&& node) {
|
||||||
|
document.nodeTraction.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeTractionPower(SceneDocument& document, ParsedNodeTractionPowerSource&& node) {
|
||||||
|
document.nodeTractionPower.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeTriangles(SceneDocument& document, ParsedNodeTriangles&& node) {
|
||||||
|
document.nodeTriangles.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeTriangleStrip(SceneDocument& document, ParsedNodeTriangleStrip&& node) {
|
||||||
|
document.nodeTriangleStrip.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeTriangleFan(SceneDocument& document, ParsedNodeTriangleFan&& node) {
|
||||||
|
document.nodeTriangleFan.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeLines(SceneDocument& document, ParsedNodeLines&& node) {
|
||||||
|
document.nodeLines.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeLineStrip(SceneDocument& document, ParsedNodeLineStrip&& node) {
|
||||||
|
document.nodeLineStrip.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeLineLoop(SceneDocument& document, ParsedNodeLineLoop&& node) {
|
||||||
|
document.nodeLineLoop.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeMemcell(SceneDocument& document, ParsedNodeMemcell&& node) {
|
||||||
|
document.nodeMemcell.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeEventlauncher(SceneDocument& document, ParsedNodeEventlauncher&& node) {
|
||||||
|
document.nodeEventlauncher.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
inline void storeSound(SceneDocument& document, ParsedNodeSound&& node) {
|
||||||
|
document.nodeSound.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Parsed, bool (*ParseBody)(TokenStream&, std::vector<SourceToken>&, const NodeHeader&, Parsed&), void (*Store)(SceneDocument&, Parsed&&)>
|
||||||
|
[[nodiscard]] inline bool parseKind(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
Parsed parsed;
|
||||||
|
if (!ParseBody(stream, raw, header, parsed)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
parsed.raw = std::move(raw);
|
||||||
|
if (embedRaw != nullptr) {
|
||||||
|
embedRaw->insert(embedRaw->end(), parsed.raw.begin(), parsed.raw.end());
|
||||||
|
}
|
||||||
|
Store(document, std::move(parsed));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseDynamic(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeDynamic, node_dynamic::parseBody, storeDynamic>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseModel(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeModel, node_model::parseBody, storeModel>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseTraction(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeTraction, node_traction::parseBody, storeTraction>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseTractionPower(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeTractionPowerSource, node_traction_power::parseBody, storeTractionPower>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseTriangles(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeTriangles, node_triangles::parseBody, storeTriangles>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseTriangleStrip(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeTriangleStrip, node_triangle_strip::parseBody, storeTriangleStrip>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseTriangleFan(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeTriangleFan, node_triangle_fan::parseBody, storeTriangleFan>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseLines(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeLines, node_lines::parseBody, storeLines>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseLineStrip(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeLineStrip, node_line_strip::parseBody, storeLineStrip>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseLineLoop(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeLineLoop, node_line_loop::parseBody, storeLineLoop>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseMemcell(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeMemcell, node_memcell::parseBody, storeMemcell>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseEventlauncher(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeEventlauncher, node_eventlauncher::parseBody, storeEventlauncher>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parseSound(
|
||||||
|
TokenStream& stream,
|
||||||
|
SceneDocument& document,
|
||||||
|
const NodeHeader& header,
|
||||||
|
std::vector<SourceToken>* embedRaw) {
|
||||||
|
return parseKind<ParsedNodeSound, node_sound::parseBody, storeSound>(
|
||||||
|
stream, document, header, embedRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr std::array<NodeKindDesc, 14> kAllKinds{{
|
||||||
|
{node_dynamic::kSubtype, node_dynamic::kEndMarker, &parseDynamic},
|
||||||
|
{node_model::kSubtype, node_model::kEndMarker, &parseModel},
|
||||||
|
{node_track::kSubtype, node_track::kEndMarker, &parseTrack},
|
||||||
|
{node_traction::kSubtype, node_traction::kEndMarker, &parseTraction},
|
||||||
|
{node_traction_power::kSubtype, node_traction_power::kEndMarker, &parseTractionPower},
|
||||||
|
{node_triangles::kSubtype, node_triangles::kEndMarker, &parseTriangles},
|
||||||
|
{node_triangle_strip::kSubtype, node_triangle_strip::kEndMarker, &parseTriangleStrip},
|
||||||
|
{node_triangle_fan::kSubtype, node_triangle_fan::kEndMarker, &parseTriangleFan},
|
||||||
|
{node_lines::kSubtype, node_lines::kEndMarker, &parseLines},
|
||||||
|
{node_line_strip::kSubtype, node_line_strip::kEndMarker, &parseLineStrip},
|
||||||
|
{node_line_loop::kSubtype, node_line_loop::kEndMarker, &parseLineLoop},
|
||||||
|
{node_memcell::kSubtype, node_memcell::kEndMarker, &parseMemcell},
|
||||||
|
{node_eventlauncher::kSubtype, node_eventlauncher::kEndMarker, &parseEventlauncher},
|
||||||
|
{node_sound::kSubtype, node_sound::kEndMarker, &parseSound},
|
||||||
|
}};
|
||||||
|
|
||||||
|
[[nodiscard]] inline const NodeKindDesc* findKind(
|
||||||
|
const std::string_view subtype,
|
||||||
|
const std::span<const NodeKindDesc> kinds) noexcept {
|
||||||
|
for (const NodeKindDesc& kind : kinds) {
|
||||||
|
if (isKeyword(subtype, kind.subtype)) {
|
||||||
|
return &kind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::node::io
|
||||||
53
eu07-parser/include/eu07/scene/node/line_loop.hpp
Normal file
53
eu07-parser/include/eu07/scene/node/line_loop.hpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeLineLoop {
|
||||||
|
NodeHeader header;
|
||||||
|
MaterialRgb color;
|
||||||
|
double thickness = 0.0;
|
||||||
|
std::vector<Vec3> points;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_line_loop {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "line_loop";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endline_loop";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeLineLoop& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
if (!node::io::takeDouble(stream, raw, out.color.r) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.color.g) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.color.b) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.thickness)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
Vec3 point;
|
||||||
|
if (!node::io::takeVec3(stream, raw, point)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.points.push_back(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_line_loop
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
53
eu07-parser/include/eu07/scene/node/line_strip.hpp
Normal file
53
eu07-parser/include/eu07/scene/node/line_strip.hpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeLineStrip {
|
||||||
|
NodeHeader header;
|
||||||
|
MaterialRgb color;
|
||||||
|
double thickness = 0.0;
|
||||||
|
std::vector<Vec3> points;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_line_strip {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "line_strip";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endline_strip";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeLineStrip& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
if (!node::io::takeDouble(stream, raw, out.color.r) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.color.g) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.color.b) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.thickness)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
Vec3 point;
|
||||||
|
if (!node::io::takeVec3(stream, raw, point)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.points.push_back(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_line_strip
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
61
eu07-parser/include/eu07/scene/node/lines.hpp
Normal file
61
eu07-parser/include/eu07/scene/node/lines.hpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node (lines)
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct LineSegment {
|
||||||
|
Vec3 a;
|
||||||
|
Vec3 b;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedNodeLines {
|
||||||
|
NodeHeader header;
|
||||||
|
MaterialRgb color;
|
||||||
|
double thickness = 0.0;
|
||||||
|
std::vector<LineSegment> segments;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_lines {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "lines";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endlines";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeLines& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
if (!node::io::takeDouble(stream, raw, out.color.r) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.color.g) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.color.b) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.thickness)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
LineSegment segment;
|
||||||
|
if (!node::io::takeVec3(stream, raw, segment.a) || !node::io::takeVec3(stream, raw, segment.b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.segments.push_back(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_lines
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
56
eu07-parser/include/eu07/scene/node/memcell.hpp
Normal file
56
eu07-parser/include/eu07/scene/node/memcell.hpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeMemcell {
|
||||||
|
NodeHeader header;
|
||||||
|
Vec3 position;
|
||||||
|
std::string command;
|
||||||
|
double value1 = 0.0;
|
||||||
|
double value2 = 0.0;
|
||||||
|
std::optional<std::string> trackName;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_memcell {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "memcell";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endmemcell";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeMemcell& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
std::string trackRaw;
|
||||||
|
if (!node::io::takeVec3(stream, raw, out.position) ||
|
||||||
|
!node::io::takeString(stream, raw, out.command) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.value1) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.value2) ||
|
||||||
|
!node::io::takeString(stream, raw, trackRaw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isKeyword(trackRaw, "none")) {
|
||||||
|
out.trackName = std::move(trackRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_memcell
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
45
eu07-parser/include/eu07/scene/node/mesh.hpp
Normal file
45
eu07-parser/include/eu07/scene/node/mesh.hpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Wspolne parsowanie siatki: triangle_strip, triangle_fan
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/triangles.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::node::io {
|
||||||
|
|
||||||
|
template <typename Parsed>
|
||||||
|
[[nodiscard]] inline bool parseTexturedMeshBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
const std::string_view subtype,
|
||||||
|
const std::string_view endMarker,
|
||||||
|
Parsed& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
if (!takeString(stream, raw, out.texture)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stream.empty() && !atEnd(stream, subtype, endMarker)) {
|
||||||
|
MeshVertex vertex;
|
||||||
|
const bool hasMore = stream.remaining() > 1;
|
||||||
|
if (!node_triangles::takeVertex(stream, raw, vertex, hasMore)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.vertices.push_back(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumeEnd(stream, raw, subtype, endMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::node::io
|
||||||
175
eu07-parser/include/eu07/scene/node/model.hpp
Normal file
175
eu07-parser/include/eu07/scene/node/model.hpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node::model
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeModel {
|
||||||
|
NodeHeader header;
|
||||||
|
Vec3 location;
|
||||||
|
double rotationY = 0.0;
|
||||||
|
std::string modelPath;
|
||||||
|
std::string replaceableSkin;
|
||||||
|
std::optional<std::vector<double>> lightStates;
|
||||||
|
std::optional<Vec3> angles;
|
||||||
|
std::optional<std::vector<MaterialRgb>> lightColors;
|
||||||
|
std::optional<Vec3> inlineScale;
|
||||||
|
bool noTransition = false;
|
||||||
|
std::vector<std::string> extra;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_model {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "model";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endmodel";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeLightColor(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
MaterialRgb& color) {
|
||||||
|
SourceToken token;
|
||||||
|
if (!node::io::takeToken(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (const std::optional<double> value = node::io::parseDouble(token.value)) {
|
||||||
|
color.r = *value;
|
||||||
|
SourceToken g;
|
||||||
|
SourceToken b;
|
||||||
|
if (!node::io::takeToken(stream, raw, g) || !node::io::takeToken(stream, raw, b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (const std::optional<double> gv = node::io::parseDouble(g.value)) {
|
||||||
|
color.g = *gv;
|
||||||
|
}
|
||||||
|
if (const std::optional<double> bv = node::io::parseDouble(b.value)) {
|
||||||
|
color.b = *bv;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const int packed = std::stoi(token.value, nullptr, 16);
|
||||||
|
color.r = static_cast<double>((packed >> 16) & 0xFF);
|
||||||
|
color.g = static_cast<double>((packed >> 8) & 0xFF);
|
||||||
|
color.b = static_cast<double>(packed & 0xFF);
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeModel& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
if (!node::io::takeVec3(stream, raw, out.location) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.rotationY) ||
|
||||||
|
!node::io::takeString(stream, raw, out.modelPath) ||
|
||||||
|
!node::io::takeString(stream, raw, out.replaceableSkin)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
const std::string_view value = stream.peek().value;
|
||||||
|
|
||||||
|
if (isKeyword(value, "lights")) {
|
||||||
|
SourceToken kw;
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<double> states;
|
||||||
|
while (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
const std::string_view next = stream.peek().value;
|
||||||
|
if (isKeyword(next, "angles") || isKeyword(next, "lightcolors") ||
|
||||||
|
isKeyword(next, "notransition") || isKeyword(next, "scale")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
double state = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, state)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
states.push_back(state);
|
||||||
|
}
|
||||||
|
out.lightStates = std::move(states);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isKeyword(value, "angles")) {
|
||||||
|
SourceToken kw;
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Vec3 angles;
|
||||||
|
if (!node::io::takeVec3(stream, raw, angles)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.angles = angles;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isKeyword(value, "lightcolors")) {
|
||||||
|
SourceToken kw;
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<MaterialRgb> colors;
|
||||||
|
while (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
const std::string_view next = stream.peek().value;
|
||||||
|
if (isKeyword(next, "notransition") || isKeyword(next, "scale")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
MaterialRgb color;
|
||||||
|
if (!takeLightColor(stream, raw, color)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
colors.push_back(color);
|
||||||
|
}
|
||||||
|
out.lightColors = std::move(colors);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isKeyword(value, "scale")) {
|
||||||
|
SourceToken kw;
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Vec3 scale;
|
||||||
|
if (!node::io::takeVec3(stream, raw, scale)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.inlineScale = scale;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isKeyword(value, "notransition")) {
|
||||||
|
SourceToken kw;
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.noTransition = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceToken token;
|
||||||
|
if (!node::io::takeToken(stream, raw, token)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out.extra.push_back(token.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_model
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
165
eu07-parser/include/eu07/scene/node/parse_util.hpp
Normal file
165
eu07-parser/include/eu07/scene/node/parse_util.hpp
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/parser.hpp>
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/include_resolve.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene::node::io {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isEndMarkerFor(
|
||||||
|
const std::string_view token,
|
||||||
|
const std::string_view subtype,
|
||||||
|
const std::string_view endMarker) noexcept {
|
||||||
|
if (isKeyword(token, endMarker)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(subtype, "triangle_strip") && isKeyword(token, "endtri")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(subtype, "triangles") &&
|
||||||
|
(isKeyword(token, "endtri") || isKeyword(token, "endtriangles"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(subtype, "triangle_fan") && isKeyword(token, "endtri")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(subtype, "lines") &&
|
||||||
|
(isKeyword(token, "endlines") || isKeyword(token, "endline"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(subtype, "line_strip") &&
|
||||||
|
(isKeyword(token, "endline_strip") || isKeyword(token, "endline"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(subtype, "line_loop") &&
|
||||||
|
(isKeyword(token, "endline_loop") || isKeyword(token, "endline"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(subtype, "eventlauncher") &&
|
||||||
|
(isKeyword(token, "end") || isKeyword(token, endMarker))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void appendRaw(std::vector<SourceToken>& raw, const SourceToken& token) {
|
||||||
|
raw.push_back(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isMissingNumericToken(const std::string_view text) noexcept {
|
||||||
|
return text.empty() || isKeywordIgnoreCase(text, "none") ||
|
||||||
|
isKeywordIgnoreCase(text, "undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline std::optional<double> parseDouble(const std::string_view text) {
|
||||||
|
if (isMissingNumericToken(text)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::size_t used = 0;
|
||||||
|
const double value = std::stod(std::string(text), &used);
|
||||||
|
if (used != text.size()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
} catch (...) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeToken(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
SourceToken& out) {
|
||||||
|
if (stream.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = stream.consume();
|
||||||
|
appendRaw(raw, out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeString(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
std::string& out) {
|
||||||
|
SourceToken token;
|
||||||
|
if (!takeToken(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = token.value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeDouble(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
double& out) {
|
||||||
|
SourceToken token;
|
||||||
|
if (!takeToken(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::optional<double> value = parseDouble(token.value);
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = *value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeDoubleOrPlaceholder(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
double& out) {
|
||||||
|
SourceToken token;
|
||||||
|
if (!takeToken(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (const std::optional<double> value = parseDouble(token.value)) {
|
||||||
|
out = *value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::uint8_t paramIndex = 0;
|
||||||
|
if (detail::parseIncludeParameterIndex(token.value, paramIndex)) {
|
||||||
|
out = 0.0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeVec3(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
Vec3& out) {
|
||||||
|
return takeDouble(stream, raw, out.x) && takeDouble(stream, raw, out.y) &&
|
||||||
|
takeDouble(stream, raw, out.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool atEnd(
|
||||||
|
TokenStream& stream,
|
||||||
|
const std::string_view subtype,
|
||||||
|
const std::string_view endMarker) {
|
||||||
|
return !stream.empty() && isEndMarkerFor(stream.peek().value, subtype, endMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool consumeEnd(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const std::string_view subtype,
|
||||||
|
const std::string_view endMarker) {
|
||||||
|
if (!atEnd(stream, subtype, endMarker)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SourceToken token;
|
||||||
|
return takeToken(stream, raw, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::node::io
|
||||||
74
eu07-parser/include/eu07/scene/node/shared.hpp
Normal file
74
eu07-parser/include/eu07/scene/node/shared.hpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
enum class NodeVisibilityMode {
|
||||||
|
Visible,
|
||||||
|
Hidden,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TractionMaterialKind {
|
||||||
|
Copper,
|
||||||
|
Aluminum,
|
||||||
|
None,
|
||||||
|
Raw,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TrainsetContext {
|
||||||
|
std::string name;
|
||||||
|
std::string track;
|
||||||
|
double consistOffset = 0.0;
|
||||||
|
double velocity = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CouplingSpec {
|
||||||
|
std::string raw = "3";
|
||||||
|
int type = 3;
|
||||||
|
std::string params;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline CouplingSpec parseCouplingSpec(const std::string& raw) {
|
||||||
|
CouplingSpec out;
|
||||||
|
out.raw = raw;
|
||||||
|
const std::size_t dot = raw.find('.');
|
||||||
|
const std::string typeToken = dot == std::string::npos ? raw : raw.substr(0, dot);
|
||||||
|
try {
|
||||||
|
out.type = std::stoi(typeToken);
|
||||||
|
} catch (...) {
|
||||||
|
out.type = 3;
|
||||||
|
}
|
||||||
|
if (dot != std::string::npos) {
|
||||||
|
out.params = raw.substr(dot + 1);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TractionMaterialKind parseTractionMaterial(const std::string_view token) {
|
||||||
|
if (isKeyword(token, "none")) {
|
||||||
|
return TractionMaterialKind::None;
|
||||||
|
}
|
||||||
|
if (isKeyword(token, "al")) {
|
||||||
|
return TractionMaterialKind::Aluminum;
|
||||||
|
}
|
||||||
|
if (isKeyword(token, "cu") || isKeyword(token, "miedz") || isKeyword(token, "miedź")) {
|
||||||
|
return TractionMaterialKind::Copper;
|
||||||
|
}
|
||||||
|
return TractionMaterialKind::Raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline NodeVisibilityMode parseNodeVisibility(const std::string_view token) noexcept {
|
||||||
|
if (isKeyword(token, "vis")) {
|
||||||
|
return NodeVisibilityMode::Visible;
|
||||||
|
}
|
||||||
|
return NodeVisibilityMode::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
40
eu07-parser/include/eu07/scene/node/sound.hpp
Normal file
40
eu07-parser/include/eu07/scene/node/sound.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeSound {
|
||||||
|
NodeHeader header;
|
||||||
|
Vec3 position;
|
||||||
|
std::string wavFile;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_sound {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "sound";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endsound";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeSound& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
return node::io::takeVec3(stream, raw, out.position) &&
|
||||||
|
node::io::takeString(stream, raw, out.wavFile) &&
|
||||||
|
node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_sound
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
736
eu07-parser/include/eu07/scene/node/track.hpp
Normal file
736
eu07-parser/include/eu07/scene/node/track.hpp
Normal file
@@ -0,0 +1,736 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node::track
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
enum class TrackVisibilityMode {
|
||||||
|
Visible,
|
||||||
|
Hidden,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TrackBezier {
|
||||||
|
Vec3 p1;
|
||||||
|
double roll1 = 0.0;
|
||||||
|
Vec3 cv1;
|
||||||
|
Vec3 cv2;
|
||||||
|
Vec3 p2;
|
||||||
|
double roll2 = 0.0;
|
||||||
|
double radius = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TrackSleeperModel {
|
||||||
|
double spacing = 0.0;
|
||||||
|
std::string model;
|
||||||
|
std::string texture;
|
||||||
|
double offsetLateral = 0.0;
|
||||||
|
double offsetAlong = 0.0;
|
||||||
|
double offsetVertical = 0.0;
|
||||||
|
double bedLowering = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TrackEvents {
|
||||||
|
std::optional<std::string> occupiedStop;
|
||||||
|
std::optional<std::string> occupiedEnterP1;
|
||||||
|
std::optional<std::string> occupiedEnterP2;
|
||||||
|
std::optional<std::string> anyStop;
|
||||||
|
std::optional<std::string> anyEnterP1;
|
||||||
|
std::optional<std::string> anyEnterP2;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TrackOptionals {
|
||||||
|
std::optional<double> velocity;
|
||||||
|
std::optional<double> overhead;
|
||||||
|
std::optional<std::string> railProfile;
|
||||||
|
std::optional<std::string> trackBed;
|
||||||
|
std::optional<double> frictionOverride;
|
||||||
|
std::optional<double> verticalRadius;
|
||||||
|
std::optional<double> angle1;
|
||||||
|
std::optional<double> angle2;
|
||||||
|
std::optional<std::string> fouling1;
|
||||||
|
std::optional<std::string> fouling2;
|
||||||
|
std::optional<TrackSleeperModel> sleeperModel;
|
||||||
|
TrackEvents events;
|
||||||
|
std::vector<std::string> isolated;
|
||||||
|
std::vector<std::pair<std::string, std::string>> extraKeywords;
|
||||||
|
};
|
||||||
|
|
||||||
|
// normal / turn / table — szyny + podsypka
|
||||||
|
struct NormalRailVisibility {
|
||||||
|
std::string railTexture;
|
||||||
|
double railTexLength = 0.0;
|
||||||
|
std::string ballastTexture;
|
||||||
|
double ballastHeight = 0.0;
|
||||||
|
double ballastWidth = 0.0;
|
||||||
|
double ballastSlope = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedTrackNormal {
|
||||||
|
NodeHeader header;
|
||||||
|
std::string trajectoryKind;
|
||||||
|
double length = 0.0;
|
||||||
|
double gauge = 0.0;
|
||||||
|
double friction = 0.0;
|
||||||
|
double soundDist = 0.0;
|
||||||
|
int quality = 0;
|
||||||
|
int damageFlag = 0;
|
||||||
|
std::string environment;
|
||||||
|
TrackVisibilityMode visibilityMode = TrackVisibilityMode::Hidden;
|
||||||
|
std::optional<NormalRailVisibility> visibility;
|
||||||
|
std::vector<TrackBezier> beziers;
|
||||||
|
TrackOptionals optionals;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
// switch — szyny toru zasadniczego + toru zwrotnego
|
||||||
|
struct SwitchRailVisibility {
|
||||||
|
std::string mainRailTexture;
|
||||||
|
double mainRailTexLength = 0.0;
|
||||||
|
std::string switchRailTexture;
|
||||||
|
double unusedHeight = 0.0;
|
||||||
|
double unusedWidth = 0.0;
|
||||||
|
double unusedSlope = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedTrackSwitch {
|
||||||
|
NodeHeader header;
|
||||||
|
double length = 0.0;
|
||||||
|
double gauge = 0.0;
|
||||||
|
double friction = 0.0;
|
||||||
|
double soundDist = 0.0;
|
||||||
|
int quality = 0;
|
||||||
|
int damageFlag = 0;
|
||||||
|
std::string environment;
|
||||||
|
TrackVisibilityMode visibilityMode = TrackVisibilityMode::Hidden;
|
||||||
|
std::optional<SwitchRailVisibility> visibility;
|
||||||
|
std::vector<TrackBezier> beziers;
|
||||||
|
TrackOptionals optionals;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
// road — nawierzchnia + pobocze/chodnik
|
||||||
|
struct RoadSurfaceVisibility {
|
||||||
|
std::string surfaceTexture;
|
||||||
|
double surfaceTexLength = 0.0;
|
||||||
|
std::string shoulderTexture;
|
||||||
|
double shoulderHeight = 0.0;
|
||||||
|
double shoulderWidth = 0.0;
|
||||||
|
double shoulderSlope = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedTrackRoad {
|
||||||
|
NodeHeader header;
|
||||||
|
double length = 0.0;
|
||||||
|
double roadWidth = 0.0;
|
||||||
|
double friction = 0.0;
|
||||||
|
double soundDist = 0.0;
|
||||||
|
int quality = 0;
|
||||||
|
int damageFlag = 0;
|
||||||
|
std::string environment;
|
||||||
|
TrackVisibilityMode visibilityMode = TrackVisibilityMode::Hidden;
|
||||||
|
std::optional<RoadSurfaceVisibility> visibility;
|
||||||
|
std::vector<TrackBezier> beziers;
|
||||||
|
TrackOptionals optionals;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
// cross — jak droga, ale zwykle dwie krzywe
|
||||||
|
struct ParsedTrackCross {
|
||||||
|
NodeHeader header;
|
||||||
|
double length = 0.0;
|
||||||
|
double roadWidth = 0.0;
|
||||||
|
double friction = 0.0;
|
||||||
|
double soundDist = 0.0;
|
||||||
|
int quality = 0;
|
||||||
|
int damageFlag = 0;
|
||||||
|
std::string environment;
|
||||||
|
TrackVisibilityMode visibilityMode = TrackVisibilityMode::Hidden;
|
||||||
|
std::optional<RoadSurfaceVisibility> visibility;
|
||||||
|
std::vector<TrackBezier> beziers;
|
||||||
|
TrackOptionals optionals;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
// river / tributary / turn / table / nieznany typ
|
||||||
|
struct RiverVisibility {
|
||||||
|
std::string waterTexture;
|
||||||
|
double waterTexLength = 0.0;
|
||||||
|
std::string bankTexture;
|
||||||
|
double bankHeight = 0.0;
|
||||||
|
double leftBankWidth = 0.0;
|
||||||
|
double rightBankWidth = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedTrackOther {
|
||||||
|
NodeHeader header;
|
||||||
|
std::string trajectoryKind;
|
||||||
|
double length = 0.0;
|
||||||
|
double width = 0.0;
|
||||||
|
double friction = 0.0;
|
||||||
|
double soundDist = 0.0;
|
||||||
|
int quality = 0;
|
||||||
|
int damageFlag = 0;
|
||||||
|
std::string environment;
|
||||||
|
TrackVisibilityMode visibilityMode = TrackVisibilityMode::Hidden;
|
||||||
|
std::optional<RiverVisibility> riverVisibility;
|
||||||
|
std::optional<NormalRailVisibility> railVisibility;
|
||||||
|
std::vector<TrackBezier> beziers;
|
||||||
|
TrackOptionals optionals;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_track {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "track";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endtrack";
|
||||||
|
|
||||||
|
struct CoreParams {
|
||||||
|
double length = 0.0;
|
||||||
|
double width = 0.0;
|
||||||
|
double friction = 0.0;
|
||||||
|
double soundDist = 0.0;
|
||||||
|
int quality = 0;
|
||||||
|
int damageFlag = 0;
|
||||||
|
std::string environment;
|
||||||
|
TrackVisibilityMode visibilityMode = TrackVisibilityMode::Hidden;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isEnvironment(const std::string_view token) noexcept {
|
||||||
|
return isKeywordIgnoreCase(token, "flat") || isKeywordIgnoreCase(token, "mountains") ||
|
||||||
|
isKeywordIgnoreCase(token, "mountain") || isKeywordIgnoreCase(token, "canyon") ||
|
||||||
|
isKeywordIgnoreCase(token, "tunnel") || isKeywordIgnoreCase(token, "bridge") ||
|
||||||
|
isKeywordIgnoreCase(token, "bank");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isTrackKeyword(const std::string_view token) noexcept {
|
||||||
|
return isKeyword(token, "velocity") || isKeyword(token, "isolated") ||
|
||||||
|
isKeyword(token, "overhead") || isKeyword(token, "railprofile") ||
|
||||||
|
isKeyword(token, "trackbed") || isKeyword(token, "friction") ||
|
||||||
|
isKeyword(token, "angle1") || isKeyword(token, "angle2") ||
|
||||||
|
isKeyword(token, "fouling1") || isKeyword(token, "fouling2") ||
|
||||||
|
isKeyword(token, "sleepermodel") || isKeyword(token, "vradius") ||
|
||||||
|
isKeyword(token, "event0") || isKeyword(token, "event1") ||
|
||||||
|
isKeyword(token, "event2") || isKeyword(token, "eventall0") ||
|
||||||
|
isKeyword(token, "eventall1") || isKeyword(token, "eventall2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vec3 addVec3(const Vec3& a, const Vec3& b) noexcept {
|
||||||
|
return {a.x + b.x, a.y + b.y, a.z + b.z};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wiki MaSzyna: cv1 wzgledem P1, cv2 wzgledem P2.
|
||||||
|
[[nodiscard]] inline bool takeBezier(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
TrackBezier& segment) {
|
||||||
|
if (!node::io::takeVec3(stream, raw, segment.p1) ||
|
||||||
|
!node::io::takeDouble(stream, raw, segment.roll1) ||
|
||||||
|
!node::io::takeVec3(stream, raw, segment.cv1) ||
|
||||||
|
!node::io::takeVec3(stream, raw, segment.cv2) ||
|
||||||
|
!node::io::takeVec3(stream, raw, segment.p2) ||
|
||||||
|
!node::io::takeDouble(stream, raw, segment.roll2) ||
|
||||||
|
!node::io::takeDouble(stream, raw, segment.radius)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cv1/cv2 zostaja wzgledem P1/P2 (wiki MaSzyna) — jak terenAI CubicBezier.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeSleeperModel(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
TrackSleeperModel& out) {
|
||||||
|
return node::io::takeDouble(stream, raw, out.spacing) &&
|
||||||
|
node::io::takeString(stream, raw, out.model) &&
|
||||||
|
node::io::takeString(stream, raw, out.texture) &&
|
||||||
|
node::io::takeDouble(stream, raw, out.offsetLateral) &&
|
||||||
|
node::io::takeDouble(stream, raw, out.offsetAlong) &&
|
||||||
|
node::io::takeDouble(stream, raw, out.offsetVertical) &&
|
||||||
|
node::io::takeDouble(stream, raw, out.bedLowering);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeCoreParams(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
CoreParams& out) {
|
||||||
|
std::vector<double> numerics;
|
||||||
|
while (!stream.empty() && !isEnvironment(stream.peek().value)) {
|
||||||
|
if (!node::io::parseDouble(stream.peek().value)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
double value = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
numerics.push_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numerics.size() >= 1) {
|
||||||
|
out.length = numerics[0];
|
||||||
|
}
|
||||||
|
if (numerics.size() >= 2) {
|
||||||
|
out.width = numerics[1];
|
||||||
|
}
|
||||||
|
if (numerics.size() >= 3) {
|
||||||
|
out.friction = numerics[2];
|
||||||
|
}
|
||||||
|
if (numerics.size() >= 4) {
|
||||||
|
out.soundDist = numerics[3];
|
||||||
|
}
|
||||||
|
if (numerics.size() >= 5) {
|
||||||
|
out.quality = static_cast<int>(numerics[4]);
|
||||||
|
}
|
||||||
|
if (numerics.size() >= 6) {
|
||||||
|
out.damageFlag = static_cast<int>(numerics[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node::io::takeString(stream, raw, out.environment)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string visToken;
|
||||||
|
if (!node::io::takeString(stream, raw, visToken)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(visToken, "unvis") || isKeywordIgnoreCase(visToken, "novis")) {
|
||||||
|
out.visibilityMode = TrackVisibilityMode::Hidden;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(visToken, "vis")) {
|
||||||
|
out.visibilityMode = TrackVisibilityMode::Visible;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeNormalVisibility(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
NormalRailVisibility& vis) {
|
||||||
|
return node::io::takeString(stream, raw, vis.railTexture) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.railTexLength) &&
|
||||||
|
node::io::takeString(stream, raw, vis.ballastTexture) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.ballastHeight) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.ballastWidth) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.ballastSlope);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeSwitchVisibility(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
SwitchRailVisibility& vis) {
|
||||||
|
return node::io::takeString(stream, raw, vis.mainRailTexture) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.mainRailTexLength) &&
|
||||||
|
node::io::takeString(stream, raw, vis.switchRailTexture) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.unusedHeight) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.unusedWidth) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.unusedSlope);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeRoadVisibility(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
RoadSurfaceVisibility& vis) {
|
||||||
|
return node::io::takeString(stream, raw, vis.surfaceTexture) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.surfaceTexLength) &&
|
||||||
|
node::io::takeString(stream, raw, vis.shoulderTexture) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.shoulderHeight) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.shoulderWidth) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.shoulderSlope);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeRiverVisibility(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
RiverVisibility& vis) {
|
||||||
|
return node::io::takeString(stream, raw, vis.waterTexture) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.waterTexLength) &&
|
||||||
|
node::io::takeString(stream, raw, vis.bankTexture) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.bankHeight) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.leftBankWidth) &&
|
||||||
|
node::io::takeDouble(stream, raw, vis.rightBankWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseOptionalKeyword(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const std::string_view key,
|
||||||
|
TrackOptionals& out) {
|
||||||
|
SourceToken kw;
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyword(key, "velocity")) {
|
||||||
|
double vel = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, vel)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.velocity = vel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "isolated")) {
|
||||||
|
std::string name;
|
||||||
|
if (!node::io::takeString(stream, raw, name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.isolated.push_back(std::move(name));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "overhead")) {
|
||||||
|
double value = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.overhead = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "railprofile")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.railProfile = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "trackbed")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.trackBed = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "friction")) {
|
||||||
|
double value = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.frictionOverride = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "vradius")) {
|
||||||
|
double value = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.verticalRadius = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "angle1")) {
|
||||||
|
double value = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.angle1 = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "angle2")) {
|
||||||
|
double value = 0.0;
|
||||||
|
if (!node::io::takeDouble(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.angle2 = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "fouling1")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.fouling1 = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "fouling2")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.fouling2 = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "sleepermodel")) {
|
||||||
|
TrackSleeperModel sleeper;
|
||||||
|
if (!takeSleeperModel(stream, raw, sleeper)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.sleeperModel = std::move(sleeper);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "event0")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.events.occupiedStop = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "event1")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.events.occupiedEnterP1 = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "event2")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.events.occupiedEnterP2 = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "eventall0")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.events.anyStop = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "eventall1")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.events.anyEnterP1 = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeyword(key, "eventall2")) {
|
||||||
|
std::string value;
|
||||||
|
if (!node::io::takeString(stream, raw, value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.events.anyEnterP2 = std::move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string arg;
|
||||||
|
if (!node::io::takeString(stream, raw, arg)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.extraKeywords.emplace_back(std::string(key), std::move(arg));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseTail(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
std::vector<TrackBezier>& beziers,
|
||||||
|
TrackOptionals& optionals) {
|
||||||
|
while (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
const std::string_view value = stream.peek().value;
|
||||||
|
|
||||||
|
if (isTrackKeyword(value)) {
|
||||||
|
if (!parseOptionalKeyword(stream, raw, value, optionals)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node::io::parseDouble(stream.peek().value)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackBezier segment;
|
||||||
|
if (!takeBezier(stream, raw, segment)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
beziers.push_back(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseNormalBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
const std::string& trajectoryKind,
|
||||||
|
ParsedTrackNormal& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.trajectoryKind = trajectoryKind;
|
||||||
|
|
||||||
|
CoreParams core;
|
||||||
|
if (!takeCoreParams(stream, raw, core)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.length = core.length;
|
||||||
|
out.gauge = core.width;
|
||||||
|
out.friction = core.friction;
|
||||||
|
out.soundDist = core.soundDist;
|
||||||
|
out.quality = core.quality;
|
||||||
|
out.damageFlag = core.damageFlag;
|
||||||
|
out.environment = std::move(core.environment);
|
||||||
|
out.visibilityMode = core.visibilityMode;
|
||||||
|
|
||||||
|
if (out.visibilityMode == TrackVisibilityMode::Visible) {
|
||||||
|
NormalRailVisibility vis;
|
||||||
|
if (!takeNormalVisibility(stream, raw, vis)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.visibility = std::move(vis);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTail(stream, raw, out.beziers, out.optionals);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseSwitchBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedTrackSwitch& out) {
|
||||||
|
out.header = header;
|
||||||
|
|
||||||
|
CoreParams core;
|
||||||
|
if (!takeCoreParams(stream, raw, core)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.length = core.length;
|
||||||
|
out.gauge = core.width;
|
||||||
|
out.friction = core.friction;
|
||||||
|
out.soundDist = core.soundDist;
|
||||||
|
out.quality = core.quality;
|
||||||
|
out.damageFlag = core.damageFlag;
|
||||||
|
out.environment = std::move(core.environment);
|
||||||
|
out.visibilityMode = core.visibilityMode;
|
||||||
|
|
||||||
|
if (out.visibilityMode == TrackVisibilityMode::Visible) {
|
||||||
|
SwitchRailVisibility vis;
|
||||||
|
if (!takeSwitchVisibility(stream, raw, vis)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.visibility = std::move(vis);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTail(stream, raw, out.beziers, out.optionals);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseRoadBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedTrackRoad& out) {
|
||||||
|
out.header = header;
|
||||||
|
|
||||||
|
CoreParams core;
|
||||||
|
if (!takeCoreParams(stream, raw, core)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.length = core.length;
|
||||||
|
out.roadWidth = core.width;
|
||||||
|
out.friction = core.friction;
|
||||||
|
out.soundDist = core.soundDist;
|
||||||
|
out.quality = core.quality;
|
||||||
|
out.damageFlag = core.damageFlag;
|
||||||
|
out.environment = std::move(core.environment);
|
||||||
|
out.visibilityMode = core.visibilityMode;
|
||||||
|
|
||||||
|
if (out.visibilityMode == TrackVisibilityMode::Visible) {
|
||||||
|
RoadSurfaceVisibility vis;
|
||||||
|
if (!takeRoadVisibility(stream, raw, vis)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.visibility = std::move(vis);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTail(stream, raw, out.beziers, out.optionals);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseCrossBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedTrackCross& out) {
|
||||||
|
out.header = header;
|
||||||
|
|
||||||
|
CoreParams core;
|
||||||
|
if (!takeCoreParams(stream, raw, core)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.length = core.length;
|
||||||
|
out.roadWidth = core.width;
|
||||||
|
out.friction = core.friction;
|
||||||
|
out.soundDist = core.soundDist;
|
||||||
|
out.quality = core.quality;
|
||||||
|
out.damageFlag = core.damageFlag;
|
||||||
|
out.environment = std::move(core.environment);
|
||||||
|
out.visibilityMode = core.visibilityMode;
|
||||||
|
|
||||||
|
if (out.visibilityMode == TrackVisibilityMode::Visible) {
|
||||||
|
RoadSurfaceVisibility vis;
|
||||||
|
if (!takeRoadVisibility(stream, raw, vis)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.visibility = std::move(vis);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTail(stream, raw, out.beziers, out.optionals);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseOtherBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
const std::string& trajectoryKind,
|
||||||
|
ParsedTrackOther& out,
|
||||||
|
const bool riverLike) {
|
||||||
|
out.header = header;
|
||||||
|
out.trajectoryKind = trajectoryKind;
|
||||||
|
|
||||||
|
CoreParams core;
|
||||||
|
if (!takeCoreParams(stream, raw, core)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.length = core.length;
|
||||||
|
out.width = core.width;
|
||||||
|
out.friction = core.friction;
|
||||||
|
out.soundDist = core.soundDist;
|
||||||
|
out.quality = core.quality;
|
||||||
|
out.damageFlag = core.damageFlag;
|
||||||
|
out.environment = std::move(core.environment);
|
||||||
|
out.visibilityMode = core.visibilityMode;
|
||||||
|
|
||||||
|
if (out.visibilityMode == TrackVisibilityMode::Visible) {
|
||||||
|
if (riverLike) {
|
||||||
|
RiverVisibility vis;
|
||||||
|
if (!takeRiverVisibility(stream, raw, vis)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.riverVisibility = std::move(vis);
|
||||||
|
} else {
|
||||||
|
NormalRailVisibility vis;
|
||||||
|
if (!takeNormalVisibility(stream, raw, vis)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.railVisibility = std::move(vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTail(stream, raw, out.beziers, out.optionals);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isNormalLike(const std::string_view kind) noexcept {
|
||||||
|
return isKeyword(kind, "normal") || isKeyword(kind, "turn") || isKeyword(kind, "table");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool isRiverLike(const std::string_view kind) noexcept {
|
||||||
|
return isKeyword(kind, "river") || isKeyword(kind, "tributary");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_track
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
100
eu07-parser/include/eu07/scene/node/traction.hpp
Normal file
100
eu07-parser/include/eu07/scene/node/traction.hpp
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node::traction
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/shared.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeTraction {
|
||||||
|
NodeHeader header;
|
||||||
|
std::string powerSourceName;
|
||||||
|
double nominalVoltage = 0.0;
|
||||||
|
double maxCurrent = 0.0;
|
||||||
|
double resistivity = 0.0;
|
||||||
|
TractionMaterialKind materialKind = TractionMaterialKind::Copper;
|
||||||
|
std::string materialRaw;
|
||||||
|
double wireThickness = 0.0;
|
||||||
|
int damageFlag = 0;
|
||||||
|
Vec3 wireLowerStart;
|
||||||
|
Vec3 wireLowerEnd;
|
||||||
|
Vec3 wireUpperStart;
|
||||||
|
Vec3 wireUpperEnd;
|
||||||
|
double minHeight = 0.0;
|
||||||
|
double segmentLength = 0.0;
|
||||||
|
int wires = 0;
|
||||||
|
double wireOffset = 0.0;
|
||||||
|
NodeVisibilityMode visibilityMode = NodeVisibilityMode::Hidden;
|
||||||
|
std::optional<std::string> parallel;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_traction {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "traction";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endtraction";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeTraction& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
double damageFlag = 0.0;
|
||||||
|
double wires = 0.0;
|
||||||
|
if (!node::io::takeString(stream, raw, out.powerSourceName) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.nominalVoltage) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.maxCurrent) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.resistivity) ||
|
||||||
|
!node::io::takeString(stream, raw, out.materialRaw) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.wireThickness) ||
|
||||||
|
!node::io::takeDouble(stream, raw, damageFlag) ||
|
||||||
|
!node::io::takeVec3(stream, raw, out.wireLowerStart) ||
|
||||||
|
!node::io::takeVec3(stream, raw, out.wireLowerEnd) ||
|
||||||
|
!node::io::takeVec3(stream, raw, out.wireUpperStart) ||
|
||||||
|
!node::io::takeVec3(stream, raw, out.wireUpperEnd) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.minHeight) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.segmentLength) ||
|
||||||
|
!node::io::takeDouble(stream, raw, wires) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.wireOffset)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.damageFlag = static_cast<int>(damageFlag);
|
||||||
|
out.wires = static_cast<int>(wires);
|
||||||
|
out.materialKind = parseTractionMaterial(out.materialRaw);
|
||||||
|
|
||||||
|
std::string vis;
|
||||||
|
if (!node::io::takeString(stream, raw, vis)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.visibilityMode = parseNodeVisibility(vis);
|
||||||
|
|
||||||
|
if (!stream.empty() && isKeyword(stream.peek().value, "parallel")) {
|
||||||
|
SourceToken kw;
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string name;
|
||||||
|
if (!node::io::takeString(stream, raw, name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.parallel = std::move(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_traction
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
80
eu07-parser/include/eu07/scene/node/traction_power.hpp
Normal file
80
eu07-parser/include/eu07/scene/node/traction_power.hpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl — node tractionpowersource (world/TractionPower.cpp)
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeTractionPowerSource {
|
||||||
|
NodeHeader header;
|
||||||
|
Vec3 position;
|
||||||
|
double nominalVoltage = 0.0;
|
||||||
|
double voltageFrequency = 0.0;
|
||||||
|
double internalResistance = 0.2;
|
||||||
|
double maxOutputCurrent = 0.0;
|
||||||
|
double fastFuseTimeout = 0.0;
|
||||||
|
double fastFuseRepetition = 0.0;
|
||||||
|
double slowFuseTimeout = 0.0;
|
||||||
|
bool recuperation = false;
|
||||||
|
bool section = false;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_traction_power {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "tractionpowersource";
|
||||||
|
inline constexpr std::string_view kEndMarker = "end";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeTractionPowerSource& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
out.recuperation = false;
|
||||||
|
out.section = false;
|
||||||
|
|
||||||
|
if (!node::io::takeVec3(stream, raw, out.position) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.nominalVoltage) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.voltageFrequency) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.internalResistance) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.maxOutputCurrent) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.fastFuseTimeout) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.fastFuseRepetition) ||
|
||||||
|
!node::io::takeDouble(stream, raw, out.slowFuseTimeout)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stream.empty()) {
|
||||||
|
std::string token;
|
||||||
|
if (!node::io::takeString(stream, raw, token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isKeyword(token, kEndMarker)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(token, "recuperation")) {
|
||||||
|
out.recuperation = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isKeywordIgnoreCase(token, "section")) {
|
||||||
|
out.section = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_traction_power
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
36
eu07-parser/include/eu07/scene/node/triangle_fan.hpp
Normal file
36
eu07-parser/include/eu07/scene/node/triangle_fan.hpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node
|
||||||
|
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/mesh.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeTriangleFan {
|
||||||
|
NodeHeader header;
|
||||||
|
std::string texture;
|
||||||
|
std::vector<MeshVertex> vertices;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_triangle_fan {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "triangle_fan";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endtriangle_fan";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeTriangleFan& out) {
|
||||||
|
return node::io::parseTexturedMeshBody(stream, raw, header, kSubtype, kEndMarker, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_triangle_fan
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
36
eu07-parser/include/eu07/scene/node/triangle_strip.hpp
Normal file
36
eu07-parser/include/eu07/scene/node/triangle_strip.hpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node
|
||||||
|
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/mesh.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeTriangleStrip {
|
||||||
|
NodeHeader header;
|
||||||
|
std::string texture;
|
||||||
|
std::vector<MeshVertex> vertices;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_triangle_strip {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "triangle_strip";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endtriangle_strip";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeTriangleStrip& out) {
|
||||||
|
return node::io::parseTexturedMeshBody(stream, raw, header, kSubtype, kEndMarker, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_triangle_strip
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
122
eu07-parser/include/eu07/scene/node/triangles.hpp
Normal file
122
eu07-parser/include/eu07/scene/node/triangles.hpp
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Obiekt_node::triangles
|
||||||
|
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/header.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct ParsedNodeTriangles {
|
||||||
|
NodeHeader header;
|
||||||
|
NodeMaterial material;
|
||||||
|
std::string texture;
|
||||||
|
std::vector<MeshVertex> vertices;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace node_triangles {
|
||||||
|
|
||||||
|
inline constexpr std::string_view kSubtype = "triangles";
|
||||||
|
inline constexpr std::string_view kEndMarker = "endtriangles";
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseMaterial(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
NodeMaterial& material) {
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "material")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SourceToken kw;
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto matchesMaterialLabel = [](const std::string_view token, const std::string_view label) noexcept {
|
||||||
|
if (isKeyword(token, label)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// MaSzyna tokenizuje "ambient:" jako jeden token (scenenode.cpp).
|
||||||
|
return token.size() == label.size() + 1 && token.back() == ':' &&
|
||||||
|
isKeyword(token.substr(0, label.size()), label);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto takeComponent = [&](const std::string_view label, MaterialRgb& rgb) -> bool {
|
||||||
|
if (stream.empty() || !matchesMaterialLabel(stream.peek().value, label)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!node::io::takeToken(stream, raw, kw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return node::io::takeDouble(stream, raw, rgb.r) && node::io::takeDouble(stream, raw, rgb.g) &&
|
||||||
|
node::io::takeDouble(stream, raw, rgb.b);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!takeComponent("ambient", material.ambient) || !takeComponent("diffuse", material.diffuse) ||
|
||||||
|
!takeComponent("specular", material.specular)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (stream.empty() || !isKeyword(stream.peek().value, "endmaterial")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return node::io::takeToken(stream, raw, kw);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool takeVertex(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
MeshVertex& vertex,
|
||||||
|
const bool allowEndSuffix) {
|
||||||
|
if (!node::io::takeDouble(stream, raw, vertex.x) || !node::io::takeDouble(stream, raw, vertex.y) ||
|
||||||
|
!node::io::takeDouble(stream, raw, vertex.z) || !node::io::takeDouble(stream, raw, vertex.nx) ||
|
||||||
|
!node::io::takeDouble(stream, raw, vertex.ny) || !node::io::takeDouble(stream, raw, vertex.nz) ||
|
||||||
|
!node::io::takeDouble(stream, raw, vertex.u) || !node::io::takeDouble(stream, raw, vertex.v)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (allowEndSuffix && !stream.empty() && isKeyword(stream.peek().value, "end")) {
|
||||||
|
SourceToken endToken;
|
||||||
|
if (!node::io::takeToken(stream, raw, endToken)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parseBody(
|
||||||
|
TokenStream& stream,
|
||||||
|
std::vector<SourceToken>& raw,
|
||||||
|
const NodeHeader& header,
|
||||||
|
ParsedNodeTriangles& out) {
|
||||||
|
out.header = header;
|
||||||
|
out.raw.clear();
|
||||||
|
|
||||||
|
if (!stream.empty() && isKeyword(stream.peek().value, "material")) {
|
||||||
|
if (!parseMaterial(stream, raw, out.material) ||
|
||||||
|
!node::io::takeString(stream, raw, out.texture)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!node::io::takeString(stream, raw, out.texture)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stream.empty() && !node::io::atEnd(stream, kSubtype, kEndMarker)) {
|
||||||
|
MeshVertex vertex;
|
||||||
|
const bool hasMore = stream.remaining() > 1;
|
||||||
|
if (!takeVertex(stream, raw, vertex, hasMore)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.vertices.push_back(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node::io::consumeEnd(stream, raw, kSubtype, kEndMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node_triangles
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
38
eu07-parser/include/eu07/scene/node/types.hpp
Normal file
38
eu07-parser/include/eu07/scene/node/types.hpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace eu07::scene {
|
||||||
|
|
||||||
|
struct Vec3 {
|
||||||
|
double x = 0.0;
|
||||||
|
double y = 0.0;
|
||||||
|
double z = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MeshVertex {
|
||||||
|
double x = 0.0;
|
||||||
|
double y = 0.0;
|
||||||
|
double z = 0.0;
|
||||||
|
double nx = 0.0;
|
||||||
|
double ny = 0.0;
|
||||||
|
double nz = 0.0;
|
||||||
|
double u = 0.0;
|
||||||
|
double v = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MaterialRgb {
|
||||||
|
double r = 0.0;
|
||||||
|
double g = 0.0;
|
||||||
|
double b = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NodeMaterial {
|
||||||
|
MaterialRgb ambient;
|
||||||
|
MaterialRgb diffuse;
|
||||||
|
MaterialRgb specular;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace eu07::scene
|
||||||
40
eu07-parser/include/eu07/scene/origin.hpp
Normal file
40
eu07-parser/include/eu07/scene/origin.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// https://wiki.eu07.pl/index.php?title=Dyrektywa_origin
|
||||||
|
// origin x y z … endorigin — push offsetu na stos, reszta idzie główną pętlą.
|
||||||
|
|
||||||
|
#include <eu07/scene/context.hpp>
|
||||||
|
#include <eu07/scene/cursor.hpp>
|
||||||
|
#include <eu07/scene/match.hpp>
|
||||||
|
#include <eu07/scene/node/parse_util.hpp>
|
||||||
|
#include <eu07/scene/node/types.hpp>
|
||||||
|
|
||||||
|
namespace eu07::scene::origin {
|
||||||
|
|
||||||
|
[[nodiscard]] inline bool parse(TokenStream& stream, ParseContext& ctx) {
|
||||||
|
if (stream.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::string_view kw = stream.peek().value;
|
||||||
|
if (!isKeyword(kw, "origin") && !isKeyword(kw, "orgin")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveBlock block;
|
||||||
|
block.line = stream.consume().sourceLine;
|
||||||
|
|
||||||
|
Vec3 offset;
|
||||||
|
std::vector<SourceToken> raw;
|
||||||
|
if (!node::io::takeDoubleOrPlaceholder(stream, raw, offset.x) ||
|
||||||
|
!node::io::takeDoubleOrPlaceholder(stream, raw, offset.y) ||
|
||||||
|
!node::io::takeDoubleOrPlaceholder(stream, raw, offset.z)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
block.tokens = std::move(raw);
|
||||||
|
ctx.scratch.pushOrigin(offset);
|
||||||
|
ctx.document.origin.push_back(std::move(block));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eu07::scene::origin
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user