{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "645cce7f",
   "metadata": {},
   "source": [
    "# Summary\n",
    "\n",
    "> **TL;DR:** *Slant* terms are a rotational transformation of the horizontal coordinate system to one referenced to a sight line.\n",
    "\n",
    "We often think of distance or location in horizontal and vertical terms that are referenced to the direction of gravity.  The *map distance* to a target ignores any variation in altitude, and *height* is measured perpendicular to the horizontal plane.\n",
    "\n",
    "When a target is not at the same altitude as a shooter, there is an angle between the horizontal and the *line of sight* to the target.  This *sight angle*, or *slant angle*, is also called *look angle*, and in military literature is often (misspelled) *angle of site*.\n",
    "\n",
    "For some purposes this *slant frame of reference* can be more convenient than the horizontal frame.  For example, when computing *point-blank range* or *danger space*, or when adjusting sights, it is the *slant height* that matters.  Basic ranging techniques typically return the *slant-distance* to a target.\n",
    "\n",
    "In this document we will start with the standard horizontal frame and then show how to convert between a slant frame."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "8719c4d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LOAD ME\n",
    "import logging\n",
    "import math\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from mpl_toolkits.mplot3d import Axes3D\n",
    "from py_ballisticcalc import Shot, DragModel, Ammo, Velocity, Weight, Distance, TableG7, TableGS\n",
    "from py_ballisticcalc import Calculator, loadMetricUnits, logger\n",
    "from py_ballisticcalc import PreferredUnits, TrajFlag, Angular\n",
    "logger.setLevel(logging.WARNING)\n",
    "loadMetricUnits()\n",
    "PreferredUnits.drop = Distance.Meter\n",
    "calc = Calculator(engine=\"scipy_engine\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8bec0f9",
   "metadata": {},
   "source": [
    "# Horizontal frame\n",
    "\n",
    "**Gravity gives $y$:** In ballistics, everything is referenced to the direction of gravity. The gravity vector points \"down,\" and this defines the vertical direction. In 3D Cartesian coordinates $(x, y, z)$, the gravity vector is $(0, -g, 0)$, where $g$ is acceleration due to gravity (typically 32 feet/second² or 9.8 meters/second²). The $y$ coordinate describes vertical (up/down) position.\n",
    "\n",
    "**Horizontal:** Having defined the vertical axis using the gravity vector, we can then define *horizontal* as any vector perpendicular (or *orthogonal*) to the direction of gravity.\n",
    "\n",
    "**Sight gives $x$:** The second key reference in ballistics is the **sight line**. We set the origin of our coordinate system $(0, 0, 0)$ at the sight, which is usually either the shooter’s eye or the center of a sighting device like a scope. The *sight line* is the ray starting at the origin and pointing in the exact direction of the sight.  The $x$ coordinate measures distance from the sight along a horizontal sight line.\n",
    "\n",
    "The $z$ coordinate describes position orthogonal to both the direction of gravity and the sight line. From the perspective of the sight, this is lateral position, also known as windage.\n",
    "\n",
    "## Diagram"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "fba027b9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 102580 characters]",
      "text/plain": [
       "<Figure size 700x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Create illustration of ballistic coordinate system\n",
    "fig = plt.figure(figsize=(7, 7))\n",
    "ax = fig.add_subplot(111, projection='3d')\n",
    "ax.view_init(elev=25, azim=35)\n",
    "arrow_length = 1.0\n",
    "# \"x\" is plot-y, \"y\" is plot-z, \"z\" is plot-x\n",
    "ax.quiver(0, 0, 1, 0, arrow_length, 0, color='red', linewidth=4, arrow_length_ratio=0.12)\n",
    "ax.text(0, 0.5, 0.85, '$x$ (Horizontal sight line)', color='red', fontsize=14, zdir='y', va='center', ha='center',\n",
    "        bbox=dict(facecolor='white', edgecolor='none', pad=2))\n",
    "ax.quiver(0, 0, 1, 0, 0, -arrow_length, color='blue', linewidth=3, arrow_length_ratio=0.12)\n",
    "ax.text(0.1, 0.1, 0.47, '$y$ (Gravity)', color='blue', fontsize=14, zdir='z', va='center', ha='left',\n",
    "        bbox=dict(facecolor='white', edgecolor='none', pad=2))\n",
    "ax.quiver(0, 0, 1, arrow_length, 0, 0, color='green', linewidth=3, arrow_length_ratio=0.12)\n",
    "ax.text(0.1, 0, 0.72, '$z$ (Windage)', color='green', fontsize=14, zdir='x', va='center', ha='right',\n",
    "        bbox=dict(facecolor='white', edgecolor='none', pad=2))\n",
    "# Origin\n",
    "ax.scatter(0, 0, 1, color='black', s=100)\n",
    "ax.text(0, 0., 1.1, 'Sight (Origin)', color='black', fontsize=12, fontweight='bold', va='bottom', ha='center',\n",
    "        bbox=dict(facecolor='white', edgecolor='gray', pad=4))\n",
    "# Crop axes tightly to arrows\n",
    "ax.set_xlim([0, arrow_length*1.1])\n",
    "ax.set_ylim([0, arrow_length*1.1])\n",
    "ax.set_zlim([0, arrow_length*1.1])\n",
    "ax.set_xticklabels([])\n",
    "ax.set_yticklabels([])\n",
    "ax.set_zticklabels([])\n",
    "ax.xaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n",
    "ax.yaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n",
    "ax.zaxis.set_pane_color((1.0, 1.0, 1.0, 0.0))\n",
    "ax.grid(True)\n",
    "#ax.set_title('Ballistics Coordinate System', fontsize=16)\n",
    "#plt.savefig('ballistics_coordinate_system.svg', bbox_inches='tight')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1cac783",
   "metadata": {},
   "source": [
    "# Simple Trajectory\n",
    "\n",
    "Imagine you're looking at a target 1km away across level ground (meaning your sight line is horizontal).  We'll model a standard 7.62mm NATO round, then use our calculator to find the barrel elevation needed for that round to hit the target, then plot the trajectory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ca11d3ae",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 52560 characters]",
      "text/plain": [
       "<Figure size 640x480 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "calc = Calculator(engine=\"euler_engine\")\n",
    "def create_7_62_mm_shot():  # 7.62x51mm NATO M118   \n",
    "    dm = DragModel(bc=0.243, drag_table=TableG7, weight=Weight.Grain(175), diameter=Distance.Millimeter(7.62), length=Distance.Millimeter(32.0))\n",
    "    return Shot(ammo=Ammo(dm, mv=Velocity.MPS(800)))\n",
    "shot = create_7_62_mm_shot()\n",
    "calc.set_weapon_zero(shot, zero_distance=Distance.Meter(1000))\n",
    "t = calc.fire(shot, trajectory_range=Distance.Meter(1100), trajectory_step=Distance.Meter(100), extra_data=True)\n",
    "t.plot()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa19d98f",
   "metadata": {},
   "source": [
    "## Table\n",
    "\n",
    "Here is a table showing some of the details the calculator generates for points along the trajectory:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "429e95e7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>time</th>\n",
       "      <th>distance</th>\n",
       "      <th>velocity</th>\n",
       "      <th>mach</th>\n",
       "      <th>height</th>\n",
       "      <th>slant_height</th>\n",
       "      <th>slant_distance</th>\n",
       "      <th>angle</th>\n",
       "      <th>energy</th>\n",
       "      <th>flag</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.000 s</td>\n",
       "      <td>0.0 m</td>\n",
       "      <td>800 m/s</td>\n",
       "      <td>2.35 mach</td>\n",
       "      <td>-0.0 m</td>\n",
       "      <td>-0.0 m</td>\n",
       "      <td>0.0 m</td>\n",
       "      <td>0.8676 °</td>\n",
       "      <td>3629 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0.130 s</td>\n",
       "      <td>100.0 m</td>\n",
       "      <td>739 m/s</td>\n",
       "      <td>2.17 mach</td>\n",
       "      <td>1.4 m</td>\n",
       "      <td>1.4 m</td>\n",
       "      <td>100.0 m</td>\n",
       "      <td>0.7725 °</td>\n",
       "      <td>3094 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0.271 s</td>\n",
       "      <td>200.0 m</td>\n",
       "      <td>680 m/s</td>\n",
       "      <td>2.00 mach</td>\n",
       "      <td>2.7 m</td>\n",
       "      <td>2.7 m</td>\n",
       "      <td>200.0 m</td>\n",
       "      <td>0.6606 °</td>\n",
       "      <td>2623 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0.425 s</td>\n",
       "      <td>300.0 m</td>\n",
       "      <td>624 m/s</td>\n",
       "      <td>1.84 mach</td>\n",
       "      <td>3.7 m</td>\n",
       "      <td>3.7 m</td>\n",
       "      <td>300.0 m</td>\n",
       "      <td>0.5282 °</td>\n",
       "      <td>2211 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0.592 s</td>\n",
       "      <td>400.0 m</td>\n",
       "      <td>571 m/s</td>\n",
       "      <td>1.68 mach</td>\n",
       "      <td>4.5 m</td>\n",
       "      <td>4.5 m</td>\n",
       "      <td>400.0 m</td>\n",
       "      <td>0.3705 °</td>\n",
       "      <td>1851 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>0.776 s</td>\n",
       "      <td>500.0 m</td>\n",
       "      <td>520 m/s</td>\n",
       "      <td>1.53 mach</td>\n",
       "      <td>5.0 m</td>\n",
       "      <td>5.0 m</td>\n",
       "      <td>500.0 m</td>\n",
       "      <td>0.1814 °</td>\n",
       "      <td>1536 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>0.937 s</td>\n",
       "      <td>580.8 m</td>\n",
       "      <td>481 m/s</td>\n",
       "      <td>1.41 mach</td>\n",
       "      <td>5.1 m</td>\n",
       "      <td>5.1 m</td>\n",
       "      <td>580.8 m</td>\n",
       "      <td>-0.0002 °</td>\n",
       "      <td>1311 J</td>\n",
       "      <td>APEX</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>0.977 s</td>\n",
       "      <td>600.0 m</td>\n",
       "      <td>472 m/s</td>\n",
       "      <td>1.39 mach</td>\n",
       "      <td>5.1 m</td>\n",
       "      <td>5.1 m</td>\n",
       "      <td>600.0 m</td>\n",
       "      <td>-0.0477 °</td>\n",
       "      <td>1261 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>1.201 s</td>\n",
       "      <td>700.0 m</td>\n",
       "      <td>425 m/s</td>\n",
       "      <td>1.25 mach</td>\n",
       "      <td>4.8 m</td>\n",
       "      <td>4.8 m</td>\n",
       "      <td>700.0 m</td>\n",
       "      <td>-0.3285 °</td>\n",
       "      <td>1023 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>1.450 s</td>\n",
       "      <td>800.0 m</td>\n",
       "      <td>381 m/s</td>\n",
       "      <td>1.12 mach</td>\n",
       "      <td>3.9 m</td>\n",
       "      <td>3.9 m</td>\n",
       "      <td>800.0 m</td>\n",
       "      <td>-0.6765 °</td>\n",
       "      <td>821 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>1.726 s</td>\n",
       "      <td>899.3 m</td>\n",
       "      <td>340 m/s</td>\n",
       "      <td>1.00 mach</td>\n",
       "      <td>2.4 m</td>\n",
       "      <td>2.4 m</td>\n",
       "      <td>899.3 m</td>\n",
       "      <td>-1.1085 °</td>\n",
       "      <td>656 J</td>\n",
       "      <td>MACH</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>1.728 s</td>\n",
       "      <td>900.0 m</td>\n",
       "      <td>340 m/s</td>\n",
       "      <td>1.00 mach</td>\n",
       "      <td>2.4 m</td>\n",
       "      <td>2.4 m</td>\n",
       "      <td>900.0 m</td>\n",
       "      <td>-1.1119 °</td>\n",
       "      <td>655 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>12</th>\n",
       "      <td>2.034 s</td>\n",
       "      <td>1000.0 m</td>\n",
       "      <td>317 m/s</td>\n",
       "      <td>0.93 mach</td>\n",
       "      <td>0.0 m</td>\n",
       "      <td>0.0 m</td>\n",
       "      <td>1000.0 m</td>\n",
       "      <td>-1.6389 °</td>\n",
       "      <td>570 J</td>\n",
       "      <td>ZERO_DOWN|RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>2.357 s</td>\n",
       "      <td>1100.0 m</td>\n",
       "      <td>304 m/s</td>\n",
       "      <td>0.89 mach</td>\n",
       "      <td>-3.4 m</td>\n",
       "      <td>-3.4 m</td>\n",
       "      <td>1100.0 m</td>\n",
       "      <td>-2.2236 °</td>\n",
       "      <td>523 J</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       time  distance velocity       mach  height slant_height slant_distance  \\\n",
       "0   0.000 s     0.0 m  800 m/s  2.35 mach  -0.0 m       -0.0 m          0.0 m   \n",
       "1   0.130 s   100.0 m  739 m/s  2.17 mach   1.4 m        1.4 m        100.0 m   \n",
       "2   0.271 s   200.0 m  680 m/s  2.00 mach   2.7 m        2.7 m        200.0 m   \n",
       "3   0.425 s   300.0 m  624 m/s  1.84 mach   3.7 m        3.7 m        300.0 m   \n",
       "4   0.592 s   400.0 m  571 m/s  1.68 mach   4.5 m        4.5 m        400.0 m   \n",
       "5   0.776 s   500.0 m  520 m/s  1.53 mach   5.0 m        5.0 m        500.0 m   \n",
       "6   0.937 s   580.8 m  481 m/s  1.41 mach   5.1 m        5.1 m        580.8 m   \n",
       "7   0.977 s   600.0 m  472 m/s  1.39 mach   5.1 m        5.1 m        600.0 m   \n",
       "8   1.201 s   700.0 m  425 m/s  1.25 mach   4.8 m        4.8 m        700.0 m   \n",
       "9   1.450 s   800.0 m  381 m/s  1.12 mach   3.9 m        3.9 m        800.0 m   \n",
       "10  1.726 s   899.3 m  340 m/s  1.00 mach   2.4 m        2.4 m        899.3 m   \n",
       "11  1.728 s   900.0 m  340 m/s  1.00 mach   2.4 m        2.4 m        900.0 m   \n",
       "12  2.034 s  1000.0 m  317 m/s  0.93 mach   0.0 m        0.0 m       1000.0 m   \n",
       "13  2.357 s  1100.0 m  304 m/s  0.89 mach  -3.4 m       -3.4 m       1100.0 m   \n",
       "\n",
       "        angle  energy             flag  \n",
       "0    0.8676 °  3629 J            RANGE  \n",
       "1    0.7725 °  3094 J            RANGE  \n",
       "2    0.6606 °  2623 J            RANGE  \n",
       "3    0.5282 °  2211 J            RANGE  \n",
       "4    0.3705 °  1851 J            RANGE  \n",
       "5    0.1814 °  1536 J            RANGE  \n",
       "6   -0.0002 °  1311 J             APEX  \n",
       "7   -0.0477 °  1261 J            RANGE  \n",
       "8   -0.3285 °  1023 J            RANGE  \n",
       "9   -0.6765 °   821 J            RANGE  \n",
       "10  -1.1085 °   656 J             MACH  \n",
       "11  -1.1119 °   655 J            RANGE  \n",
       "12  -1.6389 °   570 J  ZERO_DOWN|RANGE  \n",
       "13  -2.2236 °   523 J            RANGE  "
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = t.dataframe(True).drop(columns=['drop_angle', 'windage', 'windage_angle', 'density_ratio', 'drag', 'ogw'])\n",
    "df#[df.distance.str.contains(\"00.\") | (df.flag != \"RANGE\")]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6f441d7",
   "metadata": {},
   "source": [
    "Some of these rows look duplicative.  \"distance\" and \"slant_distance\" are always the same, as are \"height\" and \"slant_height.\"  We'll get into scenarios where those differ soon.  First, let's look at what the row corresponding to the APEX flag tells us:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2241a24d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>time</th>\n",
       "      <th>distance</th>\n",
       "      <th>velocity</th>\n",
       "      <th>mach</th>\n",
       "      <th>height</th>\n",
       "      <th>slant_height</th>\n",
       "      <th>slant_distance</th>\n",
       "      <th>angle</th>\n",
       "      <th>energy</th>\n",
       "      <th>flag</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>0.937 s</td>\n",
       "      <td>580.8 m</td>\n",
       "      <td>481 m/s</td>\n",
       "      <td>1.41 mach</td>\n",
       "      <td>5.1 m</td>\n",
       "      <td>5.1 m</td>\n",
       "      <td>580.8 m</td>\n",
       "      <td>-0.0002 °</td>\n",
       "      <td>1311 J</td>\n",
       "      <td>APEX</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      time distance velocity       mach height slant_height slant_distance  \\\n",
       "6  0.937 s  580.8 m  481 m/s  1.41 mach  5.1 m        5.1 m        580.8 m   \n",
       "\n",
       "       angle  energy  flag  \n",
       "6  -0.0002 °  1311 J  APEX  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df[df.flag == \"APEX\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "488e7450",
   "metadata": {},
   "source": [
    "We define the apex of a trajectory as the point where vertical velocity goes from positive to negative.  This is equivalent to saying that the trajectory's angle is horizontal (i.e., 0°).  When present, the apex is the highest point in the trajectory.  In this example, it occurs at a distance 580m along the sight line, and at that point the projectile is 5.1m above the sight line.\n",
    "\n",
    "Another special point, which is also depicted on the chart, is the Mach crossing.  This occurs later in the trajectory while the projectile is 899m along the sight line and 2.4m above it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1c768b3c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>time</th>\n",
       "      <th>distance</th>\n",
       "      <th>velocity</th>\n",
       "      <th>mach</th>\n",
       "      <th>height</th>\n",
       "      <th>slant_height</th>\n",
       "      <th>slant_distance</th>\n",
       "      <th>angle</th>\n",
       "      <th>energy</th>\n",
       "      <th>flag</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>1.726 s</td>\n",
       "      <td>899.3 m</td>\n",
       "      <td>340 m/s</td>\n",
       "      <td>1.00 mach</td>\n",
       "      <td>2.4 m</td>\n",
       "      <td>2.4 m</td>\n",
       "      <td>899.3 m</td>\n",
       "      <td>-1.1085 °</td>\n",
       "      <td>656 J</td>\n",
       "      <td>MACH</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       time distance velocity       mach height slant_height slant_distance  \\\n",
       "10  1.726 s  899.3 m  340 m/s  1.00 mach  2.4 m        2.4 m        899.3 m   \n",
       "\n",
       "        angle energy  flag  \n",
       "10  -1.1085 °  656 J  MACH  "
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df[df.flag == \"MACH\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cb2a556",
   "metadata": {},
   "source": [
    "# Non-zero look-angle\n",
    "\n",
    "So far we have been considering horizontal sight lines.  What happens if the shooter is looking at a target at a different altitude?\n",
    "\n",
    "Let's imagine you have a friend on the roof of a tall building and you want to launch a ball for him to catch.  The building is 100m away on level ground, and he – the target – is 100m above the ground.  In a case like this it can be more intuitive to talk about the angle and straight-line distance to the target, which we can get from trigonometry:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c01ca185",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Slant angle to target is 45.0 degrees. Slant range is 141.4.\n"
     ]
    }
   ],
   "source": [
    "horizontal_distance = 100.\n",
    "altitude = 100.\n",
    "slant_angle = Angular.Radian(math.atan2(altitude, horizontal_distance))\n",
    "slant_distance = np.hypot(horizontal_distance, altitude)\n",
    "print(f'Slant angle to target is {slant_angle >> Angular.Degree} degrees. Slant range is {slant_distance:.1f}.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39f11a80",
   "metadata": {},
   "source": [
    "The sight-line to this target is 45 degrees above the horizontal, and its distance along the sight line (the slant range) is 141.4m.  Let's suppose we have a gun that launches a ball at 55m/s and solve for the trajectory to get the ball to your friend:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "fe3cb030",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Launch angle to hit the target is 63.3 degrees.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 51884 characters]",
      "text/plain": [
       "<Figure size 640x480 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def create_ball_shot():\n",
    "    dm = DragModel(bc=0.2, drag_table=TableGS)  # GS model is used for spherical projectiles\n",
    "    return Shot(ammo=Ammo(dm, mv=Velocity.MPS(55)))\n",
    "shot = create_ball_shot()\n",
    "shot.look_angle = slant_angle  # Set the look-angle to the one we calculated\n",
    "calc.set_weapon_zero(shot, zero_distance=Distance.Meter(slant_distance))\n",
    "print(f'Launch angle to hit the target is {shot.barrel_elevation >> Angular.Degree:.1f} degrees.')\n",
    "tball = calc.fire(shot, trajectory_range=Distance.Meter(100), trajectory_step=Distance.Meter(10), extra_data=True)\n",
    "ax = tball.plot()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aaf77ff3",
   "metadata": {},
   "source": [
    "## Table\n",
    "\n",
    "Now when we look at the detailed data we see that the \"slant_distance\" is always greater than the \"distance,\" and the \"slant_height\" is always less than the \"height.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ba4295d0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>time</th>\n",
       "      <th>distance</th>\n",
       "      <th>velocity</th>\n",
       "      <th>mach</th>\n",
       "      <th>height</th>\n",
       "      <th>slant_height</th>\n",
       "      <th>slant_distance</th>\n",
       "      <th>angle</th>\n",
       "      <th>flag</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.000 s</td>\n",
       "      <td>0.0 m</td>\n",
       "      <td>55 m/s</td>\n",
       "      <td>0.16 mach</td>\n",
       "      <td>-0.0 m</td>\n",
       "      <td>-0.0 m</td>\n",
       "      <td>0.0 m</td>\n",
       "      <td>63.2914 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0.412 s</td>\n",
       "      <td>10.0 m</td>\n",
       "      <td>50 m/s</td>\n",
       "      <td>0.15 mach</td>\n",
       "      <td>19.0 m</td>\n",
       "      <td>6.4 m</td>\n",
       "      <td>20.5 m</td>\n",
       "      <td>61.2293 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0.838 s</td>\n",
       "      <td>20.0 m</td>\n",
       "      <td>44 m/s</td>\n",
       "      <td>0.13 mach</td>\n",
       "      <td>36.4 m</td>\n",
       "      <td>11.6 m</td>\n",
       "      <td>39.9 m</td>\n",
       "      <td>58.6779 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1.277 s</td>\n",
       "      <td>30.0 m</td>\n",
       "      <td>40 m/s</td>\n",
       "      <td>0.12 mach</td>\n",
       "      <td>51.9 m</td>\n",
       "      <td>15.5 m</td>\n",
       "      <td>57.9 m</td>\n",
       "      <td>55.4809 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1.729 s</td>\n",
       "      <td>40.0 m</td>\n",
       "      <td>35 m/s</td>\n",
       "      <td>0.10 mach</td>\n",
       "      <td>65.4 m</td>\n",
       "      <td>18.0 m</td>\n",
       "      <td>74.5 m</td>\n",
       "      <td>51.4204 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>2.193 s</td>\n",
       "      <td>50.0 m</td>\n",
       "      <td>31 m/s</td>\n",
       "      <td>0.09 mach</td>\n",
       "      <td>76.9 m</td>\n",
       "      <td>19.0 m</td>\n",
       "      <td>89.7 m</td>\n",
       "      <td>46.1956 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>2.668 s</td>\n",
       "      <td>60.0 m</td>\n",
       "      <td>27 m/s</td>\n",
       "      <td>0.08 mach</td>\n",
       "      <td>86.2 m</td>\n",
       "      <td>18.5 m</td>\n",
       "      <td>103.4 m</td>\n",
       "      <td>39.4090 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>3.152 s</td>\n",
       "      <td>70.0 m</td>\n",
       "      <td>24 m/s</td>\n",
       "      <td>0.07 mach</td>\n",
       "      <td>93.3 m</td>\n",
       "      <td>16.4 m</td>\n",
       "      <td>115.4 m</td>\n",
       "      <td>30.5990 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>3.646 s</td>\n",
       "      <td>80.0 m</td>\n",
       "      <td>21 m/s</td>\n",
       "      <td>0.06 mach</td>\n",
       "      <td>98.0 m</td>\n",
       "      <td>12.7 m</td>\n",
       "      <td>125.8 m</td>\n",
       "      <td>19.4106 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>4.148 s</td>\n",
       "      <td>90.0 m</td>\n",
       "      <td>20 m/s</td>\n",
       "      <td>0.06 mach</td>\n",
       "      <td>100.2 m</td>\n",
       "      <td>7.2 m</td>\n",
       "      <td>134.5 m</td>\n",
       "      <td>6.0005 °</td>\n",
       "      <td>RANGE</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>4.364 s</td>\n",
       "      <td>94.2 m</td>\n",
       "      <td>20 m/s</td>\n",
       "      <td>0.06 mach</td>\n",
       "      <td>100.4 m</td>\n",
       "      <td>4.4 m</td>\n",
       "      <td>137.7 m</td>\n",
       "      <td>-0.1370 °</td>\n",
       "      <td>APEX</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>4.658 s</td>\n",
       "      <td>100.0 m</td>\n",
       "      <td>20 m/s</td>\n",
       "      <td>0.06 mach</td>\n",
       "      <td>100.0 m</td>\n",
       "      <td>-0.0 m</td>\n",
       "      <td>141.4 m</td>\n",
       "      <td>-8.5431 °</td>\n",
       "      <td>ZERO_DOWN|RANGE</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       time distance velocity       mach   height slant_height slant_distance  \\\n",
       "0   0.000 s    0.0 m   55 m/s  0.16 mach   -0.0 m       -0.0 m          0.0 m   \n",
       "1   0.412 s   10.0 m   50 m/s  0.15 mach   19.0 m        6.4 m         20.5 m   \n",
       "2   0.838 s   20.0 m   44 m/s  0.13 mach   36.4 m       11.6 m         39.9 m   \n",
       "3   1.277 s   30.0 m   40 m/s  0.12 mach   51.9 m       15.5 m         57.9 m   \n",
       "4   1.729 s   40.0 m   35 m/s  0.10 mach   65.4 m       18.0 m         74.5 m   \n",
       "5   2.193 s   50.0 m   31 m/s  0.09 mach   76.9 m       19.0 m         89.7 m   \n",
       "6   2.668 s   60.0 m   27 m/s  0.08 mach   86.2 m       18.5 m        103.4 m   \n",
       "7   3.152 s   70.0 m   24 m/s  0.07 mach   93.3 m       16.4 m        115.4 m   \n",
       "8   3.646 s   80.0 m   21 m/s  0.06 mach   98.0 m       12.7 m        125.8 m   \n",
       "9   4.148 s   90.0 m   20 m/s  0.06 mach  100.2 m        7.2 m        134.5 m   \n",
       "10  4.364 s   94.2 m   20 m/s  0.06 mach  100.4 m        4.4 m        137.7 m   \n",
       "11  4.658 s  100.0 m   20 m/s  0.06 mach  100.0 m       -0.0 m        141.4 m   \n",
       "\n",
       "        angle             flag  \n",
       "0   63.2914 °            RANGE  \n",
       "1   61.2293 °            RANGE  \n",
       "2   58.6779 °            RANGE  \n",
       "3   55.4809 °            RANGE  \n",
       "4   51.4204 °            RANGE  \n",
       "5   46.1956 °            RANGE  \n",
       "6   39.4090 °            RANGE  \n",
       "7   30.5990 °            RANGE  \n",
       "8   19.4106 °            RANGE  \n",
       "9    6.0005 °            RANGE  \n",
       "10  -0.1370 °             APEX  \n",
       "11  -8.5431 °  ZERO_DOWN|RANGE  "
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = tball.dataframe(True).drop(columns=['drop_angle', 'windage', 'windage_angle', 'density_ratio', 'energy', 'drag', 'ogw'])\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9f32293",
   "metadata": {},
   "source": [
    "For every point in the trajectory:\n",
    "\n",
    "* \"distance\" is always the $x$ coordinate, which is the horizontal distance from the sight towards the target.\n",
    "* \"height\" is always the $y$ coordinate, which is the distance above or below the horizontal plane containing the sight.  Height is measured orthogonal to the horizontal axis.\n",
    "* \"slant_distance\" is the distance towards the target *along the sight line*, which meets the origin at the slant angle (here, 45 degrees) relative to the horizontal.\n",
    "* \"slant_height\" is the distance above or below the sight line, orthogonal to the sight line.  This is analogous to the height in the horizontal reference frame.\n",
    "\n",
    "Given a slant angle $\\theta$ it is straightforward to transform between the horizontal and slant frames:\n",
    "\n",
    "* slant_distance $= x \\cos(\\theta) + y \\sin(\\theta)$\n",
    "* slant_height $= y \\cos(\\theta) - x \\sin(\\theta)$\n",
    "\n",
    "## Diagram\n",
    "\n",
    "To illustrate, we'll diagram these four lengths on the trajectory plot for the middle trajectory point (where \"distance\" is 50m):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "d257c16d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 41848 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "poi = tball.get_at_distance(Distance.Meter(50))\n",
    "from py_ballisticcalc.visualize.plot import trajectory_as_plot\n",
    "ax = trajectory_as_plot(tball)\n",
    "ax.legend().set_visible(False)\n",
    "ax.set_ylim([0, 110])\n",
    "ax.set_xlabel('')\n",
    "ax.set_ylabel('')\n",
    "for txt in ax.texts:\n",
    "    if \"Launch\" in txt.get_text():\n",
    "        txt.set_position((51, 105))\n",
    "    if \"Sight\" in txt.get_text():\n",
    "        txt.set_color('purple')\n",
    "        txt.set_fontsize(12)\n",
    "ax.set_aspect('equal')\n",
    "distance = poi.distance >> Distance.Meter\n",
    "height = poi.height >> Distance.Meter\n",
    "# Point of interest\n",
    "ax.plot(distance, height, 'o', color='red', markersize=12, label='Target')\n",
    "# Distance\n",
    "ax.plot([0, distance], [0, 0], color='blue', linewidth=5)\n",
    "ax.text(5 + distance / 2, 2, 'distance', color='blue', fontsize=12, ha='center', va='bottom')\n",
    "# Height\n",
    "ax.plot([distance, distance], [0, height], color='green', linewidth=2)\n",
    "ax.text(distance+2, height/2 - 5, 'height', color='green', fontsize=12, rotation=90, va='center', ha='left')\n",
    "# slant_distance\n",
    "angle = slant_angle >> Angular.Degree\n",
    "sine = math.sin(slant_angle >> Angular.Radian)\n",
    "slant_distance = sine * (poi.slant_distance >> Distance.Meter)\n",
    "center = (slant_distance) / 2\n",
    "ax.plot([0, slant_distance], [0, slant_distance], color='purple', linewidth=2)\n",
    "ax.text(center+3, center-5, 'slant-distance', color='purple', fontsize=12, rotation=angle, ha='center', va='bottom')\n",
    "# Target drop\n",
    "slant_height = poi.slant_height >> Distance.Meter\n",
    "ax.plot([slant_distance, distance], [slant_distance, height], color='black', linewidth=2)\n",
    "ax.text(10 + slant_distance - sine * slant_height, 10 + height - sine * slant_height, 'slant-height', color='black',\n",
    "        fontsize=12, rotation=angle-90, va='center', ha='center')\n",
    "ax.text(7, 1, r'$\\theta$', color='purple', fontsize=14, ha='left', va='bottom')\n",
    "ax.text(100, 1, r'($\\theta$ = slant angle)', color='black', fontsize=12, ha='right', va='bottom')\n",
    "#plt.savefig('slant_terms.svg', bbox_inches='tight')\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-ballisticcalc",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
