summaryrefslogtreecommitdiff
path: root/circrect.py
blob: a1dd27c7e0a13a06dffdb2e606aa75446391424e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from numbers import Number
from math import sqrt, asin

def clamp(low: Number, high: Number, num: Number) -> Number:
    """Limit range of ``num`` between ``low`` and ``high``"""
    return max(low, min(high, num))

def circrect(a: tuple[Number, Number], b: tuple[Number, Number], c: Number = 1) -> Number:
    """
    Calculate the area of the overlap between a rectangle defined by opposing
    corner points ``a`` and ``b`` and a circle with radius ``c``.

    Args:
        a: first corner coordinate of rectangle
        b: second corner coordinate of rectangle (opposite of b)
        c: circle radius (optional, with default of 1)

    Returns:
        Overlap area (square units)
    """
    # input variable normalization
    a, b = (
      (min(a[0], b[0]), min(a[1], b[1])),
      (max(a[0], b[0]), max(a[1], b[1])),
    )
    c = abs(c)
    a = (clamp(-1, 1, a[0] / c), clamp(-1, 1, a[1] / c))
    b = (clamp(-1, 1, b[0] / c), clamp(-1, 1, b[1] / c))

    # can not have shared area if ab already has no area
    if a == b: return 0.0

    f = lambda x: sqrt(1 - x**2)
    F = lambda x, n: 0.5 * (2*n*x + sqrt(1 - x**2)*x + asin(x))
    g = lambda a, b: F(clamp(a[0], b[0],  f(a[1])), -a[1]) \
                   - F(clamp(a[0], b[0], -f(a[1])), -a[1]) \
                   - F(clamp(a[0], b[0],  f(b[1])), -b[1]) \
                   + F(clamp(a[0], b[0], -f(b[1])), -b[1])
    h = lambda a, b: g((a[0], max(0,  a[1])), (b[0], max(0,  b[1]))) \
                   + g((a[0], max(0, -b[1])), (b[0], max(0, -a[1])))
    o = h(a, b) * (c ** 2)
    return o