0
\$\begingroup\$

I am trying to calculate a vertical angle needed to hit a target at [distance, elevation] 2D coordinates relative to shooter given a fixed projectile velocity, air drag and gravitational acceleration.

Although I operate in 3D world (minecraft) the task itself eliminates the 3D problem. Once you calculate the rotation around your axis, which is in my case just atan2(-x3D, -z3D) what I'm solving is described on this picture:

image description

From a wikipedia article about balistics I have picked this equation. As you can see, it seems to directly solve for my angle:

$$ \theta = arctan \bigg( \frac{v^2 \pm \sqrt{v^4-g(gx^2+2yv^2)}}{gx} \bigg) $$

So I rewrote the equation in javascript as Math.atan((v*v+Math.sqrt(v*v*v*v - g*(g*x*x+2*y*v*v)))/g*x); where v is speed, g is gravitational acceleration, x is distance and y is elevation of the target.

I implemented the whole algorithm as following:

//Assumed constants
var g = 9.81;  //Gravitational acceleration
var v = 35.9;  //Experimantally measured arrow speed
//The point of player head 3D [x,y,z]
var mepoint = this.bot.entity.position.offset(0, this.bot.entity.height, 0);
//The point of the target center assuming we're at [0, 0, 0] again using 3D [x,y,z]
var point = this.target.position.offset(0, this.target.height/2, 0).minus(mepoint);
//Transfer the coordinates with regards to the bot rotation
//That is, the 2D X coordinate is the distance as seen from the top
var x = Math.sqrt(point.x*point.x+point.z*point.z);
//The 2D Y coordinate is just plain difference between elevation
var y = point.y;
console.log("|BOT: TaskShoot| Target 2D coords [distance, elevation]: ",[x, y]);
//Calculate the horizontal angle - look at the target 3D [x,z] coordinates
var yaw = Math.atan2(-point.x, -point.z);

//Calculate the pitch using the equation from wikipedia
var pitch =  Math.atan((v*v+Math.sqrt(v*v*v*v - g*(g*x*x+2*y*v*v)))/g*x);
//Since it has 2 solutions, solve for bot + and - variant
var pitch2 = Math.atan((v*v-Math.sqrt(v*v*v*v - g*(g*x*x+2*y*v*v)))/g*x);          
console.log("|BOT: TaskShoot| Angles: ",[pitch, pitch2]);
//NaN test: !(NaN>=0 || NaN<=0)  
//Test two pitches and avoid NaNs. If both pitches are NaN, the target is out of range.
if(!isNaN(pitch))
  this.bot.look(yaw, pitch, false, function() {_this.finished();});
else if(!isNaN(pitch2))
  this.bot.look(yaw, pitch2, false, function() {_this.finished();});
else
  this.bot.look(yaw, 0, false, function() {_this.finishedError(123, "Couldn't calculate aim pitch - out of range? Aiming straight.");}); 

But this calculation allways returns almost the same values for the angles:

|BOT: TaskShoot| Target 2D coords [distance, elevation]:  [ 13.027689861406742, 3.032749999999993 ]
|BOT: TaskShoot| Angles:  [ 1.5704302724148764, -1.5643119398157057 ]

1.5704 is about pi/2. I never got significantly different result than this. So what's wrong with my calculation and how should I fix it? Did I even pick the right formula?

\$\endgroup\$
  • \$\begingroup\$ This is not gonna help your problem... but if the player is out of range, isn't the best choice to aim upwards at an angle which will produce the optimal distance, to increase the chances of the player walking into the arrow mid-flight? \$\endgroup\$ – MickLH Jul 20 '15 at 16:39
  • \$\begingroup\$ You need to divide by (g*x) but forgot the parentheses. \$\endgroup\$ – Bram Jan 11 at 17:28
1
\$\begingroup\$

Refactoring is your friend !!!
Separate the issues and try to get your code as generic as possible.
Separate and factorise the steps of the computing.
Keep comments short.

• Boilerplate :

// ------------------------

var g = 9.81; //Gravitational acceleration
var v = 35.9; //Experimantally measured arrow speed

var sqrt = Math.sqrt,
    atan2 = Math.atan2,
    abs = Math.abs,
    sq = function (x) {
        return x * x;
    };

• Function dealing with entities

// ------------------------

function getAngleToEntity(srcEntity, tgtEntity) {
    return getAngleTo(
    srcEntity.position.offset(0, srcEntity.height / 2, 0),
    tgtEntity.position.offset(0, srcEntity.height / 2, 0),
    v);
}

• Function just dealing with vectors/point

// ------------------------

function getAngleTo(srcPoint, dstPoint, speed) {
    // delta = dst - src
    var delta = dstPoint.minus(srcPoint);
    // distance as seen from the top
    var x = Math.sqrt(sq(delta.x) + sq(delta.z));
    // elevation
    var y = delta.y;
    //Calculate the direct angle
    var straight = Math.atan2(y, x);
    //Calculate the pitch using the equation from wikipedia
    var sqSpeed = sq(speed),
        qdSpeed = sq(sqSpeed);
    var rightTerm = qdSpeed - g * (g * sq(x) + 2 * sq(y));
    if (rightTerm < 0) {
        // handle not-able-to-reach case
        console.log('no reach');
        // return ... ??? ;
    }
    rightTerm = sqrt(rightTerm);
    var angle1 = atan2((sqSpeed + rightTerm), (g * x));
    var angle2 = atan2((sqSpeed - rightTerm), (g * x));
    // Choose the angle closest from straight angle.
    var resAngle = abs(angle1 - straight) < abs(angle2 - straight) ? angle1 : angle2;
    // log
    console.log("|BOT: TaskShoot| Target 2D coords [distance, elevation]: ", [x, y]);
    console.log("|BOT: TaskShoot| Angles: ", [angle1, angle2]);
    //
    return resAngle;
}

• Unit tests (add some others, and check results !! ) :

// ------------------------

function Vector(x, y, z) { this.x = x;
    this.y = y;
    this.z = z;
    this.minus = function (other) {
        return new Vector(this.x - other.x, this.y - other.y, this.z - other.z);
    }
}

// ------------------------

// some unit tests here
getAngleTo(new Vector(0, 0, 0), new Vector(30, 0, 90), v);
\$\endgroup\$
0
\$\begingroup\$

Try fixing parenthesis:

var pitch =  Math.atan((v*v+Math.sqrt(v*v*v*v - g*(g*x*x+2*y*v*v)))/(g*x));
var pitch2 = Math.atan((v*v-Math.sqrt(v*v*v*v - g*(g*x*x+2*y*v*v)))/(g*x));  

Consider also using atan2 function. Maybe in your case it won't fix anything but it's always good to know about it. https://en.wikipedia.org/wiki/Atan2

\$\endgroup\$
  • \$\begingroup\$ I know about atan2, but I wasn't sure how to use it in this case. What would be the first and the second argument? \$\endgroup\$ – Tomáš Zato Jul 19 '15 at 22:35
  • \$\begingroup\$ Numerator would be first, and denominator second: Math.atan2((v*v+Math.sqrt(v*v*v*v - g*(g*x*x+2*y*v*v))),g*x); \$\endgroup\$ – Adrian Krupa Jul 19 '15 at 22:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.