Skip to content

Conversation

@gfgit
Copy link
Contributor

@gfgit gfgit commented Aug 17, 2025

Hi, this is an experiment branch.
I've added fast reload of JSON, so you can edit and see the new result instantly.
Then I've increased a bit the max zoom level and temporarily draw all segment in red because of better contrast on image background.
Image background is done with QPainter because I don't know OpenGL well enough...

Tooltips are super useful when building layout to adjust values. Code is still a bit messy.

Now about double slip switches:

  • We parse the curve. How to calculate straight segments intersections?
  • straightEnd does not support segments with 2 straights
  • We now need to know from which side we came from to calculate to which side we are going to!

@gfgit gfgit changed the base branch from master to 185-model-railway-simulator August 17, 2025 16:56
@reinder
Copy link
Member

reinder commented Aug 17, 2025

Now about double slip switches:

  • We parse the curve. How to calculate straight segments intersections?

@gfgit I thought a bit about it during the day, see image:

image

points[0] is the origin, using the angle and radius we can calculate points[1].

We also know the angle of both points, so we draw virtually two lines (red/orange) and calculate the intersection point C.
That kind of math is not really my cup of tea, but I promted ChatGPT, which came with this (untested, so it might need some adjustments/corrections):

Point calcIntersection(Point pointA, float angleA, Point pointB, float angleB)
{
  // Direction vectors
  float dxA = std::cos(angleA);
  float dyA = std::sin(angleA);
  float dxB = std::cos(angleB);
  float dyB = std::sin(angleB);

  // Solve:
  // pointA + t * dirA = pointB + s * dirB
  // Rearranged: (pointB - pointA) = t * dirA - s * dirB

  const float det = dxA * (-dyB) - dyA * (-dxB);  // determinant of 2x2 system
  if(std::fabs(det) < 1e-6f)
  {
    return invalidPoint; // Lines are parallel or nearly parallel, no unique intersection.
  }

  const float rhsX = pointB.x - pointA.x;
  const float rhsY = pointB.y - pointA.y;

  // Solve for t using Cramer's rule
  const float t = (rhsX * (-dyB) - rhsY * (-dxB)) / det;

  Point intersection;
  intersection.x = pointA.x + t * dxA;
  intersection.y = pointA.y + t * dyA;
  return intersection;
}

Using the distance of points[0] to C we can calculate points[2], same for points[1] and points[3].

  • straightEnd does not support segments with 2 straights

That needs to be modified, TrackSegment should be able to hold two straights -> std::array<Straight, 2> straights;
Similar to how it already works for curves.

We can also use those two straights for crossings, they can then be defined by a length and an angle.

  • We now need to know from which side we came from to calculate to which side we are going to!

Do you mean for the trains that are moving?


I'll take a look at the other improvements :)

@reinder reinder marked this pull request as draft August 17, 2025 21:44
@gfgit
Copy link
Contributor Author

gfgit commented Aug 18, 2025 via email

@gfgit
Copy link
Contributor Author

gfgit commented Aug 18, 2025

bug_sim_piazzale [padova_centrale_2.json](https://github.com/user-attachments/files/21839798/padova_centrale_2.json)

Hi, this are my experiments on extracting Padua station layout on top of satellite images.
There is a weird bug about train positioning:

  • Train start on "test" straight segment
  • Se turnout_17b to go left
  • Drive train trough turnout_17b, then straight on the double slip (made of 4 normal turnouts), again on 16b and other double slip.
  • Now there is a curve and here all is still fine
  • The there is turnout_15a, taken from its curve end, here train suddenly jumps forward increasing speed, then back to normal.
  • When exiting turnout (going out side 0) on the next curve it happens again briefly, then it continues normally.

Variants:

  • If you keep turnout_15a set to straight/(closed?), the train coming from curve end will jump a bit laterally of course but speed is fine.
  • If you move turnout from curve to straight and again curve while train is passing it, it will blow up and derail, as shown in the picture above. It seems not recoverable, had to reload file and start again...
  • If you keep 15a straight and come from its 0 side, train will pass it and stop at end as there is no next segment.
    Now you move to curve and train will jump out of turnout, go to double slip switch. It is still on track and able to move and... direction is now inverted!!!

Quite strange. I don't know well how to debug.

@reinder
Copy link
Member

reinder commented Aug 18, 2025

I like the maths, it was like I imagined except I had not thinked yet about the intersection algorithm. About trains we now have getNextSegment passing omly true/false. But for cross or double slip segments we need to know where we are coming from. I suggest we split in:

  1. store side we come from in vehicle face
  2. get opposite segment side (depends on segment type and state)
  3. trivially get adjacent segment on that side.

I agree, that will work, we can add an entryIndex field and use that for the getNextSegment funtion instead of the direction positive flag.

Also I've discovered a bug when 2 turnouts are connected by the curve side: sometimes train suddenly slide at crazy speed on turnout curve and then returns to it's normal speed.

I've a test layout with that too, but no issue. Can you create a minimum example? Then I'll look into it :)

I'n currently using google earth images to build Padua layout. I did 1 unit = 1 meter, hence I increased max zoom level. What happens if a segment is shorter than vehicle position delta on simulator tick?

That doesn't work currently, it only tests for one overflow, we must add a while loop around it, but that will affect occupation sensors as well. It currently works like an axle counter, but that won't work when it jumps over it in one simulator tick. I'm going to think about it, I you have an idea, its welcome :)

