{"id":41,"date":"2021-04-03T03:20:00","date_gmt":"2021-04-03T03:20:00","guid":{"rendered":"https:\/\/harryzhou.info\/blog\/?p=41"},"modified":"2022-10-02T07:10:21","modified_gmt":"2022-10-02T07:10:21","slug":"minecraft-projectile-launch-angle-calculation","status":"publish","type":"post","link":"https:\/\/blog.harryzhou.info\/index.php\/2021\/04\/03\/minecraft-projectile-launch-angle-calculation\/","title":{"rendered":"Minecraft Projectile Launch Angle Calculation"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">What Other People Did<\/h2>\n\n\n\n<p>For one of my projects, I needed some way to determine the launch angle of a projectile in Minecraft that would hit a specific point, so I recalled that the Wurst client, <a href=\"https:\/\/github.com\/Wurst-Imperium\/Wurst7\">which is open source<\/a>, had a bow aimbot. Looking at their code, they calculated arrow launch angle like this:<\/p>\n\n\n\n<pre class=\"wp-block-code has-extra-small-font-size\"><code lang=\"java\" class=\"language-java\">double hDistance = Math.sqrt(posX * posX + posZ * posZ);\ndouble hDistanceSq = hDistance * hDistance;\nfloat g = 0.006F;\nfloat velocitySq = velocity * velocity;\nfloat velocityPow4 = velocitySq * velocitySq;\nfloat neededPitch = (float)-Math.toDegrees(Math.atan((velocitySq - Math.sqrt(velocityPow4 - g * (g * hDistanceSq + 2 * posY * velocitySq))) \/ (g * hDistance)));<\/code><\/pre>\n\n\n\n<p>From what I can tell, this is some form or another of the equation found on Wikipedia for the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Projectile_motion#Angle_\u03b8_required_to_hit_coordinate_(x,_y)\">angle \u03b8 required to hit coordinate (x, y)<\/a>, which is \\(\\tan\\theta=\\frac{v^{2}\\pm\\sqrt{v^{4}-g\\left(gx^{2}+2yv^{2}\\right)}}{gx}\\). Unfortunately, this equation assumes that there is no air resistance and that movement is continuous. Minecraft, however, does have air resistance and updates position and velocity every 1\/20 second (one game tick) rather than continuously, so this equation serves only as an approximation.<\/p>\n\n\n\n<p>As travel time or air resistance increases, this approximation becomes more and more inaccurate. I tested this out in a graphing calculator, and when I simulated the launch of an arrow traveling at one block per tick, the resulting landing point of the arrow with air resistance was a couple of blocks off from the landing point without air resistance. For a fully charged bow, which launches an arrow at a velocity over two blocks per tick, this inaccuracy would be even greater.<\/p>\n\n\n\n<p>In Wurst&#8217;s code, they seem to play around with the parameters to compensate for these inaccuracies. g is set to 0.006 instead of 0.05 and velocity is divided by 3, which seems to work well enough for their bow aimbot. However, I wanted a more generalized solution that could work with a greater variety of projectiles and launch velocities.<\/p>\n\n\n\n<p>I looked online for more solutions, but everything I could find seemed to assume a parabolic trajectory in some way or another.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">New Approach<\/h2>\n\n\n\n<p>Discarding the idea of deriving a purely analytical solution, I went for a new approach: getting an accurate approximation for the angle. After all, simulating Minecraft projectile trajectories is quite straightforward, so I can just make a guess for the angle and refine that guess until it hits the target.<\/p>\n\n\n\n<p>In order to simulate the projectile trajectory, I need to know how Minecraft does its physics. According to the <a href=\"https:\/\/minecraft.fandom.com\/wiki\/Entity#Motion_of_entities\">Minecraft wiki<\/a>, there is different gravitational acceleration and drag for different projectiles, and drag is applied before acceleration in each tick. They even supplied a table of the air resistance and gravitation acceleration for various entity types. In their footnotes, however, they had the following equation for vertical velocity at time t given a certain starting velocity: <em>V(t) = ((1-drag)x)-(acceleration((1-(1-drag)^t)\/drag))<\/em>. Looking at this equation, it makes no sense, since that would mean that at 0 acceleration, velocity would stay constant even if there was drag.<\/p>\n\n\n\n<p>Since I was no longer confident about the accuracy of this section, I cross-referenced the information in it with a decompiled version of Minecraft&#8217;s code that I had handy. Indeed, there were other inconsistencies that I started to find, such as the fact that potions had a gravitational acceleration of 0.05 rather than 0.03. However, most of the general details like the order of drag and acceleration were correct. In the code, they do entity physics in this order for every in-game tick:<\/p>\n\n\n\n<ol><li>increment position by the velocity<\/li><li>apply drag<\/li><li>apply gravity<\/li><\/ol>\n\n\n\n<p>With this information, I came up with the following equation for velocity at time t:<br>$$v(t)=\\left(1-d\\right)^{t}\\left(v-\\frac{a}{d}\\right)+\\frac{a}{d}$$<br>where v is initial velocity, a is acceleration, d is drag, and t is time in ticks.<\/p>\n\n\n\n<p>Looking at this equation, I realized that it was simply a geometric series with an added constant value. That meant that position could be calculated as the sum of a finite geometric series plus t times the constant. That produces the following equation for position:<br>$$p(t)=\\frac{(v-T)(1-(1-d)^{t})}{d}-\\frac{gt}{d}$$<\/p>\n\n\n\n<p>There&#8217;s no horizontal acceleration, so my equation for horizontal position becomes<br>$$x(t)=\\frac{v_x\\left(1-\\left(1-d\\right)^{t}\\right)}{d}$$<\/p>\n\n\n\n<p>And vertical acceleration is just -g since gravity is just acceleration in the negative y direction, so my equation for vertical position becomes<br>$$y(t)=\\frac{\\left(v_y+\\frac{g}{d}\\right)\\left(1-\\left(1-d\\right)^{t}\\right)}{d}-\\frac{gt}{d}$$<\/p>\n\n\n\n<p>Solving for t in terms of x I got<br>$$t=\\frac{\\ln\\left(1-\\frac{xd}{v_{x}}\\right)}{\\ln\\left(1-d\\right)}$$<\/p>\n\n\n\n<p>Plugging that into my equation for y(t) I get<br>$$y=\\frac{x\\left(\\frac{g}{d}+v_{y}\\right)}{v_{x}}-\\frac{g\\ln\\left(1-\\frac{xd}{v_{x}}\\right)}{d\\ln\\left(1-d\\right)}$$<\/p>\n\n\n\n<p>Then substituting \\(v\\cos\\left(\\theta\\right)\\) for x and \\(v\\sin\\left(\\theta\\right)\\) for y, I get<br>$$y=\\frac{x*(\\frac{g}{d}+v\\sin\\left(x)\\right)}{v\\cos x}-\\frac{g\\ln\\left(1-\\frac{xd}{v\\cos x}\\right)}{d\\ln\\left(1-d\\right)}$$<\/p>\n\n\n\n<p>And so the derivative of y with respect to \u03b8 is<br>$$\\frac{dy}{d\\theta}=x+\\frac{xg\\tan x}{\\left(-xd+v\\cos x\\right)\\ln\\left(1-d\\right)}+\\frac{xg\\tan x}{dv\\cos x}+x\\tan^2\\left(x\\right)$$<\/p>\n\n\n\n<p>From there, I just pick the angle that points straight toward my target x and y with <code>Math.atan2(ty, tx)<\/code> to get a rough estimate for my desired angle and use newton approximation from there to get the correct launch angle. It will always solve for the lowest possible angle since my starting angle (pointing straight at the target) will always undershoot the target.<\/p>\n\n\n\n<pre class=\"wp-block-code has-extra-small-font-size\"><code lang=\"java\" class=\"language-java\">public static Double getNeededAngle(double tx, double ty, double v, double d, double g) {\n    \/\/ If it's near the asymptotes, just return a vertical angle\n    if (tx &lt; ty * 0.001) {\n        return ty&gt;0 ? Math.PI\/2.0 : -Math.PI\/2.0;\n    }\n    \n    double md = 1.0-d;\n    double log_md = Math.log(md);\n    double g_d = g\/d; \/\/ This is terminal velocity\n    double theta = Math.atan2(ty, tx);\n    double prev_abs_ydif = Double.POSITIVE_INFINITY;\n    \n    \/\/ 20 iterations max, although it usually converges in 3 iterations\n    for (int i=0; i&lt;20; i++) {\n        System.out.println(i);\n        double cost = Math.cos(theta);\n        double sint = Math.sin(theta);\n        double tant = sint\/cost;\n        double vx = v * cost;\n        double vy = v * sint;\n        double y = tx*(g_d+vy)\/vx - g_d*Math.log(1-d*tx\/vx)\/log_md;\n        double ydif = y-ty;\n        double abs_ydif = Math.abs(ydif);\n        \n        \/\/ If it's getting farther away, there's probably no solution\n        if (abs_ydif&gt;prev_abs_ydif) {\n            return null;\n        }\n        else if (abs_ydif &lt; 0.0001) {\n            return theta;\n        }\n        \n        double dy_dtheta = tx + g*tx*tant \/ ((-d*tx+v*cost)*log_md) + g*tx*tant\/(d*v*cost) + tx*tant*tant;\n        theta -= ydif\/dy_dtheta;\n        prev_abs_ydif = abs_ydif;\n    }\n    \n    \/\/ If exceeded max iterations, return null\n    return null;\n}<\/code><\/pre>\n\n\n\n<p>To get the launch angle to shoot an arrow with a fully charged bow from position1 to position2, for example, you would just have to do something like<\/p>\n\n\n\n<pre class=\"wp-block-code has-extra-small-font-size\"><code lang=\"java\" class=\"language-java\">double dx = position2.x-position1.x;\ndouble dy = position2.y-position1.y;\ndouble dz = position2.z-position1.z;\ndouble h = Math.sqrt(dx*dx + dz*dz);\ndouble yaw = (360 - Math.toDegrees(Math.atan2(dx, dz))) % 360;\ndouble pitch = Math.toDegrees(\n    getNeededAngle(h, dy, 3, 0.01, 0.05));<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>What Other People Did For one of my projects, I needed some way to determine the launch angle of a projectile in Minecraft that would hit a specific point, so I recalled that the Wurst client, which is open source, had a bow aimbot. Looking at their code, they calculated arrow launch angle like this:&hellip; <a class=\"more-link\" href=\"https:\/\/blog.harryzhou.info\/index.php\/2021\/04\/03\/minecraft-projectile-launch-angle-calculation\/\">Continue reading <span class=\"screen-reader-text\">Minecraft Projectile Launch Angle Calculation<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"templates\/template-full-width.php","format":"standard","meta":{"footnotes":""},"categories":[4,8],"tags":[10,6,7,5],"_links":{"self":[{"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/posts\/41"}],"collection":[{"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/comments?post=41"}],"version-history":[{"count":52,"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/posts\/41\/revisions"}],"predecessor-version":[{"id":113,"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/posts\/41\/revisions\/113"}],"wp:attachment":[{"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/media?parent=41"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/categories?post=41"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.harryzhou.info\/index.php\/wp-json\/wp\/v2\/tags?post=41"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}