# Miscellaneous utility functions for the HotPath software.
# Last edited on 2021-10-29 09:28:05 by stolfi

import color
import hacks_IMP
import pyx

# MATH HACKS:

def real_quadratic_roots(A, B, C):
  # Returns the roots {t0} and {t1} of the equation {A t^2 + B t + C =
  # 0}, with {t0 < t1}, if they are real and distinct. Otherwise returns
  # {None,None}. Requires {A != 0}. May return {-inf,+inf}.
  return hacks_IMP.real_quadratic_roots(A, B, C)

# GEOMETRIC HACKS

def is_point(p):
  # Returns {True} if {p} is a pair (2-tuple or 2-list) of floats,
  # {False} otherwise.
  return hacks_IMP.is_point(p)

def same_point(p, q, tol):
  # Returns {True} if the coordinates of {p} and {q} differ by at most {tol} 
  return hacks_IMP.same_point(p, q, tol)

def poly_cleanup(PTS, dmin):
  # Returns a copy of the point list {PTS} with points that are closer than {dmin}
  # replaced by a single point.  Result may be funny if there are too many close 
  # points.
  return hacks_IMP.poly_cleanup(PTS, dmin)

def poly_orientation(PTS):
  # The argument {PS} should be a list of of vertices of a simple polygon.
  # Returns {+1} if the vertices are in {CCW} order, {-1} if in {CW}
  # order, 0 if cannot figure it out.
  return hacks_IMP.poly_orientation(PTS)

def filet_center(p, q, ro, rt):
  # Computes the center {t} of the smaller circle of radius {rt} that
  # is tangent to the circles of radius {ro} with centers {p} and {q}.
  # Returns the solution that lies to right of the line from {p} to {q}.
  return hacks_IMP.filet_center(p, q, ro, rt)

def circle_x(y, yctr, rad):
  # positive abscissa of point on circle with center {(0,yctr)} and radius {rad}
  # with ordinate {y}.  Returns 0 if {|y-yctr| >= rad}.
  return hacks_IMP.circle_x(y, yctr, rad)

def cut_poly_with_band(CS, ydir, y0, y1):
  # Given the list {CS} of vertices of a simple polygon {D}, and two
  # floats {y0,y1}, returns a list {LS} of the pieces of polygon that
  # cross the strip of the plane between the lines perpendicular to
  # {ydir} whose coordinates in that direction are {y0,y1}.
  #
  # Specifically, {LS[jpo][kpt]} will be point {kpt} of the piece with index
  # {jpo}. Only considers pieces that completely cross the strip.
  # The pieces will be listed in no particular order.
  return hacks_IMP.cut_poly_with_band(CS, ydir, y0, y1)
  
def clip_seg_to_strip(p,q, ydir, y0,y1):
  # Clips the segment from {p} to {q} to the interior of the strip of the plane
  # perpendicular to {ydir} between coordinates {y0} and {y1} in that direction.
  # Requires {y0 < y1}.
  #
  # If the segment intersects the interior of the strip, returns four
  # values {ps,ploc,qs,qloc} where {ps,qs} are the endpoints of the
  # clipped segment, At most one of them will be on the line {y0}, or on
  # the line {y1}. The value {ploc} is 0 if {ps} is on the {y0} line, 1
  # if {ps} is on the {y1} line, and {None} otherwise (that is, {ps} is
  # the original {p}). The value {qloc} has the same meaning for {qs}
  # and {q}.
  # 
  # Returns {None,None,None,None} if the segment is entirely outside or on the boundary of the strip.
  return hacks_IMP.clip_seg_to_strip(p,q, ydir, y0,y1)
  
def find_point_on_poly(CS,idr,g):
  # Given the list {CS} or vertices of a polygonal line, Returns an
  # index {i_first} such that {CS[i_first]} is the first vertex that is
  # at distance greater than {g} away from the endpoint {idr} (0 = beg,
  # 1 = end) along the polygonal.
  #
  # Also returns the point {q_first} that is at eactly that distance
  # from the endpoint.
  #
  # Fails if {g} is greater than or equal to the computed length of the
  # polygonal (which may be affected by roundoff errors) or the polygonal
  # has fewer than 2 vertices.
  return hacks_IMP.find_point_on_poly(CS,idr,g)

