* perf(instancing): skip a cell's buckets lying wholly beyond the range of their instances
* perf(instancing): go only through the instances drawn one by one in the per-node pass
* perf(instancing): leave out of the color pass the cells holding only instances too far to be drawn
* refactor(instancing): keep the caches of the instances of a cell without mutable fields, set by the renderer when the cell is queued
The editor writes the turntable controls on each save to <scenery>_obrotnice.scm and includes it. A copy of a
scenery, saved under another name, still included the file of the scenery it came from. Its save wrote a file of
its own and included it too, so each turntable had its events twice.
Now the save leaves out of the scenery the other files of turntable controls it includes, told by the line the
editor writes at their start. Their files stay on the disk, for the scenery they were made for. A file of
turntables made by hand, without that line, stays included.
Co-authored-by: maj00r <maj00r@host>
Running backwards, AutoRelayCheck() keeps the camshaft from joining the motors in more branches than
BackwardsBranchesAllowed (the series connection alone, by default). It did that by returning before any move of
the camshaft, so once it reached the last series position (22 of 43 in the Skoda 181 and 182, the next one joins
the motors in two branches) it stayed there whatever the driver did with the master controller: with the
controller back at zero the line contactors stayed closed and the motors kept pulling, until the line contactors
were opened by hand. Now the limit only keeps the camshaft from going further up; it goes back down as usual.
Tests: tests/unit/test_camshaft.cpp, on a locomotive made after the Skoda 181 (its RList); the test definitions
take the RList and MotorParamTable0 tables.
- the params of a shader named by the type the shader keeps them in
- ranges::any_of and erase_if, with explicit captures
- the weathering set component by component with an index of the
type of the vector
- the lookups declared in the ifs that use them
- the empty Erase of the base renderer told why it is empty
- the log stops through a flag kept in a function, not a global one
- the messages are put together with std::format
- a vehicle dropped on a trainset in the launcher goes in once the
trainset is drawn, so the vehicles are gone through with a plain loop
again; the drop split into a new vehicle and a moved one
- the scenario that can't be opened is caught as the runtime error it
is, a file name outside of the code page as the system error it is
- a track of an unknown type is left out through the pointer owning it
- the keyword of a track type written out by a function of its own
- the picked node forgotten with ranges::replace_if, the empty
Forget_Node of the base renderer told why it is empty
- FileCased takes the path it works on
* fix(physics): articulated couplers stall freight trains (eu07/maszyna#553)
Articulated wagons (T3000e, N-230, N-234) set up with CType=Articulated
barely moved in a freight train and jerked.
- The articulated joint took the spring force up to DmaxC = 0.05 m only,
225 kN, well short of its FmaxC of 850 kN and of what a loaded section
in a freight train takes. It overstretched and the sections collided at
every physics step. Dmax is now sized so the spring carries FmaxC
(0.19 m stretched, 0.09 m pressed in), never below the old 0.05 m.
- ComputeCollision() scaled the velocities of both vehicles by
sqrt(1 - beta), so each collision braked the pair as a whole: two
coupled vehicles in a sustained "collision" lost a third of their speed
per step. The restitution now applies to the velocities relative to the
centre of mass only, and the pair keeps its momentum.
Tests: the articulated joint carries FmaxC within its travel; a jerked
articulated set and a jerked screw-coupled pair keep their momentum. On
the code before the fix both pairs stop dead within 3 s.
* style(physics): English-only comment in ComputeCollision()
The comment above the collision velocities started in Polish and went on in English.
* style(physics): SonarQube findings in the #553 fix
std::lerp for the collision velocities (cpp:S6179) and test locals renamed so they no longer shadow the end and coupling enumerators (cpp:S1117).
utilities.cpp starts with a byte order mark, so the compiler reads it as utf-8. The game's targets don't get
/utf-8, though: set_target_properties(... COMPILE_OPTIONS ...) in the MSVC block of CMakeLists.txt replaces the
options add_compile_options gave them. So the string literals went into the program in the code page of the
system, windows-1250 on a polish one, and the map of win1250_to_utf8 turned each letter into itself.
What read windows-1250 through it got no utf-8: the names, descriptions and categories of the sceneries and the
names of the textures in the launcher, and the descriptions of the templates in the editor. The editor writes
such a description back through utf8_to_win1250, which makes a question mark of each byte that doesn't start
a utf-8 sequence, so the letters it had read unchanged didn't come back.
The utf-8 sequences are written as bytes now, the way utf8_to_win1250 has them, so they don't depend on the
options. The options stay as they are: with /utf-8 other literals of the files with a byte order mark would
change, the "±" of the table of Bezogonkow would take two bytes and move the letters after it.
The unit tests check the letters and that the text given is valid utf-8.
Co-authored-by: maj00r <maj00r@host>
On master neither got a segment: update_location() dereferenced the missing one of a track of an unknown type, and
a tributary went to Switch(0), which logged "Cannot switch normal track", with no segment under it either.
A tributary is now a river of its first path. Its second path is kept in m_paths, isn't drawn nor run on, and is
written back with the type "tributary". A track of an unknown type is logged and left out by the loader; as it is no
node, its text stays in the file when the editor saves the scenery.
The copies don't update themselves, their names aren't files to read again: after a change of the weather or the season the material got its new textures and its copies kept the old ones. They are made again from it then, under their handles. primitive() of an empty handle or one past the banks read out of the bank list; it gives no_primitive there, -1 from the renderer.
A scenario failing past its opening popped the loader mode and went on to pop it again and push the driver mode with the scenery half loaded; it goes back to the launcher as one that can't be opened. FileCased() goes through the folder only where the file system tells the case apart: on Windows a missing file was looked for in its whole folder for nothing, and a name outside the code page threw from the parser. The Discord thread sleeps a quarter of a second outside the driver mode, not to hold the closing up.
Matte_Material() gives a copy of a material with its glossiness damped and its weathering on: the faces cut out of a polished surface, as the sides of a worn rail head, darken and lose the shine by the userdata of their vertices. Tinted_Material() gives a copy whose texture is tinted to a colour as far instead. Both are made once per material.
The weathering and the tint colour have a param slot of their own, param[3]: the shaders of the materials use all four components of param[2] among them (detail, parallax, water, the terrain placement), and light_common.glsl is included in all of them. MAX_PARAMS grows from 3 to 4, the model UBO by 16 bytes. Neither copy is made when another param of the shader lies on the components it would set.
Erase() empties a chunk and keeps its handle to be filled again, for geometry made anew as it changes. Primitive() tells whether a chunk holds triangles or strips, -1 from a renderer which doesn't know. The VAO bank reused the buffer of a chunk of the same length even when the userdata came or went, the layout of the buffer differing then.
Deleting a model in the editor left the renderer holding it as the picked node, the next pick used freed memory: the renderer forgets it now (Forget_Node). A texture filled straight from memory (internalsrc:) can't be read again, released by the garbage sweep it came back grey; it stays now, and the units bound with the old name are rebound. The arrays of the ED brake force sharing are vectors, they leaked once per update. The scene hierarchy and the editor menu items go through the translation. Python caches ignored.
A folder without the permission to read it or a name that can't be converted ended the scan with an exception. Ctrl+Delete with no trainset selected, a dropped skin whose vehicle is gone, and a description with a % in it no longer crash the launcher.
The command line gives the name of the scenery lowercased, and the sceneries made on Windows name their files and includes in any case: on a case-sensitive filesystem they weren't found. FileCased() finds the file whose name differs only in case; the parser and the scenario loader use it. A scenario that can't be opened logs the reason and goes back to the launcher instead of ending with an exception.
The log service stopped with applicationQuitOrder, so whatever was logged while the simulator closed was lost; it now runs until StopLogService() and writes out what came before. The closing logs each of its steps with the time since it began, to tell where a closing that hangs stops. Outside the driver mode the Discord thread spun a core in its loop, it now sleeps a second between the checks.
The side sway from the lateral acceleration now goes off only on a track with a sideways value, and the random jolts on one with a vertical or a sideways value. Dips at the rail joints and cross level alone replace neither. On a switch it goes by the path set and what its joined tracks allow.
An edit in the editor also rebuilds the other switches of a joined group with the tracks they meet, and the tracks of a rail run whose joints moved. Removing a track does the same. While dragging, the tracks further off wait until the drag ends.
The clatter of a bogie's axles was right: the followers stand at the pivots and the offsets are the places of the wheel sets. The misleading name goes.
Also what Sonar had left: a parameter as a span, a const method, a struct and its member apart, commented out code, and a function of the grading tool split, with the same output.
* fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds
fix(cab): the line contactors lamp goes out with the pressure switch (eu07/maszyna#470)
The lamp of the open line contactors, which tells the start can be made, went out at 1 bar in the brake
cylinders, while the pressure switch cuts the start off only at 2 bar (or below 3.6 bar in the main pipe), so
with the brakes partly applied the start could be made with the lamp out. With a pressure switch fitted the lamp
now follows it; without one it goes out at 1 bar as before. The same holds for the lamp of the second unit, which
used to keep its last state between 1 bar and the cut-off. The condition is
TMoverParameters::LineContactorsLampLit().
Tests: tests/unit/test_line_contactors.cpp.
refactor: Sonar findings of PR 217
- horn_supply::pneumatic shadowed the pneumatic enumerator of the sound flags (cpp:S1117), it's horn_supply::air;
the supplies read with using enum (S6177),
- CouplerForce(): the automatic coupling refused or caught without another level of nested ifs (S134), the
same outcome: too fast, the coupling is locked; caught, coupled and locked; otherwise left to try again,
- CompressorCheck(): the power the compressor doesn't draw yet noted without a TODO (S1135),
- tests: the previous crash damage setting in an in-class initializer (S3230), the loops which stop early with
break rather than in their conditions (S886), the stand-ins of test::pair in a std::vector rather than an array
(S5945), units in the comments of the example without /* */ (S1103).
test: the cases the changes of the branch left without a test
- Cntrl.: BDelay1 to BDelay4 reach the brake delays (now through std::format), the default start types of the
ground and converter overload relays of a multiple unit and of other vehicles (out of the nested conditionals),
- the compressor: fed from the low voltage it waits from the low voltage coming on; the pressure switch stops it
above the cut-out pressure, without one the excess air is vented (the vented fraction rewritten),
- the horns: a vehicle without any air sounds as before, the threshold pressure, an electric horn on 110 V,
- the positions of a cab control below zero, and a control without position sounds.
refactor(cab): the kind of the train heating switch as a flag
Read once with the vehicle definition, rather than compared as text in each of the switch commands.
feat(cab): train heating switch springing back (MaSzyna-EU07/maszyna#85)
TrainHeating=[toggle]|impulse in the Switches: section of the vehicle definition makes the train heating switch
an unstable one: it goes up to switch the heating on, down to switch it off, and back to the middle once let go,
instead of staying where it was put. The position of such a switch isn't set from the heating state when the cab
is entered.
Tests: tests/unit/test_switches.cpp.
feat(sound): mirror sounds (MaSzyna-EU07/maszyna#84, eu07/maszyna#322)
The sound definitions of a vehicle (.mmd) take mirroropen: and mirrorclose:, played as a mirror sets off
unfolding or folding, and the locations section mirrors: <offset>... end, the offsets along the vehicle of the
mirror pairs, as for the doors (negative ahead of the centre), each pair at the sides of the vehicle. As the
animation, only the mirrors at the occupied cab sound. The start of a move is mirror_moves::motion in
vehicle/mirrors.h.
Tests: tests/unit/test_mirrors.cpp.
feat(cab): joint oil and fuel pump switch (MaSzyna-EU07/maszyna#81)
A cab control oilfuelpump_sw: switches both the oil and the fuel pump at once, with the new command
oilfuelpumptoggle (no key by default). As a two-state switch it turns both pumps off when both are on and both on
otherwise; as an impulse switch it's held to start them, like the separate pump switches. Its position follows
the pumps when the cab is entered.
refactor(editor): text fields filled without strncpy (MaSzyna-EU07/maszyna#112)
The last three strncpy calls Sonar flags (cpp:S5816), in the road and track editors, copy the text into the
ImGui field buffers with std::string::copy and terminate it right after; the text gets cut to the buffer as
before.
fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds
- automatic couplers don't catch above the safe collision speed (#86): with crash damage on they couple at a
closing speed of 15 km/h at most, a faster hit locks the coupling until the vehicles part and the collision
runs its course. On the way: CollisionVelocity() read a head-on 10 + 10 km/h as 14.1 km/h (quaternions turn
by half the angle), it's now taken from the touching ends; CollisionDetect() took the other vehicle's speed
the wrong way round for vehicles facing each other. Head-on hits now derail sooner, at the intended limit,
- a single horn lever can't sound both tones at once (eu07/maszyna#392),
- a skid ends once the vehicle stops on locked wheels (eu07/maszyna#479),
- electronic destination signs are blanked at the end of the timetable (eu07/maszyna#446),
- horns need the air or the voltage to sound (eu07/maszyna#290, #76): HornType=[Pneumatic]|Electric and
Horn3Type in Cntrl.; a pneumatic horn needs 0.2 MPa in the main reservoir (or the main pipe without one),
an electric one the low voltage; the lever stays where it was put,
- compressor start delay (#67): CompressorStartDelay [s] in Brake., counted from the compressor getting its
power, after the engine starts say; none by default,
- the channel and volume buttons of a radio without power do nothing (eu07/maszyna#429); the power check of
the radio is TMoverParameters::RadioPowered(),
- a lever moved smoothly sounds the positions it passes (#83).
Sonar findings in the functions changed: nesting, nested conditionals, commented-out code, std::format,
references to const. Unit tests: test::pair for two vehicles running into each other, test_couplers,
test_horn, test_wheelslip, test_compressor, test_radio, test_gauge_positions.
* fix: review findings of PR 217
- the pressures of the mover are in bar (MinCP=7.0 in the vehicle definitions), so the 0.2 the pneumatic horns
needed was 0.2 bar; it's 2 bar now, and the tests take their pressures in bar too,
- the lamps of the ground relay and of the motor overload relays follow the pressure switch as the line
contactors one does, TMoverParameters::MainCircuitLampsEnabled(), so the lamps of one cab go out together,
- the joint oil and fuel pump switch leaves out a pump started automatically, which ignores the switching:
with one, it couldn't turn the other off, and showed off with both running (OilFuelPumpsEnabled()),
- the kind of the train heating switch comes from the occupied vehicle on entering the cab too, as in the
switch commands; in a multiple unit these are different vehicles,
- CouplerForce() put the force taken back by the other vehicle on its far coupler when the vehicles faced each
other (Couplers[1 - End] rather than the coupler it's coupled with),
- the velocity difference along the coupler axis computed once, CouplerVelocityDifference(), for CouplerForce()
and CollisionVelocity(),
- the attachcoupler flag added without the SetFlag() call with 0, the index of the mirror moves noted as by side.
Tests: the coupler force of vehicles facing each other, the joint switch with an automatic pump, the lamps of
the main circuit, the horn and compressor pressures in bar.
* fix: coupler force of vehicles facing each other taken back along the other vehicle's axis
CForce runs along the axis of its own vehicle, so the force the other vehicle takes back on its coupler of the pair
is turned around once more when the vehicles face each other (-CF * DirPatch()), as that vehicle computes it itself.
Tests: the coupler forces of the pair facing each other and facing the same way.
The blockers are in the EP brake of a vehicle, older than this work: its
arrays made with new[] are vectors now, which also removes the deletes.
- The caches of the unevenness of a track are no longer mutable: what fills
them isn't const any more.
- TSegment gets back to RaPositionGet in one piece, with what the path has
to do with the others in one struct.
- Less nesting in the group of switches and in the fields of the editor,
nested conditionals written out, and the attribute is read from a
string_view. A test no longer counts in floating point.
No change in behaviour.
A plain file like baltyk/mod/grass.scm was taken for a plant and never read,
which lost about a quarter of the plants on Baltyk, so overgrown tracks came
out better than they are. The grading is also split into steps.
The rules stay in TrackUneven.h with no dependence on the engine, as they can
be tested. Their use on the tracks moves out of Track.cpp to TrackUneven.cpp,
split into steps: the group of switches, the values of its paths, the meeting
of two ends. One set of helpers for the paths of a track takes the place of
the same conditions written out in several places, and the value at the ends
of a switch is worked out in one place. The clatter of an axle leaves the long
Update of a vehicle. The fields in the editor say themselves when they show.
No change in behaviour.
- A path of no length gave a joint with a vector across of NaN to the path
joined to it, on any scenery, with unevenness or not. It now has no
vector across, and so makes no joint.
- Axles no longer stray along the track. It moved vehicles against each
other, changing the distance their couplers go by.
- The bend under an axle goes straight on past the ends of a path: the
polynomial ran off on a short curved one, with a kick in the cab shake.
- A track left out of its rail run, where tracks are joined one way only,
no longer walks the whole run again on every call.
- The clatter of an axle goes by the outer axle on its side of the vehicle,
and not always by the same one, as the two can be on different tracks.
- A vertex works out the amounts once, and the foot of the ballast not at all.
Its numbers go by position, and the positions differ between a plain track
and a switch. A switch made in the style of a plain track wrote them in the
layout of the plain one, so they were read back as other values: the up and
down part as the value of the path p3-p4, and so on.
The values are now one struct with pure functions to read, write and resolve
them, which replaces eight fields and which the unit tests can reach.
Usage:
python tools/track_uneven_grade.py scenery/lk357.scn --out graded
python tools/track_uneven_grade.py scenery/baltyk_skm1.scn --out graded --route -107,2300 -4120,2491
It follows the includes from a .scn, .scm or .inc, and takes the state of
each track from what the scenery shows: ballast and rail textures, grass
on the track, curves, speed and road crossings. The originals stay as they
are, copies of the files with tracks go to --out, and with no --out it
only reports. Values stay under what a pantograph can follow on those
tracks, by simulation, the rise of the wire itself not counted.
Tracks in .inc files taking parameters are skipped and rotate blocks not
applied, the report says so. See track_uneven_doc.md.
Usage, before endtrack, in mm, 0-100, separated by semicolons with no
spaces:
a plain track: uneven value;up;side;cross;dip
a switch: uneven p1-p2;p3-p4;up p1-p2;side p1-p2;up p3-p4;side p3-p4
uneven 18 the value alone
uneven 18;;;25;6 with its own cross level and dip at the joints
uneven 0;;30 sideways alone
uneven 14;35 a switch, 14 on the straight path, 35 on the diverging
What's left out takes the first number, the dip goes by the up and down
one, and a path p3-p4 with no numbers of its own takes those of p1-p2.
This replaces unevenup, unevenside, unevencross and jointdip. The fields
in the editor stay, track panel, section Ride and sound.
At the end they share, the paths of a switch keep to the lower value, and
each comes to its own value along the way. Joined switches take the lowest
of the same path, a path joined to another path the lower of the two, and
a plain track comes to the value of the path it meets. See
track_uneven_doc.md.
Ends less than 20 cm apart meet in the middle with a common vector
across, which closes the cracks from coordinates that don't match in the
data, on tracks with no unevenness as well. At such a joint a switch takes
the cant of the plain track, and alike ones meet halfway: the slips on
Baltyk had steps of 0.2 degrees. The physics goes by the same cant.
On such a track the sway drawn from the lateral acceleration, that is
from the curve radius, and the jolts drawn at random are off. Tracks
with no unevenness shake the cab as before. Hunting stays: it goes by the
vehicle and its speed, not by the track.
Fewer short waves: at uneven 18 the humps over 12.6 mm on the 10 m chord
go from 28 to 38 per km, with the peak still at 18 mm. Waves of 47-83 m
join them. They hardly count on that chord, the body rises and falls on
them.
The documentation tells how far unevenness goes before a pantograph
can't keep up. A wire rising fast enough trips the main breaker on a
straight track too: it does at tor0290 on Baltyk, over about 46 km/h.
Tracks laid towards each other settled to opposite sides: a step of cant
of twice the value at their joint, in the geometry and under the axles.
It's now a tilt of the plan, which reads the same from either track.
The phase of the rails was wrapped to the rail length of the track the
vehicle is on, while an axle can be on a track with longer rails. From
then on every frame looked like a joint crossed.
Usage, optional attributes of a track entry, before endtrack:
uneven <mm> peak deviation of a rail on a 10 m chord, 0-100; normal and switch
unevenup <mm> up and down alone, instead of uneven; normal and switch
unevenside <mm> sideways alone, instead of uneven; normal and switch
unevencross <mm> cross level alone, instead of uneven; normal only
jointdip <mm> depth of the dip at rail joints, every sound distance; normal only,
default 0.4 of the up and down component
Values of uneven follow PLK Id-14 appendix 1: 6 mm is the limit at 160 km/h, 10 at 120,
18 at 80, 35 at 40; half of it for a track in a good state. Example:
uneven 18
unevencross 25
jointdip 6
endtrack
The same fields are in the editor, track panel, section Ride and sound. See
track_uneven_doc.md.
The unevenness is a displacement field over the plan of the scenery, with no random
numbers: one place strays the same every time, and joined tracks, the paths of a switch,
sleepermodel and axles at one place move together. Tracks meet at a joint at the lower of
their values. Joined switches take the lowest of theirs, the tracks joined to them come to
it, and a track with none of a component keeps the switch to its design in it. The clatter
comes from the dipping joints. Curve radius and grade for the physics stay as designed.
Tracks with it get geometry every 2 m on straights: 4x the vertices on Baltyk with all
tracks flagged, 0.2 ms more per frame on td.scn.
Pulling a panel's grip into the corner of the screen pushed it a couple of
pixels past the main window, and ImGui gave it an OS window of its own. Now
a panel only comes out when dragged with the cursor outside the main window.
A panel that stays in the main window needs its grips in reach, so a resize
stops at the edge, and a panel left past the edge or too big for the window
is moved back and shrunk to fit.
This is a new io.ConfigViewportsNoAutoDetach option in our copy of ImGui, so
it has to be carried over when ImGui gets updated. ImGui itself has no option
for this, as of 1.93.1.
* fix(ai): release what the AI was holding when the player takes over
* fix(ai): keep the light selector lookup within its table, Sonar findings of the handover
- the light selector size comes from the vehicle definition (LightsList Size)
and isn't limited, while the table holds 18 positions; a bigger selector
read past the table, and so did a table index other than the front or
rear one. Only the positions the table holds are looked at now, and the
selector stays where it is for a bad table index (cpp:S2259),
- the lambdas of the handover capture what they use explicitly (cpp:S3608),
- the chained assignments resetting the headlight state on the AI taking
over are split (cpp:S1121).