# Copyright (C) 2003 by Intevation GmbH
# Authors:
# Martin Mueller <mmueller@intevation.de>
# Bernhard Herzog <bh@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with the software for details.

"""Hit testing functions"""

__version__ = "$Revision: 1589 $"
# $Source$
# $Id: hittest.py 1589 2003-08-15 12:49:08Z bh $

from math import hypot, sqrt

def line_hit(sx, sy, ex, ey, px, py):
    """Determine whether the line fom (SX, SY) to (EX, EY) is `hit' by a
    click at (PX, PY), or whether a polygon containing this line is hit
    in the interior at (PX, PY).

    Return -1 if the line it self his hit. Otherwise, return +1 if a
    horizontal line from (PX, PY) to (-Infinity, PY) intersects the line
    and 0 if it doesn't.

    The nonnegative return values can be used to determine whether (PX, PY)
    is an interior point of a polygon according to the even-odd rule.
    """

    if (ey < sy):
        sx, ex = ex, sx
        sy, ey = ey, sy

    not_horizontal = ey > sy + 2
    if not_horizontal and (py >= ey or py < sy):
        return 0

    vx = ex - sx
    vy = ey - sy

    len = sqrt(vx * vx + vy * vy)
    if not len:
        # degenerate case of coincident end points. Assumes that some
        # other part of the code has already determined whether the end
        # point is hit.
        return 0

    dx = px - sx
    dy = py - sy
    dist = vx * dy - vy * dx

    if ((not_horizontal or (px >= sx and px <= ex) or (px >= ex and px <= sx))
        and abs(dist) <= (len * 2)):
        return -1

    # horizontal lines (vy == 0) always return 0 here.
    return vy and py < ey and py >= sy and dx * abs(vy) > vx * abs(dy)


def polygon_hit(points, x, y):
    """Return whether x, y is in the polygon or on or near the boundary

    The return value is -1 if the point (x, y) is near the boundary, 1
    if the point is inside and 0 if it's outside.

    The points argument should be a list of lists of coordinate pairs.
    """
    crossings = 0
    for ring in points:
        for i in range(len(ring) - 1):
            sx, sy = ring[i]
            ex, ey = ring[i + 1]
            hit = line_hit(sx, sy, ex, ey, x, y)
            if hit < 0:
                return hit
            crossings += hit
    return crossings % 2


def arc_hit(points, x, y):
    """Return whether x, y is on or near the arc's boundary

    The return value is -1 if the point (x, y) is near the boundary, 1
    if the point is inside and 0 if it's outside.

    The points argument should be a list of lists of coordinate pairs.
    """
    return polygon_hit(points, x, y) < 0
