Search code examples
javascriptmathgame-physicsprojectile

Projectile Mathematics error


So, essentially I am trying to create two calculation methods to do the following:

1) Get the hit location of a projectile launcher based on the position, velocity and angle of firing.

2) Get the angle of firing based on the velocity and desired hit location.

I currently have the following code.

'use strict';

import Mortar from '../mortar/Mortar.js';

const GRAVITY = 9.8;
const MAX_RANGE = 1250;
const MIN_RANGE = 50;

/**
 * A class for the mortar calculator.
 */
class Calc {
    /**
     * Get Hit Location Data from Dial Data.
     * @param {*} dialData The current dialed in data
     * @param {*} mortarData The current Mortar Data
     * @return {*} Hit Location Data
     */
    static getHitLocData(dialData, mortarData = Mortar.getDefaultData()) {
        const {mrads, bearing} = dialData;
        const {velocity, x, y, elevation} = mortarData;

        const rads = Calc.getRadFromMils(mrads);
        const b = Calc.getRadFromDegree(bearing);

        const vx = (velocity * Math.cos(rads));
        const vy = (velocity * Math.sin(rads));
        const time = (2 * (vy / GRAVITY));
        const range = ((2 * vx) * (vy / GRAVITY));
        const yMax = (Math.pow(vy, 2) / (2 * GRAVITY));

        let canHit = true;

        // We need a modification for the difference in elevation

        // If the range wont hit
        if ((range < MIN_RANGE) || (range > MAX_RANGE)) {
            canHit = false;
        }

        const hitX = (x + (range * Math.cos(b)));
        const hitY = (y + (range * Math.sin(b)));

        return {
            bearing,
            'mrads': `${Math.round(mrads)}mrads`,
            'map': {
                'x': Math.round(hitX),
                'y': Math.round(hitY),
                'heightDiff': '0m',
                'distance': `${range.toFixed(2)}m`,
                'flight': `${time.toFixed(2)}sec`
            },
            'grid': 'A1-1-1-000',
            'misc': {
                'velocityX': `${vx.toFixed(1)}m/s`,
                'velocityY': `${vy.toFixed(1)}m/s`,
                'maxHeight': `${yMax.toFixed(2)}m`
            },
            canHit
        };
    }

    /**
     * Get Dial Data Data from Hit Location Data.
     * @param {*} hitLocData The current requested hit location
     * @param {*} mortarData The current Mortar Data
     * @return {*} Dial Data
     */
    static getDialData(hitLocData, mortarData = Mortar.getDefaultData()) {
        const {hitX, hitY, hitHeight} = hitLocData;
        const {x, y, mortHeight, velocity} = mortarData;

        const range = Math.sqrt(Math.pow((hitX - x), 2) + Math.pow((hitY - y), 2));
        const bearing = (Math.atan2((hitY - y), (hitX - x)) * (180 / Math.PI));
        const heightDiff = (hitHeight - mortHeight);
        let rads = 0;

        if (heightDiff) {
            // Height difference has not been math proven as 0,0 doesnt even work

            const v4 = Math.pow(velocity, 4);
            const gx = (GRAVITY * range);
            const gx2 = (GRAVITY * Math.pow(range, 2));
            const yv2 = (heightDiff * (Math.pow(velocity, 2)));
            const m1 = (GRAVITY * (gx2 - (2 * yv2)));
            rads = Math.atan((v4 - m1) / gx);
        } else {
            const gx = (GRAVITY * range);
            rads = Math.atan(Math.pow(velocity, 2) / gx);
        }

        const mrads = (rads * 1000);

        const vy = (velocity * Math.sin(rads));
        const time = (2 * (vy / GRAVITY));

        let canHit = true;

        // If the range wont hit
        if ((range < MIN_RANGE) || (range > MAX_RANGE)) {
            canHit = false;
        }

        return {
            bearing,
            'mrads': (`${Math.round(mrads)}mrad`),
            'heightDiff': (`${heightDiff}m`),
            'flight': (`${time.toFixed(2)}sec`),
            'distance': (`${range.toFixed(2)}m`),
            'spread': 0,
            canHit
        };
    }

    /**
     * Get the number of radians from Milradians.
     *
     * @param {Number} mrads The number of mrads
     * @return {Number} The new number of rads
     */
    static getRadFromMils(mrads) {
        return (mrads / 1000);
    }

    /**
     * Get the number of radians from degrees.
     *
     * @param {Number} deg The current degree bearing
     * @return {Number} The new number of rads
     */
    static getRadFromDegree(deg) {
        return (deg * (Math.PI / 180));
    }
}

export default Calc;

The former, getting the hit location is the one I know works. If I pass it a given bearing and mrad firing angle I can find where I am hitting in relation to the mortar location.

However the latter does not match despite passing the same output as given from the first function. Please see below for input and output data examples

Input:

var dialData = getDialData({
  'hitX': 1250,
  'hitY': 0,
  'hitHeight': 0
}, {
  'x': 0,
  'y': 0,
  'mortHeight': 0,
  'velocity': 110.75
});
var hitData = getHitLocData({
  'mrads': 800,
  'bearing': 0,
}, {
  'x': 0,
  'y': 0,
  'elevation': 0,
  'velocity': 110.75
});

Output:

{"bearing":0,"mrads":"786mrad","heightDiff":"0m","flight":"15.99sec","distance":"1250.00m","spread":0,"canHit":true}
{"bearing":0,"mrads":"800mrads","map":{"x":1251,"y":0,"heightDiff":"0m","distance":"1251.05m","flight":"16.21sec"},"grid":"A1-1-1-000","misc":{"velocityX":"77.2m/s","velocityY":"79.4m/s","maxHeight":"322.03m"},"canHit":false}

I cannot for the life of me figure out why this is happening. Both should calculate to have the same distance, mrads and flight.


Solution

  • I've figured out what the issue was. The issue was using the following equation: Equation image My implementation was completely incorrect. Rather than calculating both sides of the ± I was merely calculating one or the other. You can see this in the if statement which checks the heightDiff.

    The correct solution is to do as follows:

    // Below are the two calculaitons we need to work out
            const dx = range;
            const v = velocity;
            const g = GRAVITY;
            const hd = heightDiff;
    
            const v4 = (Math.pow(velocity, 4));
            const gx2 = (GRAVITY * Math.pow(range, 2));
            const yv2 = (heightDiff * Math.pow(velocity, 2));
            const gx = (GRAVITY * range);
    
            const p1 = Math.sqrt(v4 - (GRAVITY * (gx2 + (2 * yv2))));
            const a1 = Math.atan((Math.pow(velocity, 2) + p1) / gx);
    
            rads = a1;
            const mrads = (rads * 1000);