I'm also thinking about position sensor. They could be activated if vehicle position delta includes sensor position. This would "pulse" sensor on/off. But I need to power a relay which is slow, so I could start a 3 seconds timer which then deactivates the sensor, like real world weight sensor which have slow return to nornal position.

Or the sensor can just send a triggered message, then the consuming application can do a delay if it needs that. What if the sensor is triggered again within these 3 seconds, should it stay high aka restart the 3 sec timer?


Can you upload the JSON again?, I can't download it, link results in an error.

@reinder
Copy link
Member

reinder commented Aug 18, 2025

Changing the turnout position while a train is on it doesn't work properly indeed, still a bit in doubt how to handle that. Possible solutions:

  1. Ignore the command
  2. Wait until train is passed then change position
  3. Stop the simulation and raise an error

Also when you enter a turnout from the "curved" position while the turnout is in the "straight" position the train will make a weird jump. Possible solutions:

  1. Change turnout position
  2. Stop train
  3. Stop the simulation and raise an error

I think we should make in configurable in the JSON file. For automated testing Traintastic stuff you most likely want an error, because Traintastic made an mistake and the test should fail then. What's your opinion?

@gfgit
Copy link
Contributor Author

gfgit commented Aug 18, 2025

bug_simulator_train.mp4

This shows the bug with multiple wagons on another part of layout but same behavior.
Also could you run the windows build of this branch so I can get the binaries? Too lazy to boot windows :)

@reinder
Copy link
Member

reinder commented Aug 18, 2025

I've started the build, but it is failing on the sim.

That is indeed not correct, I'll try to reproduce and look into it. There are no short segments in between?

@reinder
Copy link
Member

reinder commented Aug 18, 2025

@gfgit can you upload your JSON file (or send it by email)?

@gfgit
Copy link
Contributor Author

gfgit commented Aug 19, 2025 via email

reinder added a commit that referenced this pull request Aug 19, 2025
…t now checks for overflows until it is within a segment, see #195.
@reinder
Copy link
Member

reinder commented Aug 19, 2025

Yes, we could add a while loop.
About axle-counter behavior I think it's fine as is.
Short tracks are just for adjusting layout shape so usually are part of same sensor as adjacent tracks.
Thus whole sensor is already occupied when train passes over a short track.

You're right they are most likely share a sensor or don't have a sensor, so that will work for common case, i've added the loop, see c4f5592

Position sensor with delay should restart the timer each time they get pressed.
So if a train runs fast over it it could happen to get pressed with fisrt axle and then stay pressed until whole train has passed, then last axle will trigger 3s delay to get up again.
If train is slow or has long wagons, the sensor might be able to get up after 3 seconds and the get pressed again by next axle.
Delay can be configured as a JSON property of sensor.
Zero delay will make it pulse on/off at each axle, so we cannot wait updateSensors() to trigger, we must switch it inside vehicle position update.
I can also add delay on consuming application if you think it's better.
Real word delay is mechanical and happens inside the sensor itself. It's basically an oil piston, going down it has a big pipe and to go back up it has a really small pipe so oil will flow slowly.

I prefer to the sensor trigger stuff in the updateSensors() function, a zero delay can then pulse for one simulator cycle. For the delay we can convert the time in seconds to cycles. Then every cycle we decrement it, once it reaches zero it becomes false again. This is easy to implement we don't need to have timer objects etc. it will however have a bit jitter due to the synchronization with the cycle time. But consuming software should be able to handle that, its more real world (model) train like :)

For turnouts in real railways there are 2 kinds of them:

  • Ones which will change position if taken from wrong side
  • Ones which will make train deray and also break the turnout mechanism.

Some turnouts in Italian railways can switch behavior based on reserved route.
We could make it a property, auto switch on train passage if true, stop train and raise error if false.

We can add a global property to control that behavior and allow overriding it on turnout level. Do you need to be able to switch that behavior at runtime as well?


Something else I thought about today, currently the simulator is part of the Traintastic installer. But as it becomes more and more an application that also can be used by other applications I think it is better to build a separate Windows installer for it.

reinder added a commit that referenced this pull request Aug 19, 2025
@gfgit
Copy link
Contributor Author

gfgit commented Aug 19, 2025 via email

@gfgit
Copy link
Contributor Author

gfgit commented Aug 19, 2025 via email

@gfgit
Copy link
Contributor Author

gfgit commented Oct 2, 2025 via email

@reinder
Copy link
Member

reinder commented Oct 2, 2025

It might be an issue with the buffers, they are only 1024 bytes currently. Did you try to enlarge them? The write function just drops messages when there is no room in the write buffer. (That should be improved, there should be at least some kind of indication that it is happening.)

@gfgit
Copy link
Contributor Author

gfgit commented Oct 3, 2025 via email

@reinder
Copy link
Member

reinder commented Oct 3, 2025

The buffers are really small currently, only 1kB. For my use case that was good enough, but for yours it isn't :)

I suggest we first enlange the buffers to e.g. 16kB. If it still happens an auto resize buffer is probably better, else we have to implement another buffering system.

Filtering is a nice option, I prefer that by default everything is send and the limiting it to certain channel is an option.

@gfgit
Copy link
Contributor Author

gfgit commented Oct 3, 2025 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants