mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 22:39:18 +02:00
The progressive load previously streamed the deferred visual nodes (3d model instances + terrain shapes/lines) in file order, so distant scenery could load before the player's surroundings. This builds them nearest-camera first instead. The visual pass now runs in two steps. Enumeration replays the twin and captures each visual node verbatim (its resolved tokens as text -- numbers round-trip losslessly through cParser) together with the transform/group context it was read under and, for models, its transformed world position. Once the replay is exhausted the records are sorted by squared distance to the camera (terrain shapes first so the ground appears before the props on it), then built a budgeted slice per frame through the normal node path with the captured transform and group restored -- so placement, grouping and the per-cell instance buckets come out identical to an in-order load. Two supporting fixes make out-of-order/late insertion correct: - a cell/section whose geometry was already baked (the renderer finalised it before a deferred node arrived) now appends the new shape/lines straight into its live geometry bank instead of merging into vertex-freed geometry, which would silently drop it; create_geometry() remembers the bank for every cell. - events that bind to visual model instances (lights/animation/texture/visible) are deferred from InitEvents() to a new InitInstanceEvents() run after the visual nodes are built, so their target models exist when they initialise. Verified on td.scn: playable ~2s, 540 deferred nodes enumerated and built nearest-first ~0.5s later, no duplicate instances. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
488 lines
20 KiB
C++
488 lines
20 KiB
C++
/*
|
||
This Source Code Form is subject to the
|
||
terms of the Mozilla Public License, v.
|
||
2.0. If a copy of the MPL was not
|
||
distributed with this file, You can
|
||
obtain one at
|
||
http://mozilla.org/MPL/2.0/.
|
||
*/
|
||
|
||
#pragma once
|
||
|
||
#include <vector>
|
||
#include <deque>
|
||
#include <array>
|
||
#include <stack>
|
||
#include <unordered_set>
|
||
#include <unordered_map>
|
||
#include <map>
|
||
|
||
#include "utilities/parser.h"
|
||
#include "rendering/geometrybank.h"
|
||
#include "scene/scenenode.h"
|
||
#include "world/Track.h"
|
||
#include "world/Traction.h"
|
||
#include "audio/sound.h"
|
||
#include "input/command.h"
|
||
#include "utilities/utilities.h"
|
||
|
||
class opengl_renderer;
|
||
class opengl33_renderer;
|
||
|
||
namespace scene {
|
||
|
||
int constexpr EU07_CELLSIZE = 250;
|
||
int constexpr EU07_SECTIONSIZE = 1000;
|
||
int constexpr EU07_REGIONSIDESECTIONCOUNT = 500; // number of sections along a side of square region
|
||
|
||
struct scratch_data {
|
||
|
||
struct location_data {
|
||
|
||
std::stack<glm::dvec3> offset;
|
||
// per-axis scale stack — mirrors `offset` for the `scale`/`endscale`
|
||
// scenario directives. Effective scale at any nesting depth is the
|
||
// component-wise product of all stack entries (outer `scale 2 2 2` ×
|
||
// inner `scale 1.5 1.5 1.5` yields (3,3,3)). Empty stack means (1,1,1).
|
||
std::stack<glm::vec3> scale;
|
||
glm::vec3 rotation;
|
||
} location;
|
||
|
||
struct trainset_data {
|
||
|
||
std::string name;
|
||
std::string track;
|
||
float offset { 0.f };
|
||
float velocity { 0.f };
|
||
std::vector<TDynamicObject *> vehicles;
|
||
std::vector<int> couplings;
|
||
TDynamicObject * driver { nullptr };
|
||
bool is_open { false };
|
||
std::unordered_map<std::string, std::string> assignment;
|
||
} trainset;
|
||
|
||
std::string name;
|
||
|
||
bool initialized { false };
|
||
bool time_initialized { false };
|
||
};
|
||
|
||
// basic element of rudimentary partitioning scheme for the section. fixed size, no further subdivision
|
||
// TBD, TODO: replace with quadtree scheme?
|
||
class basic_cell {
|
||
|
||
friend opengl_renderer;
|
||
friend opengl33_renderer;
|
||
|
||
public:
|
||
// constructors
|
||
basic_cell() = default;
|
||
// methods
|
||
// potentially activates event handler with the same name as provided node, and within handler activation range
|
||
void
|
||
on_click( TAnimModel const *Instance );
|
||
// legacy method, finds and assigns traction piece to specified pantograph of provided vehicle
|
||
void
|
||
update_traction( TDynamicObject *Vehicle, int const Pantographindex );
|
||
// legacy method, polls event launchers within radius around specified point
|
||
void
|
||
update_events();
|
||
// legacy method, updates sounds within radius around specified point
|
||
void
|
||
update_sounds();
|
||
// legacy method, triggers radio-stop procedure for all vehicles located on paths in the cell
|
||
void
|
||
radio_stop();
|
||
// legacy method, adds specified path to the list of pieces undergoing state change
|
||
bool
|
||
RaTrackAnimAdd( TTrack *Track );
|
||
// legacy method, updates geometry for pieces in the animation list
|
||
void
|
||
RaAnimate( unsigned int const Framestamp );
|
||
// sends content of the class to provided stream
|
||
void
|
||
serialize( std::ostream &Output ) const;
|
||
// restores content of the class from provided stream
|
||
void
|
||
deserialize( std::istream &Input );
|
||
// sends content of the class in legacy (text) format to provided stream
|
||
void
|
||
export_as_text( std::ostream &Output ) const;
|
||
// adds provided shape to the cell
|
||
void
|
||
insert( shape_node Shape );
|
||
// adds provided lines to the cell
|
||
void
|
||
insert( lines_node Lines );
|
||
// adds provided path to the cell
|
||
void
|
||
insert( TTrack *Path );
|
||
// adds provided path to the cell
|
||
void
|
||
insert( TTraction *Traction );
|
||
// adds provided model instance to the cell
|
||
void
|
||
insert( TAnimModel *Instance );
|
||
// adds provided sound instance to the cell
|
||
void
|
||
insert( sound_source *Sound );
|
||
// adds provided event launcher to the cell
|
||
void
|
||
insert( TEventLauncher *Launcher );
|
||
// adds provided memory cell to the cell
|
||
void
|
||
insert( TMemCell *Memorycell );
|
||
// registers provided path in the lookup directory of the cell
|
||
void
|
||
register_end( TTrack *Path );
|
||
// registers provided traction piece in the lookup directory of the cell
|
||
void
|
||
register_end( TTraction *Traction );
|
||
// removes provided model instance from the cell
|
||
void
|
||
erase( TAnimModel *Instance );
|
||
// removes provided memory cell from the cell
|
||
void
|
||
erase( TMemCell *Memorycell );
|
||
// find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance
|
||
std::tuple<TDynamicObject *, float>
|
||
find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ) const;
|
||
// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint
|
||
std::tuple<TTrack *, int>
|
||
find( glm::dvec3 const &Point, TTrack const *Exclude ) const;
|
||
// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint
|
||
std::tuple<TTraction *, int>
|
||
find( glm::dvec3 const &Point, TTraction const *Exclude ) const;
|
||
// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece
|
||
std::tuple<TTraction *, int, float>
|
||
find( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection ) const;
|
||
// sets center point of the cell
|
||
void
|
||
center( glm::dvec3 Center );
|
||
// generates renderable version of held non-instanced geometry in specified geometry bank
|
||
void
|
||
create_geometry( gfx::geometrybank_handle const &Bank );
|
||
void
|
||
create_map_geometry(std::vector<gfx::basic_vertex> &Bank, const gfx::geometrybank_handle Extra);
|
||
void
|
||
get_map_active_paths(map_colored_paths &handles);
|
||
glm::vec3 find_nearest_track_point(const glm::dvec3 &pos);
|
||
// provides access to bounding area data
|
||
bounding_area const &
|
||
area() const {
|
||
return m_area; }
|
||
//private:
|
||
// types
|
||
using path_sequence = std::vector<TTrack *>;
|
||
using shapenode_sequence = std::vector<shape_node>;
|
||
using linesnode_sequence = std::vector<lines_node>;
|
||
using traction_sequence = std::vector<TTraction *>;
|
||
using instance_sequence = std::vector<TAnimModel *>;
|
||
// Composite key: instances of the same TModel3d sharing the same replacable
|
||
// skin set can be GPU-batched together, but instances with different skins
|
||
// must go to different buckets so each batched draw call uses one consistent
|
||
// material binding. Sub-bucketing here keeps Render_Instanced correct without
|
||
// forcing per-instance material switching inside a single batch.
|
||
struct instance_bucket_key {
|
||
TModel3d *pModel { nullptr };
|
||
std::array<material_handle, 5> skins {};
|
||
|
||
bool operator==( instance_bucket_key const &other ) const {
|
||
return pModel == other.pModel && skins == other.skins;
|
||
}
|
||
};
|
||
struct instance_bucket_key_hash {
|
||
std::size_t operator()( instance_bucket_key const &k ) const {
|
||
std::size_t h = std::hash<TModel3d *>()( k.pModel );
|
||
for( auto s : k.skins ) {
|
||
// boost-style hash combine
|
||
h ^= std::hash<int>()( s ) + 0x9e3779b9 + ( h << 6 ) + ( h >> 2 );
|
||
}
|
||
return h;
|
||
}
|
||
};
|
||
using instance_bucket_map = std::unordered_map< instance_bucket_key, std::vector<TAnimModel *>, instance_bucket_key_hash >;
|
||
using sound_sequence = std::vector<sound_source *>;
|
||
using eventlauncher_sequence = std::vector<TEventLauncher *>;
|
||
using memorycell_sequence = std::vector<TMemCell *>;
|
||
// methods
|
||
void
|
||
launch_event(TEventLauncher *Launcher, bool local_only);
|
||
void
|
||
enclose_area( scene::basic_node *Node );
|
||
// members
|
||
scene::bounding_area m_area { glm::dvec3(), static_cast<float>( 0.5 * M_SQRT2 * EU07_CELLSIZE ) };
|
||
bool m_active { false }; // whether the cell holds any actual data content
|
||
shapenode_sequence m_shapesopaque; // opaque pieces of geometry
|
||
shapenode_sequence m_shapestranslucent; // translucent pieces of geometry
|
||
linesnode_sequence m_lines;
|
||
path_sequence m_paths; // path pieces
|
||
instance_sequence m_instancesopaque;
|
||
instance_sequence m_instancetranslucent;
|
||
// batched instance buckets keyed by shared TModel3d*; populated alongside
|
||
// m_instancesopaque for nodes whose TAnimModel::m_instanceable == true.
|
||
// The renderer uses these to amortise per-model state setup across many instances.
|
||
instance_bucket_map m_instancebuckets_opaque;
|
||
traction_sequence m_traction;
|
||
sound_sequence m_sounds;
|
||
eventlauncher_sequence m_eventlaunchers;
|
||
memorycell_sequence m_memorycells;
|
||
// search helpers
|
||
struct lookup_data {
|
||
path_sequence paths;
|
||
traction_sequence traction;
|
||
} m_directories;
|
||
// animation of owned items (legacy code, clean up along with track refactoring)
|
||
bool m_geometrycreated { false };
|
||
// bank this cell's geometry was baked into; remembered so shapes/lines inserted
|
||
// *after* the section was first rendered (deferred visual streaming) can be appended
|
||
// straight into the live bank instead of being silently dropped. null until the owning
|
||
// section bakes its geometry, which doubles as the "already baked" signal.
|
||
gfx::geometrybank_handle m_geometrybank {};
|
||
unsigned int m_framestamp { 0 }; // id of last rendered gfx frame
|
||
TTrack *tTrackAnim = nullptr; // obiekty do przeliczenia animacji
|
||
command_relay m_relay;
|
||
};
|
||
|
||
// basic scene partitioning structure, holds terrain geometry and collection of cells
|
||
class basic_section {
|
||
|
||
friend opengl_renderer;
|
||
friend opengl33_renderer;
|
||
|
||
public:
|
||
// constructors
|
||
basic_section() = default;
|
||
// methods
|
||
// potentially activates event handler with the same name as provided node, and within handler activation range
|
||
void
|
||
on_click( TAnimModel const *Instance );
|
||
// legacy method, finds and assigns traction piece to specified pantograph of provided vehicle
|
||
void
|
||
update_traction( TDynamicObject *Vehicle, int const Pantographindex );
|
||
// legacy method, updates sounds and polls event launchers within radius around specified point
|
||
void
|
||
update_events( glm::dvec3 const &Location, float const Radius );
|
||
// legacy method, updates sounds and polls event launchers within radius around specified point
|
||
void
|
||
update_sounds( glm::dvec3 const &Location, float const Radius );
|
||
// legacy method, triggers radio-stop procedure for all vehicles in 2km radius around specified location
|
||
void
|
||
radio_stop( glm::dvec3 const &Location, float const Radius );
|
||
// sends content of the class to provided stream
|
||
void
|
||
serialize( std::ostream &Output ) const;
|
||
// restores content of the class from provided stream
|
||
void
|
||
deserialize( std::istream &Input );
|
||
// sends content of the class in legacy (text) format to provided stream
|
||
void
|
||
export_as_text( std::ostream &Output ) const;
|
||
// adds provided shape to the section
|
||
void
|
||
insert( shape_node Shape );
|
||
// adds provided lines to the section
|
||
void
|
||
insert( lines_node Lines );
|
||
// adds provided node to the section
|
||
template <class Type_>
|
||
void
|
||
insert( Type_ *Node ) {
|
||
auto &targetcell { cell( Node->location() ) };
|
||
targetcell.insert( Node );
|
||
// some node types can extend bounding area of the target cell
|
||
m_area.radius = std::max(
|
||
m_area.radius,
|
||
static_cast<float>( glm::length( m_area.center - targetcell.area().center ) + targetcell.area().radius ) ); }
|
||
// erases provided node from the section
|
||
template <class Type_>
|
||
void
|
||
erase( Type_ *Node ) {
|
||
auto &targetcell { cell( Node->location() ) };
|
||
// TODO: re-calculate bounding area after removal
|
||
targetcell.erase( Node ); }
|
||
// registers provided node in the lookup directory of the section enclosing specified point
|
||
template <class Type_>
|
||
void
|
||
register_node( Type_ *Node, glm::dvec3 const &Point ) {
|
||
cell( Point ).register_end( Node ); }
|
||
// find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance
|
||
std::tuple<TDynamicObject *, float>
|
||
find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler );
|
||
// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint
|
||
std::tuple<TTrack *, int>
|
||
find( glm::dvec3 const &Point, TTrack const *Exclude );
|
||
// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint
|
||
std::tuple<TTraction *, int>
|
||
find( glm::dvec3 const &Point, TTraction const *Exclude );
|
||
// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece
|
||
std::tuple<TTraction *, int, float>
|
||
find( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection );
|
||
// sets center point of the section
|
||
void
|
||
center( glm::dvec3 Center );
|
||
// generates renderable version of held non-instanced geometry
|
||
void
|
||
create_geometry();
|
||
void
|
||
create_map_geometry(const gfx::geometrybank_handle handle);
|
||
void
|
||
get_map_active_paths(map_colored_paths &handles);
|
||
// provides access to bounding area data
|
||
bounding_area const &
|
||
area() const {
|
||
return m_area; }
|
||
|
||
const gfx::geometrybank_handle get_map_geometry()
|
||
{ return m_map_geometryhandle;}
|
||
glm::vec3 find_nearest_track_point(const glm::dvec3 &point);
|
||
|
||
//private:
|
||
// types
|
||
using cell_array = std::array<basic_cell, (EU07_SECTIONSIZE / EU07_CELLSIZE) * (EU07_SECTIONSIZE / EU07_CELLSIZE)>;
|
||
using shapenode_sequence = std::vector<shape_node>;
|
||
// methods
|
||
// provides access to section enclosing specified point
|
||
basic_cell &
|
||
cell(glm::dvec3 const &Location, const glm::ivec2 &offset = glm::ivec2(0));
|
||
// members
|
||
// placement and visibility
|
||
|
||
scene::bounding_area m_area { glm::dvec3(), static_cast<float>( 0.5 * M_SQRT2 * EU07_SECTIONSIZE ) };
|
||
// content
|
||
cell_array m_cells; // partitioning scheme
|
||
shapenode_sequence m_shapes; // large pieces of opaque geometry and (legacy) terrain
|
||
// TODO: implement dedicated, higher fidelity, fixed resolution terrain mesh item
|
||
// gfx renderer data
|
||
gfx::geometrybank_handle m_geometrybank;
|
||
bool m_geometrycreated { false };
|
||
|
||
gfx::geometrybank_handle m_map_geometryhandle;
|
||
};
|
||
|
||
// top-level of scene spatial structure, holds collection of sections
|
||
class basic_region {
|
||
|
||
friend opengl_renderer;
|
||
friend opengl33_renderer;
|
||
|
||
public:
|
||
// constructors
|
||
basic_region();
|
||
// destructor
|
||
~basic_region();
|
||
// methods
|
||
// potentially activates event handler with the same name as provided node, and within handler activation range
|
||
void
|
||
on_click( TAnimModel const *Instance );
|
||
// legacy method, finds and assigns traction piece to specified pantograph of provided vehicle
|
||
void
|
||
update_traction( TDynamicObject *Vehicle, int const Pantographindex );
|
||
// legacy method, polls event launchers around camera
|
||
void
|
||
update_events();
|
||
// legacy method, updates sounds around camera
|
||
void
|
||
update_sounds();
|
||
// sends content of the class in legacy (text) format to provided stream
|
||
void
|
||
export_as_text( std::ostream &Output ) const;
|
||
// legacy method, links specified path piece with potential neighbours
|
||
void
|
||
TrackJoin( TTrack *Track );
|
||
// legacy method, triggers radio-stop procedure for all vehicles in 2km radius around specified location
|
||
void
|
||
RadioStop( glm::dvec3 const &Location );
|
||
// inserts provided shape in the region
|
||
void
|
||
insert( shape_node Shape, scratch_data &Scratchpad, bool const Transform );
|
||
// inserts provided lines in the region
|
||
void
|
||
insert( lines_node Lines, scratch_data &Scratchpad );
|
||
// inserts provided node in the region
|
||
template <class Type_>
|
||
void
|
||
insert( Type_ *Node ) {
|
||
auto const location { Node->location() };
|
||
if( false == point_inside( location ) ) {
|
||
// NOTE: nodes placed outside of region boundaries are discarded
|
||
// TBD, TODO: clamp coordinates to region boundaries?
|
||
return; }
|
||
section( location ).insert( Node ); }
|
||
// inserts provided node in the region and registers its ends in lookup directory
|
||
template <class Type_>
|
||
void
|
||
insert_and_register( Type_ *Node ) {
|
||
insert( Node );
|
||
for( auto const &point : Node->endpoints() ) {
|
||
if( point_inside( point ) ) {
|
||
section( point ).register_node( Node, point ); } } }
|
||
// removes specified node from the region
|
||
template <class Type_>
|
||
void
|
||
erase( Type_ *Node ) {
|
||
auto const location{ Node->location() };
|
||
if( point_inside( location ) ) {
|
||
section( location ).erase( Node ); } }
|
||
// find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance
|
||
std::tuple<TDynamicObject *, float>
|
||
find_vehicle( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler );
|
||
// finds a path with one of its ends located in specified point. returns: located path and id of the matching endpoint
|
||
std::tuple<TTrack *, int>
|
||
find_path( glm::dvec3 const &Point, TTrack const *Exclude );
|
||
// finds a traction piece with one of its ends located in specified point. returns: located traction piece and id of the matching endpoint
|
||
std::tuple<TTraction *, int>
|
||
find_traction( glm::dvec3 const &Point, TTraction const *Exclude );
|
||
// finds a traction piece located nearest to specified point, sharing section with specified other piece and powered in specified direction. returns: located traction piece
|
||
std::tuple<TTraction *, int>
|
||
find_traction( glm::dvec3 const &Point, TTraction const *Other, int const Currentdirection );
|
||
// finds sections inside specified sphere. returns: list of sections
|
||
std::vector<basic_section *> const &
|
||
sections( glm::dvec3 const &Point, float const Radius );
|
||
void
|
||
create_map_geometry();
|
||
void
|
||
update_poi_geometry();
|
||
basic_section* get_section(size_t section)
|
||
{ return m_sections[section]; }
|
||
gfx::geometrybank_handle
|
||
get_map_poi_geometry() { return m_map_poipoints; }
|
||
glm::vec3 find_nearest_track_point(const glm::dvec3 &pos)
|
||
{ return section(pos).find_nearest_track_point(pos); }
|
||
|
||
//private:
|
||
// types
|
||
using section_array = std::array<basic_section *, sq(EU07_REGIONSIDESECTIONCOUNT)>;
|
||
|
||
struct region_scratchpad {
|
||
|
||
std::vector<basic_section *> sections;
|
||
};
|
||
|
||
gfx::geometrybank_handle m_map_geometrybank;
|
||
gfx::geometrybank_handle m_map_poipoints;
|
||
|
||
// methods
|
||
// checks whether specified point is within boundaries of the region
|
||
bool
|
||
point_inside( glm::dvec3 const &Location );
|
||
// legacy method, trims provided shape to fit into a section. adds trimmed part at the end of provided list, returns true if changes were made
|
||
static
|
||
bool
|
||
RaTriangleDivider( shape_node &Shape, std::deque<shape_node> &Shapes );
|
||
// provides access to section enclosing specified point
|
||
basic_section &
|
||
section( glm::dvec3 const &Location );
|
||
|
||
// members
|
||
section_array m_sections;
|
||
region_scratchpad m_scratchpad;
|
||
|
||
};
|
||
|
||
// global hierarchy map for scene nodes
|
||
extern std::map<std::string, basic_node *> Hierarchy;
|
||
} // scene
|
||
|
||
//---------------------------------------------------------------------------
|