Minecart system overhaul
#1
Hi everyone

So I'd like to work on the minecart system. There are several bugs, like derailing, that can only be fixed with an overhaul of their movement system. I'd like to explain why and how I'd go about it. As I am not completly familiar with the codebase I might make mistakes, so please correct me anywhere if I'm wrong. And sorry for the wall of text - I might have overdone it a bit ^^

Where in the code is the movement system implemented?
Well, obviously in Entities/Minecart.cpp. The important functions are
[1] void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk)
and
[2a] void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt)
[2b] void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta)
[2c] void cMinecart::HandleDetectorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt)
[2d] void cMinecart::HandleActivatorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt)
and
[3] void cMinecart::SnapToRail(NIBBLETYPE a_RailMeta)
and
[4a] bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta)
[4b] bool cMinecart::TestEntityCollision(NIBBLETYPE a_RailMeta)
These alone define how the movement system of minecarts works. I'll give a brief explanation of all the functions and what they do.
[1] basically wraps all the other functions. It checks if the minecart even is on a rail and if so it will call the according [2] function. Is called every tick (?)
[2a] checks on what rail direction the cart is and what its movement direction is. It accordingly decelerates the car because of friction. [2b] is more or less the same as [2a], only that it accelerates the cart (if the rail is powered ofc) rather then deccelerate it. [2c] activates the detetor rail and then calls [2a]. [2d] is not implemented yet and only calls [2a].
[3] rotates the minecart accordingly to the rail and setsthe speed opposing the rail to 0. Is called each time in [1]
[4a] and [4b] test possible collision and react followingly. [4b] is for example responsible for pushing minecarts.

How are things implemented now?
[2a]/[2b] right now adjust the speed according to the underlying block. For example: the minecart is on a powered rail with orientation -X +X. It will increase/decrease (depending of direction) SpeedX.
[3] not much to say - it just checks the railtype and then sets stuff accordingly.
[4a] checks for collision with blocks, sets the carts speed to 0
[4b] checks on what rail the cart is and in which direction the entity pushes the cart. It then moves it along this direction. Right now it uses some strange equations, see discussion on https://github.com/mc-server/MCServer/pull/1324