def trim_poly(CS, g0, g1):
  # Given the list {CS} or vertices of a polygonal line,
  # trims a length {g0} from the beginning and a length {g1} from the end,
  # and returns the list of vertices of the resulting polygonal line.
  # The first and last vertices are usually new; all the others are 
  # copied from {CS}.  Fails if the length of the given line is 
  # less than {g0+g1}.
  return hacks_IMP.trim_poly(CS, g0, g1)
  
def fbomb(die):
  # Returns {False} if die is {False}, else fails. Handy in procedures that 
  # verify things.
  hacks_IMP.fbomb(die)
  
# PLOTTING HACKS

def make_canvas(B, dp, scale, frame, grid, nx, ny):
  # Creates a {Pyx} canvas {c} for an array of plots, each with bounding
  # box {B}. The array will have {nx} columns and {ny} rows. Optionally
  # draws a frame and/or a grid on each plot, as requested by the
  # booleans {frame} and {grid}.
  #
  # The procedure returns the new canvas {c} and the sizes {szx,szy} of
  # each array slot.
  #
  # The frame will be drawn around each slot of the array 
  # but slightly displaced inwards from its edges. The grid
  # will span the slot minus a slightly wider margin, so it will
  # almost but not quite touch the frame.
  #
  # If {dp} is not {None}, it must be a 2-vector (pair of floats).
  # Then all frames and grids will be displaced by {dp}.
  #
  # If {scale} is not {None}, the procedure also sets the {pyx} scale
  # factor to that value. If {scale} is {None}, it sets the scale so
  # that the total plot will have a reasonable size in pixels.
  #
  return hacks_IMP.make_canvas(B, dp, scale, frame, grid, nx, ny)

def round_box(B, mrg):
  # Returns the given {box} rounded outwards to edges with integer
  # coordinates, at least {mrg} away from the original ones.
  return hacks_IMP.round_box(B, mrg)
  
def plot_line(c, p, q, dp, clr, wd, dashpat):
  # Draws a line segment from {p} to {q}, displaced by the vector {dp},
  # on the {pyx} context {c}, with color {clr} and width {wd}.
  # 
  # If {dashpat} is not {None}, it should be a pair {(dlen,dgap)}; the
  # line is dashed with dashes of length {dlen} separated by gaps of
  # length {dgap}. These dimensions are absolute (not relative to {wd}).
  # 
  # Nothing is drawn if {wd} is {None} or non-positive.  
  # If {clr} is {None}, it defaults to black.
  hacks_IMP.plot_line(c, p, q, dp, clr, wd, dashpat)

def plot_box(c, B, dp, clr):
  # Paints the interior of the box {B} displaced by the vector {dp} on the {pyx} context {c},
  # with color {clr}. 
  #
  # If {clr} is {None}, uses black. If {dp} is {None}, it defaults to {(0,0)}.
  hacks_IMP.plot_box(c, B, dp, clr)
  
def plot_text(c, tx, p, dp, fsize, texclr):
  # Writes the string {txt} with font size {fsize} (in points) and color {texclr} at point {p}
  # shifted by {dp}.
  # 
  # The {texclr} must be a string naming one of the predefined colors in
  # the {color.sty} LaTeX package. If {None}, the default color (black,
  # normally) is used. If {dp} is {None}, it defaults to {(0,0)}.
  hacks_IMP.plot_text(c, tx, p, dp, fsize, texclr)

def plot_frame(c, B, dp, clr, wd, dashpat, inset):
  # Plots on the {pyx} context {c} a frame around the box {B}, displaced
  # by the vector {dp}, with color {clr}, line width {wd}.
  #
  # If {inset} is not {None}, the frame will be displaced inwards by
  # {inset} (or outwards by {-inset}, if negative). The lines will be
  # dashed with the dash pattern {dashpat} if it isnot {None}.
  #
  # If {dp} is {None}, it defaults to {(0,0)}. If {clr} is None,
  # defaults to black. If {wd} is {None} or non-positive, does nothing.
  hacks_IMP.plot_frame(c, B, dp, clr, wd, dashpat, inset)

