
# conicsEccen.py
# Andrew Davison, ad@coe.psu.ac.th, Oct. 2025

"""
This program displays conic sections (circle, ellipse, parabola, hyperbola) defined by the polar equation: r = l / (1 + e*cos(theta))
where:
  - l is the semi-latus rectum (fixed at 1)
  - e is the eccentricity (adjustable via slider)
  - r, theta are polar coordinates

The visualization shows:
  - The conic curve
  - One focus at the origin (red dot), and the other moves
  - The directrix lines (green dashed line)
  - The conic type based on eccentricity value

Use the slider to adjust eccentricity and see how the 
conic shape changes.
"""

import math
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider



def conicPoints(e, steps=720):
  # Generate Cartesian coordinates
  xs, ys = [], []
  for i in range(steps):
    theta = 2*math.pi * i/steps
    denom = 1 + e * math.cos(theta)
    if abs(denom) < 1e-6:
      xs.append(math.nan)  # Insert NaN to break the line
      ys.append(math.nan)
      continue
    r = l / denom
    # Skip points that are too far (for hyperbolas)
    if abs(r) > 100:
      xs.append(math.nan)
      ys.append(math.nan)
      continue
    x = r * math.cos(theta)
    y = r * math.sin(theta)
    xs.append(x)
    ys.append(y)
  return xs, ys


def conicType(e):
  # Determine conic type from eccentricity
  if abs(e) < 1e-6:   # v.close to 0
    return "Circle"
  elif e < 1:
    return "Ellipse"
  elif abs(e - 1) < 1e-6:  # v.close to 1
    return "Parabola"
  else:
    return "Hyperbola"


def update(val):
  e = slider.val
  eType = conicType(e)

  xs, ys = conicPoints(e)
  line.set_data(xs, ys)  # draw conic

  # Update directrix 1
  drxX1 = l/e if e != 0 else 1e6
  drx1.set_data([drxX1, drxX1], [-10, 10])

  # Update focus 2 and directrix 2
  if eType == "Parabola":
    focus2Dot.set_visible(False)
    drx2.set_visible(False)
  else:  
    a = l/abs(1-e*e)
    c = a*e
    if eType == "Hyperbola":
      focus2Dot.set_data([2*c], [0])
      drxX2 = 2*c-(l/e) if e != 0 else 1e6
    else: # ellipse
      focus2Dot.set_data([-2*c], [0])
      drxX2 = -2*c-(l/e) if e != 0 else 1e6
    focus2Dot.set_visible(True)
    drx2.set_data([drxX2, drxX2], [-10, 10])
    drx2.set_visible(True)

  ax.legend(loc="upper right")
  typeText.set_text(f"Type: {eType}")
  fig.canvas.draw_idle()


# -----------------------------------
l = 1     # Semi-latus rectum (fixed)
e0 = 0.5  # Initial eccentricity: an ellipse
a = l/abs(1-e0*e0)
c = a*e0

fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
ax.set_aspect('equal', adjustable='box')
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)

# Plot initial conic
xs, ys = conicPoints(e0)
(line,) = ax.plot(xs, ys, lw=2, label="Conic")

# focus 1 is at origin
focus1Dot, = ax.plot(0, 0, 'ro', label="Focus")

# focus 2 for an ellipse
focus2Dot, = ax.plot(-2*c, 0, 'ro')

# Plot directrix 1 
drxX1 = l / e0 if e0 != 0 else 1e6
(drx1,) = ax.plot([drxX1, drxX1], [-10, 10], 
                          'g--', label=f"Directrix")

# Plot directrix 2 for an ellipse
drxX2 = -2*c-(l/e0) if e0 != 0 else 1e6
(drx2,) = ax.plot([drxX2, drxX2], [-10, 10],'g--')

# Conic type label
typeText = ax.text(0.02, 0.95, f"Type: {conicType(e0)}", 
                                transform=ax.transAxes)

# Slider
axSlider = plt.axes([0.25, 0.1, 0.5, 0.03])
slider = Slider(axSlider, 'e', 0.0, 2.0, valinit=e0)
slider.on_changed(update)

ax.legend(loc="upper right")
plt.show()