The problems
Yeah, the actual reason why we're actually talking about this. So here are some major and minor problems with this system:
[A] DERAILING:
This is the most important problem. When a minecart goes fast it can randomly derail in curves. Actually it can already derail from the speed of 1m/s on. The thing is there is a chance that between two calls of HandlePhysics the minecart "misses" the curve, because its speed was too great. At the next update it's not on a rail anymore, but on some other block. Please excuse my photo editing skills:
[Image: EPsVq7T.png] [Image: 4hdW1lP.png]
[B] PUSHING:
So right now as, discussed on github (https://github.com/mc-server/MCServer/pull/1324), the implementation of [4b] is a bit wanky. It isn't really documented and I'd suggest to reimplement it.
[C] ORIENTATION:
I know this is very nit-picky, but this is a bug that is actually even on vanilla! The orientation of minecarts only depends on the rail type - therefore it can change in curves. This fortunately isn't noticeable with players in it. This has no priority, but it would be of course nice-to-have ;-)
Image (too wide for forumTongue)

Proposed solutions
So let's get to the interesting part. This is how I'd go about solving the above problems:
[A]:
First of all the concept: In [2] we should analyze the surrounding tracks and see where the minecart will be at the next tick. Once we know that we move the minecart into the calculated direction rather than just in the direction of the rail.
[Image: d2PBwBS.png]
[3] (called before [2] in [1]) would then adjust the direction of the speed to the rail (with the same absolute value of the vector)

As of now [2b] is more or less a copy of [2a]. I'd change it like that: First it adds the speed and then calls [2a].
Furthermore the constants of acceleration/decceleration are set everywhere separately in the code. I'd suggest defining them at the beginning of the file. We could even make them changable through the plugin API at some point. It is not documented in the code, so I have to ask: are these constants in the current code extracted from the game? I'm not an expert on this, but it would be a nice addition.
[B]:
I'd suggest following solution: The entity creates a force in the opposing direction. The closer the entity, the greater the force. The force that is actually accounted to the minecart is only the projection on the rail axis:
[Image: SbdAU6s.png][Image: 6NK2AfR.png]
This gives the advantage that the cart cannot be pushed from the side.
If however somebody knows how this is actually handeled in vanilla (I guess one would have to decompile the code to know) this would be even better.
[C]:
This would be quite easy with the solution of [A]. Knowing the direction before a curved rail allows us to calculate the direction after the curved rail.

So... what now?
Before implementing I'd like to put forward the exact equations I'm going to be using. And before doing that I'd like to hear some of your feedback on my approach Smile
Thanks for reading through all of this!
Reply
Thanks given by:
#2
As for the Proposed solutions part A, Vanilla actually doesn't use curves, it uses straight lines even for the curved rails. This is why alternating curves produce a straight diagonal movement.

I don't think adjusting the velocity is a good solution; rather, calculate the end position, set it directly and then calculate the end speed (right-wards in your example picture) and set it directly as well. Orientation (for C) could be calculated as well.

As for the constants, definitely do pull them out of the code into the front of the file. Keep their values for now (unless you change the calculations), we can check them against vanilla later on.

It might be a good idea to actually implement "neighbor walking" - when ticking the minecart physics, start with the block the minecart's in, then continue along the neighbor into which it runs using the current velocity, then the next neighbor, and the next; update the velocity's direction on entering each neighbor, as well as add in each rail's effect (booster / brake / activate / detect / ...). Do this until the tick's worth of movement is exhausted, then set the final position, velocity and yaw into the minecart entity.
Reply
Thanks given by:
#3
You are right, setting the position each time and then setting the velocity afterwards is definitely a better solution. As for the "neighbor walking", this was about what I imagined myself (sorry if my English isn't always very clearTongue)
Reply
Thanks given by:
#4
I wonder what would be the best way to do friciton. Should it decrease the speed by a constant value or by a fraction? Should friction be calculated for each tick or for each block the minecart has passed?
Reply
Thanks given by:
#5
I would say that by a fraction for each block would seem the most realistic.
Reply
Thanks given by:
#6
Realistic as in "Looks most like real live" or realistic as in "Easiest to do"?

As far as I know friction works all the time. Not only when you are one meter further. Smile
Reply
Thanks given by:
#7
Unfortunately we shouldn't be doing the calculations too different from what Vanilla does, because the client runs its own physics simulation and if it doesn't match the server, it will make the minecart seem to jump.
Reply
Thanks given by:
#8
So, I've been using MCP to decompile the minecraft source code. These were my findings:

-The Y offset of an entity riding the car has following offset:
(double)this.height * 0.0D - 0.3;
Thats strange, why not simpily -0.3? I guess the decompiler does some strange things here

-I believe to have found the friction code (pseudocode):
if( MineCartIsRiddenByEntity ) {
  SpeedX *= 0.997;
  SpeedY *= 0.0;
  SpeedZ *= 0.997;
} else {
  SpeedX *= 0.96;
  SpeedY *= 0.0;
  SpeedZ *= 0.96;
}
I believe it's called once per tick. I don't really understand why SpeedY is set to 0. I cannot find code after this that seems to alter SpeedY. When the cart is not on a rail the friction constant seems to be 0.5 and when it is in the air, the friction constant is 0.95

-The entity collision code is there too

I'll have to spend some more time to try to figure out the stuff that's going on. With mostly undocumented code and often inexistens function/variable names (func_70495_a sounds nice for a function, does it?Tongue) it'll need some time.
Reply
Thanks given by:
#9
Hm. In reality it appears that friction in dry scenarios is constant, and in wet scenarios is proportional to velocity.

So to answer STR, the former of the choices.
Reply
Thanks given by:
#10
I think I will try to implement the friction the way i read it from the decompiled code. I'll then set up some tests and check wether it really behaves like it does in vanilla or not. Changing the friction equation afterward should be easy.

I've written some more-or-less-pseudocode how I'd like to implement this. Notice that I'd get rid of HandlePoweredRailPhysics, HandleDetectorRailPhysics and HandleActivatorRailPhysics in favour of only one function: HandleRailEffect. HandleRailEffect is called by HandleRailPhysics. HandleRailPhysics is called independent which rail type it is on.

Vector3d GetExitPoint(RailType, Speed);
// Gets the coordinates at which the cart will exit this block. Needs speed to know the direction

Vector3d GetMovingDirection(RailType, Speed);
// Returns a normalized vector. Needs speed to know the direction

void HandleRailEffect(RailType, Speed);
// Handle the effects of special rails (also includes inclined rails!)


void HandleRailPhysics()
{

      CurrentBlock = GetBlock(GetPos());
      DistanceToTravel = GetSpeed().Length();

      while (DistanceToTravel > 0)
      {

            HandleRailEffect (CurrentBlock, Speed);

            ExitDirection = GetExitPoint() - GetPos();

            if (DistanceToTravel < ExitDirection)
            // Cart halts in this block
            {
                  AddPos(GetMovingDirection() * DistanceToTravel);
                  DistanceToTravel = 0;
            }
            else
            // Cart will travel further blocks
            {
                  DistanceToTravel -= (GetExitPosition() - GetPos()).Length();
                  SetPos(GetExitPosition());
                  CurrentBlock = GetBlock(GetExitPosition() + GetMovingDirection(CurrentBlock, GetSpeed()) * 0.1);
                  // GetMovingDirection(CurrentBlock, GetSpeed()) * 0.1 is to be sure we land in the next block
                  SetSpeed(GetMovingDirection(CurrentBlock, Speed) * Speed.Length());
            }

      }

      // Handle friction, in whatever way we decide to
      Speed *= FRICTION_CONSTANT;
}

This doesn't handle yet if the rails suddenly stop. Another problem is that rail effects don't change the DistanceToTravel, but I wonder wether this is really an issue.
Reply
Thanks given by:




Users browsing this thread: 8 Guest(s)