1.16.5 Parabolic entity velocity towards location

Discussion in 'Spigot Plugin Development' started by MrDienns, May 13, 2022.

  1. Hi all,

    I'm looking for an algorithm which makes shoots my zombie from one location towards the other, in a parabolic matter. I found this thread on Bukkit, but the algorithm seems to go incorrect as soon as you start adjusting the height. I had to also adjust the gravity, as the given one doesn't seem to be correct. The height in my case is variable; it can change dynamically since the entity walks around in an area at different Y levels. So I'm looking for an algorithm that calculates a parabolic velocity from A to B, with a variable height, that's consistently accurate.

    I'm pretty terrible at math, so would appreciate small comments explaining some code samples.

    Anyone that can help?
     
  2. Do you want an algorithm that gets the velocity need to shoot the zombie from point a to point b ensuring that the zombie reaches the height you specify?
     
  3. It doesn't have to necessarily reach the specified height, but it is a variable parameter. I pretty much just need to ensure the zombie always ends on the specified location regardless of height specified.
     
  4. I dont quite understand what you mean by "height specified"

    Can you draw a 2d drawing showing what you mean?
     
  5. Did some research, seems that there is air resistance in minecraft which i didnt know.
    According to https://blog.benw.xyz/2014/01/minecraft-physics-steve-in-drag/ it seems to be 2%.
    Didn't do physics to the point where air resistance was no longer negligible so my hacky solution would be to set the zombies velocity to (their velocity.multiply(100/98)) every tick until they stop falling.
    Maybe someone has a better solution
     
  6. You can use physics formulas to determine the initial velocity needed for reaching a certain height.

    The following math is for 2d plane but since the arc of motion is not 3 dimensional this is enough - I'll use x and y, rotate around Y axis as necessary.

    KineticEnergyStartY + KineticEnergyStartX = PotentialEnergyEnd + KineticEnergyEndY + KineticEnergyEndX + EnergyLostAsDrag

    This formula tells that velocity in the beginning will be turned to height, and upon reaching peak of parabola they start falling again, so KineticEnergyEndY is 0, and can be left out.

    I'll ignore drag and X direction for now, you can adjust for those when you test your code.

    So:
    mass * velocityStartY^2 = mass * gravitational constant * height

    So velocityStartY = sqrt(gravitational constant * height)

    This will give you the needed start velocity in the Y direction to reach specific height. The blog post gives gravity constant of 32. Note that my calculations don't take into account drag so you may need to nudge the constant in the calculation.

    Calculating necessary start velocity for x is as simple as deciding distance/time. (I assume there is no air resistance in horizontal directions)

    There you go, initial bump of specific velocity and your zombie should fly in a parabola.

    If you need to change direction rotate the initial velocity vector around the Y axis.
     
    • Like Like x 1
  7. You could make this much simpler by calculating an arc from the origin to the target.
    This would then exclude all of the extra requirements that are required for a projectile. (Parabola)
     
  8. Except there's air resistance in minecraft
     
  9. This should be able to be negated using a CustomEntity(), which I presume that the OP is using.
     
  10. We're not. We're using a plain zombie that's tricked into being a player on the client.
     
  11. ↑ Example Plugin. Code snippets are taken from this ↑


    When it comes to movement in air, there are mainly three factors influencing the movement: acceleration/gravity (0.08) and drag (0.02) for the vertical movement and inertia (0.91) for the horizontal movement. To calculate the required speed, it is easiest to consider these two axes separately.

    Minecraft calculates the y velocity in three steps:

    1. Moving the entity
    2. Adding acceleration
    3. Applying drag
    To get the initial y velocity to reach the specified height, we can just reverse the process. This gives us a result where we are just a bit above the specified height.
    Code (Java):
    //calculate y velocity to reach specified height

            double startHeight = 0;
            double startVelocity = 0;
            int startTicks = 0;

            while(startHeight < height) {
                startTicks++;
                startVelocity = startVelocity / (1-drag) + acceleration;
                startHeight += startVelocity;
            }
    From this point on we use minecrafts calculation to track the height and find the last point above the target location. This gives us the exact time from start to end.
    Code (Java):
    //calculate time to end height
            double safeEndHeight = startLocation.getY() + startHeight;
            double endHeight = safeEndHeight;
            double endVelocity = 0.0;
            int endTicks = 0;

            double targetLocationHeight = selectedTarget.getY();

            while(endHeight > targetLocationHeight) {
                endTicks++;
                safeEndHeight = endHeight;
                endVelocity = (endVelocity + acceleration) * (1-drag); //not quite sure about order
                endHeight -= endVelocity;
            }

    The next part is the horizontal movement. This movement is only affected by the inertia. From this we can derive the following calculations:
    [​IMG]
    Or in Java:
    Code (Java):
    double flatVelocity = ((inertia-1) * distance) / (Math.pow(inertia, startTicks+endTicks-1) - 1);
    We already know the time we have from start to end since we alreay calculated the vertical movement and the distance can be calculated with the start and end location.
    Code (Java):
    //calculate horizontal velocity
            Vector flatEntityLocation = selectedEntity.getLocation().toVector().clone().setY(0);
            Vector flatTargetLocation = selectedTarget.clone().setY(0);

            double distance = flatEntityLocation.clone().setY(0).distance(flatTargetLocation.clone().setY(0));
            double flatVelocity = ((inertia-1) * distance) / (Math.pow(inertia, startTicks+endTicks-1) - 1);
            Vector flatVelocityVector = flatTargetLocation.clone().subtract(flatEntityLocation).normalize().multiply(flatVelocity);
    The last step is combining both velocities.
    Code (Java):
    Vector flingVelocity = flatVelocityVector.clone().setY(startVelocity);
    Problems:
    This calculations seem to be quite accurate and consistent, however there are to problems when using them.
    The first is that the last calculated y height is above the target height. Since the horizontal movement ends perfectly on the target it will furter move on this axis until the entity has touched ground. One way to solve this problem woild be to set x and z movement to 0 after the calculated length of the fling. This way the mob would just drop with its previous y velocity and land on the desired location. There are probably better mathematical ways to determine the y velocity so that it reaches the top exactly or the target exactly. However, since the velocity is calculated in steps, I dont think you can create an algoritm to fulfill both. Nevertheless, I hope to be proven wrong.
    The second problem is that the horizontal calculations only work when in air. When the mob starts on the ground the inertia is different for the first (few?) ticks. This also depends on the floor blocks. This is why the second fling in the video fails. I currently dont have a solution to this problem. One way would probably be to use NMS to tell the server that the entity is currently not touching the ground.


    If something is still unclear about the code or the explanation, I'm happy to help :)
     
    • Useful Useful x 1
  12. Thank you, this is great. Won't have time to test this out today, but I'll give this a shot the upcoming days.

    I guess this could explain a lot of things. Me and @ysl3000 were testing multiple algorithms, but all failed when jumping off the ground (at least for me). We've got a few tricks we can apply here, maybe you could suggest us what's best. We do have a way of convincing the entity that the block he's standing on is different than what the player sees. If necessary, we could also modify nms / spigot to be more consistent on this logic instead of being different per blocks. We might even be able to modify the server to not have the inertia be different for the first few ticks if we know where this is coming from.
     
  13. The different inertia basically comes from this part in the movement function of EntityLiving
    Code (Java):
    BlockPosition blockposition = this.as();
    f2 = this.world.getType(blockposition).getBlock().getFrictionFactor();
    f = this.onGround ? f2 * 0.91F : 0.91F;
    vec3d1 = this.a(vec3d, f2);
                   ...

    this.setMot(vec3d1.x * (double)f, d7 * 0.9800000190734863D, vec3d1.z * (double)f);
    As long as the minecraft determines the entity to be on the ground the inertia is reduced due to the friction factor of the ground block. I think solving this could either be done by modifying this method or the move method in Entity where onGround is being set, either by overwriting with a custom entity or modifying NMS.
    I've implemented a custom Zombie which overwrites the Entity move function and bypasses the ground check. (First video half)

    Another method which works quite well without using NMS is by offsetting / teleporting the entity on the y axis so that it is removed from the ground. When you then apply the velocity it works just as expected. (Second video half).

    I also implemented the stop of horizontal movement after the last calculated location.

    I'm not sure which of the methods is the best. Offsetting is definitely the easier way, and it works just as well as the other method, but I think there are more likely to be edge cases here where this might fail than with the other. Using NMS however will always be a bit unsafe and more work to implement and keep up to date.

    Please also keep in mind that I don't really have that much of an idea of what I'm talking about. The last time I touched Spigot was half a year ago, and I've never had more than a three times rebooted, half-finished plot management plugin and a buggy command framework. Apart from two or three weeks of Fabric tutorials, yesterday was the first time I worked with NMS, so I wouldn't consider myself the most reliable source.
    I honestly just had a boring bus ride that made me stop by the forum again.

    I nonetheless hope that I can help you with this

    Updated Plugin demonstration


    PS: Sorry for the ugly code. It got a bit out of hand during the day