def plot_grid(c, B, dp, clr, wd, dashpat, inset, xstep, ystep):
  # Plots on the {pyx} context {c} a grid spanning the box {B}. The grid
  # lines will be at coordinates that are integer multiples of {xstep}
  # and {ystep}.
  #
  # If {inset} is not {None}, the grid edger will be displaced inwards
  # by {inset} (or outwards by {-inset}, if negative) relative to the
  # box {B}. If {dp} is not {None}, the whole grid will be displaced by
  # {dp}. The lines will be dashed with the dash pattern {dashpat} if it
  # is not {None}.
  #
  # If {dp} is {None}, it defaults to {(0,0)}. If {clr} is {None},
  # defaults to a light gray. If {wd} is {None} or non-positive, does
  # nothing.
  hacks_IMP.plot_grid(c, B, dp, clr, wd, dashpat, inset, xstep, ystep)
  
def write_plot(c, name):
  # Writes the {pyx} canvas {c} to an EPS file "{name}.eps", 
  # a PNG file named "{name}.png", and a JPEG file "{name}.jpg".
  hacks_IMP.write_plot(c, name)

def adjust_dash_pattern(rdist, rlen, rgap):
  # Adjusts a dash pattern so that a dashed line will begin and end
  # with full dashes.
  #
  # The parameter {rdist} should be the distance to be traced. The
  # procedure assumes that the ideal dash pattern should have dashes of
  # length {rlen} separated bt gaps of lenth {rgap}. (These measurements
  # may be absolute, or relative to some common unit, e. g. the line
  # width.)
  #
  # If the procedure succeeds, it returns the adjusted dash pattern
  # {(a*rlen, a*rgap)} where {a} is a suitable adjustment factor, near
  # 1. The factor {a} will be such that {rdist} will be exactly
  # {a*((n+1)*rlen + n*rgap)}.
  #
  # The procedure fails if the distance {rdist} is too small (compared
  # to {rlen} and {rgap}) to use a dashed line. In that case it returns
  # {None}.
  #
  # ??? Generalize to allow the first and last dashes be some given multiple of {rlen} ??? 
  return hacks_IMP.adjust_dash_pattern(rdist, rlen, rgap)

def choose_grid_steps(sz):
  # Returns the major and minor grid steps appropriate for a picture
  # that measures {sz} millimeters in the largest dimension.
  # They will be powers of 10 times some simple factor like 1, 2, 5.
  # The minor grid step will be a sub-multiple of the major one.
  return hacks_IMP.choose_grid_steps(sz)

def trace_colors(nc, Y):
  # Returns an array {C} of {nc} colors that can be used to paint
  # different paths, etc. Each element is a {pyx.color.rgb} triple.
  # 
  # The colors will have mean brightness {Y}. If {Y} is {None}, a
  # default value will be used.
  return hacks_IMP.trace_colors(nc, Y)

def link_colors(nc, Y):
  # Returns an array {C} of {nc} colors that can be used to paint different links.
  # Each element is a {pyx.color.rgb} triple.
  # 
  # The colors will have mean brightness {Y}. If {Y} is {None}, a
  # default value will be used.
  return hacks_IMP.link_colors(nc, Y)

def matter_color(Y):
  # Returns the standard {pyx.color.rgb} value for the matter traces
  # to be used by procedures like {move.plot_standard} that do not
  # have that as a parameter.  
  #
  # If {Y} is not {None}, itmust be a number between 0 and 1, and the color will have that 
  # brightness.
  return hacks_IMP.matter_color(Y)

# IMAGE HACKS

def convert_eps_to_png(name):
  # Reads the file "{name}.eps" and converts it to a PNG image file "{name}.png".
  hacks_IMP.convert_eps_to_png(name)

def convert_eps_to_jpg(name):
  # Reads the file "{name}.eps" and converts it to a JPEG image file "{name}.jpg".
  hacks_IMP.convert_eps_to_jpg(name)

