{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Line-of-Sight and Field-of-View Control\n\nThis example demonstrates the two complementary APIs for aiming and framing the\nobserver's camera in helioprojective coordinates:\n\n- :attr:`~pyvisual.core.mixins.ObserverMixin.observer_los_view` \u2014 sets the\n  field of view as explicit angular extents $(x_0, x_1, y_0, y_1)$ in\n  degrees, measured from Sun-center along the observer's line of sight.\n- :attr:`~pyvisual.core.mixins.ObserverMixin.observer_fov_view` \u2014 sets the\n  (square) field of view by specifying the minimum impact radius\n  $r_{\\min}$ (in $R_\\odot$) that any line of sight must clear.\n\nBoth properties require :attr:`~pyvisual.core.mixins.ObserverMixin.observer_position`\nto be set first, because the focal-point calculation depends on the observer\ndistance.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import numpy as np\nfrom pyvisual import Plot3d"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## LOS View \u2014 Asymmetric Field of View\n\n:attr:`~pyvisual.core.mixins.ObserverMixin.observer_los_view` accepts a\n4-tuple ``(x0, x1, y0, y1)`` of helioprojective angular extents in degrees.\nHere the observer sits at $r = 50\\,R_\\odot$ on the equatorial plane\nand looks back at the Sun with a wide horizontal sweep\n($-15\u00b0$ to $+15\u00b0$) and a narrower vertical window\n($-8\u00b0$ to $+8\u00b0$), placing the viewport aspect ratio at\n$30/16 \\approx 1.875$.\n\nThe focal point is automatically computed from the center of the angular\nrange via the Thomson sphere, so the camera always aims along the correct\nhelioprojective direction.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plotter = Plot3d()\nplotter.add_sun()\nplotter.add_shell(inner_radius=2.0, outer_radius=2.0, opacity=0.15, color='cyan')\nplotter.add_shell(inner_radius=6.0, outer_radius=6.0, opacity=0.10, color='orange')\nplotter.observer_position = 50, np.pi / 2, 0\nplotter.observer_los_view = -15, 15, -8, 8\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## LOS View \u2014 Off-Center Pointing\n\nShifting the angular range off-center moves the focal point away from\nSun-center.  Here the horizontal window is displaced by $+5\u00b0$\n(``x0 = -5``, ``x1 = +15``) so that the camera is biased to the east\nlimb of the Sun.  The vertical extent remains symmetric.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plotter = Plot3d()\nplotter.add_sun()\nplotter.add_shell(inner_radius=2.0, outer_radius=2.0, opacity=0.15, color='cyan')\nplotter.observer_position = 50, np.pi / 2, 0\nplotter.observer_los_view = -5, 15, -8, 8\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## FOV View \u2014 Impact-Parameter Framing\n\n:attr:`~pyvisual.core.mixins.ObserverMixin.observer_fov_view` is a\nhigher-level alternative.  Instead of raw angles, you supply a single scalar\n$r_{\\min}$.\n\nThe result is a square viewport whose edge lines of sight just graze the\nsphere of radius $r_{\\min}$ around Sun-center.  Setting\n$r_{\\min} = 1\\,R_\\odot$ frames the entire solar disc with minimal\nmargin; larger values show the extended corona.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plotter = Plot3d()\nplotter.add_sun()\nplotter.add_shell(inner_radius=2.5, outer_radius=2.5, opacity=0.15, color='cyan')\nplotter.observer_position = 50, np.pi / 2, 0\nplotter.observer_fov_view = 4\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Comparing Impact Radii\n\nThe same observer position is used at three different impact radii to\nillustrate how a smaller $r_{\\min}$ zooms in on the solar disc\nwhile a larger one opens the view out to the extended corona.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>The observer distance is fixed at $50\\,R_\\odot$ throughout.\n   Changing $r_{\\min}$ only rescales the angular FOV; it does not\n   move the camera.</p></div>\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "for rmin in (1.5, 4.0, 10.0):\n    plotter = Plot3d()\n    plotter.add_sun()\n    plotter.add_shell(inner_radius=rmin, outer_radius=rmin, opacity=0.2, color='yellow')\n    plotter.observer_position = 50, np.pi / 2, 0\n    plotter.observer_fov_view = rmin\n    plotter.show()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "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.13.12"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}