Calculate the area of intersection of two rotated rectangles in python

后端 未结 3 1544
粉色の甜心
粉色の甜心 2021-02-12 18:02

I have two 2D rotated rectangles, defined as an (center x,center y, height, width) and an angle of rotation (0-360°). How would I calculate the area of intersection of these two

3条回答
  •  忘掉有多难
    2021-02-12 18:15

    Here is a solution that does not use any libraries outside of Python's standard library.

    Determining the area of the intersection of two rectangles can be divided in two subproblems:

    • Finding the intersection polygon, if any;
    • Determine the area of the intersection polygon.

    Both problems are relatively easy when you work with the vertices (corners) of the rectangles. So first you have to determine these vertices. Assuming the coordinate origin is in the center of the rectangle, the vertices are, starting from the lower left in a counter-clockwise direction: (-w/2, -h/2), (w/2, -h/2), (w/2, h/2), and (-w/2, h/2). Rotating this over the angle a, and translating them to the proper position of the rectangle's center, these become: (cx + (-w/2)cos(a) - (-h/2)sin(a), cy + (-w/2)sin(a) + (-h/2)cos(a)), and similar for the other corner points.

    A simple way to determine the intersection polygon is the following: you start with one rectangle as the candidate intersection polygon. Then you apply the process of sequential cutting (as described here. In short: you take each edges of the second rectangle in turn, and remove all parts from the candidate intersection polygon that are on the "outer" half plane defined by the edge (extended in both directions). Doing this for all edges leaves the candidate intersection polygon with only the parts that are inside the second rectangle or on its boundary.

    The area of the resulting polygon (defined by a series of vertices) can be calculated from the coordinates of the vertices. You sum the cross products of the vertices of each edge (again in counter-clockwise order), and divide that by two. See e.g. www.mathopenref.com/coordpolygonarea.html

    Enough theory and explanation. Here is the code:

    from math import pi, cos, sin
    
    
    class Vector:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def __add__(self, v):
            if not isinstance(v, Vector):
                return NotImplemented
            return Vector(self.x + v.x, self.y + v.y)
    
        def __sub__(self, v):
            if not isinstance(v, Vector):
                return NotImplemented
            return Vector(self.x - v.x, self.y - v.y)
    
        def cross(self, v):
            if not isinstance(v, Vector):
                return NotImplemented
            return self.x*v.y - self.y*v.x
    
    
    class Line:
        # ax + by + c = 0
        def __init__(self, v1, v2):
            self.a = v2.y - v1.y
            self.b = v1.x - v2.x
            self.c = v2.cross(v1)
    
        def __call__(self, p):
            return self.a*p.x + self.b*p.y + self.c
    
        def intersection(self, other):
            # See e.g.     https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Using_homogeneous_coordinates
            if not isinstance(other, Line):
                return NotImplemented
            w = self.a*other.b - self.b*other.a
            return Vector(
                (self.b*other.c - self.c*other.b)/w,
                (self.c*other.a - self.a*other.c)/w
            )
    
    
    def rectangle_vertices(cx, cy, w, h, r):
        angle = pi*r/180
        dx = w/2
        dy = h/2
        dxcos = dx*cos(angle)
        dxsin = dx*sin(angle)
        dycos = dy*cos(angle)
        dysin = dy*sin(angle)
        return (
            Vector(cx, cy) + Vector(-dxcos - -dysin, -dxsin + -dycos),
            Vector(cx, cy) + Vector( dxcos - -dysin,  dxsin + -dycos),
            Vector(cx, cy) + Vector( dxcos -  dysin,  dxsin +  dycos),
            Vector(cx, cy) + Vector(-dxcos -  dysin, -dxsin +  dycos)
        )
    
    def intersection_area(r1, r2):
        # r1 and r2 are in (center, width, height, rotation) representation
        # First convert these into a sequence of vertices
    
        rect1 = rectangle_vertices(*r1)
        rect2 = rectangle_vertices(*r2)
    
        # Use the vertices of the first rectangle as
        # starting vertices of the intersection polygon.
        intersection = rect1
    
        # Loop over the edges of the second rectangle
        for p, q in zip(rect2, rect2[1:] + rect2[:1]):
            if len(intersection) <= 2:
                break # No intersection
    
            line = Line(p, q)
    
            # Any point p with line(p) <= 0 is on the "inside" (or on the boundary),
            # any point p with line(p) > 0 is on the "outside".
    
            # Loop over the edges of the intersection polygon,
            # and determine which part is inside and which is outside.
            new_intersection = []
            line_values = [line(t) for t in intersection]
            for s, t, s_value, t_value in zip(
                intersection, intersection[1:] + intersection[:1],
                line_values, line_values[1:] + line_values[:1]):
                if s_value <= 0:
                    new_intersection.append(s)
                if s_value * t_value < 0:
                    # Points are on opposite sides.
                    # Add the intersection of the lines to new_intersection.
                    intersection_point = line.intersection(Line(s, t))
                    new_intersection.append(intersection_point)
    
            intersection = new_intersection
    
        # Calculate area
        if len(intersection) <= 2:
            return 0
    
        return 0.5 * sum(p.x*q.y - p.y*q.x for p, q in
                         zip(intersection, intersection[1:] + intersection[:1]))
    
    
    if __name__ == '__main__':
        r1 = (10, 15, 15, 10, 30)
        r2 = (15, 15, 20, 10, 0)
        print(intersection_area(r1, r2))
    

提交回复
热议问题