Better readability for SAT demo
This commit is contained in:
		@ -6,6 +6,40 @@ from ..math import *
 | 
			
		||||
from ..draw import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def compute_separating_axes(p1, p2):
 | 
			
		||||
    """Compute the projections upon the separating axes of polygons p1 and p2.
 | 
			
		||||
 | 
			
		||||
    For efficient collision detection, this could early-exit as soon as one
 | 
			
		||||
    axis is found where the projections of p1 and p2 do not overlap. For
 | 
			
		||||
    visualization purposes, we compute the projections for all axes and
 | 
			
		||||
    return the results."""
 | 
			
		||||
 | 
			
		||||
    # The list of axes to project upon
 | 
			
		||||
    axes = []
 | 
			
		||||
 | 
			
		||||
    # Iterate the normals of both polygons
 | 
			
		||||
    for normal in p1.get_normals() + p2.get_normals():
 | 
			
		||||
        # we can trim out normals that are in the same or opposite direction
 | 
			
		||||
        # as axes we already have, since they will give us the same overlap
 | 
			
		||||
        # information
 | 
			
		||||
        if not any(is_colinear(*normal, *axis) for axis in axes):
 | 
			
		||||
            axes.append(normal)
 | 
			
		||||
 | 
			
		||||
    # helper function to find the min/max projections of vertices onto
 | 
			
		||||
    # the given axis.
 | 
			
		||||
 | 
			
		||||
    def get_min_max(axis, vertices):
 | 
			
		||||
        projections = [dot(*axis, *vertex) for vertex in vertices]
 | 
			
		||||
        return min(projections), max(projections)
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
        (axis,
 | 
			
		||||
         get_min_max(axis, p1.get_translated_vertices()),
 | 
			
		||||
         get_min_max(axis, p2.get_translated_vertices()))
 | 
			
		||||
        for axis in axes
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SeparatingAxisTheorem:
 | 
			
		||||
 | 
			
		||||
    title = "Separating Axis Theorem"
 | 
			
		||||
@ -19,6 +53,7 @@ class SeparatingAxisTheorem:
 | 
			
		||||
        self.generate_shape2()
 | 
			
		||||
 | 
			
		||||
    def generate_shape2(self):
 | 
			
		||||
        """Generates the shape we can move around - a regular polygon."""
 | 
			
		||||
        rotation = 45 if self.shape2 is None else self.shape2.rotation
 | 
			
		||||
        self.shape2 = generate_polygon(*get_cursor_pos(),
 | 
			
		||||
                                       rotation,
 | 
			
		||||
@ -27,9 +62,11 @@ class SeparatingAxisTheorem:
 | 
			
		||||
 | 
			
		||||
    def handle_key_down(self, key):
 | 
			
		||||
        if key == pygame.K_a:
 | 
			
		||||
            # remove a side from the polygon
 | 
			
		||||
            self.shape2_sides = max(self.shape2_sides - 1, 3)
 | 
			
		||||
            self.generate_shape2()
 | 
			
		||||
        if key == pygame.K_d:
 | 
			
		||||
            # add a side to the polygon
 | 
			
		||||
            self.shape2_sides = min(self.shape2_sides + 1, 31)
 | 
			
		||||
            self.generate_shape2()
 | 
			
		||||
 | 
			
		||||
@ -38,77 +75,65 @@ class SeparatingAxisTheorem:
 | 
			
		||||
 | 
			
		||||
        inputs = get_inputs()
 | 
			
		||||
        if inputs[pygame.K_q]:
 | 
			
		||||
            # rotate counter-clockwise by 90 degrees every second
 | 
			
		||||
            self.shape2.rotate((1/60) * -90)
 | 
			
		||||
        if inputs[pygame.K_e]:
 | 
			
		||||
            # same as above, but clockwise
 | 
			
		||||
            self.shape2.rotate((1/60) * 90)
 | 
			
		||||
 | 
			
		||||
    def draw_axis(self, surface, normal, overlap, min1, max1, min2, max2):
 | 
			
		||||
        offset = scale(*rnormal(*normal), 300)
 | 
			
		||||
 | 
			
		||||
        colliding = overlap < 0
 | 
			
		||||
 | 
			
		||||
        if not colliding:
 | 
			
		||||
            # separating line
 | 
			
		||||
            dist = scale(*normal,
 | 
			
		||||
                         (min1 if min1 > min2 else min2) - overlap * 0.5)
 | 
			
		||||
            line(surface, CYAN,
 | 
			
		||||
                 add(*dist, *offset),
 | 
			
		||||
                 add(*dist, *scale(*lnormal(*normal), 1000)))
 | 
			
		||||
 | 
			
		||||
        # axis
 | 
			
		||||
        line(surface, WHITE,
 | 
			
		||||
             add(*scale(*reverse(*normal), 1000), *offset),
 | 
			
		||||
             add(*scale(*normal, 1000), *offset))
 | 
			
		||||
 | 
			
		||||
        # shape 1
 | 
			
		||||
        line(surface, GREEN if not colliding else RED,
 | 
			
		||||
             add(*scale(*normal, min1), *offset),
 | 
			
		||||
             add(*scale(*normal, max1), *offset), 8)
 | 
			
		||||
        # shape 2
 | 
			
		||||
        line(surface, GREEN if not colliding else RED,
 | 
			
		||||
             add(*scale(*normal, min2), *offset),
 | 
			
		||||
             add(*scale(*normal, max2), *offset), 8)
 | 
			
		||||
 | 
			
		||||
    def render(self, surface):
 | 
			
		||||
        self.handle_input()
 | 
			
		||||
 | 
			
		||||
        normals = []
 | 
			
		||||
        gaps = []
 | 
			
		||||
 | 
			
		||||
        def add_if_not_exists(normal):
 | 
			
		||||
            for existing in normals:
 | 
			
		||||
                if is_colinear(*existing, *normal):
 | 
			
		||||
                    return
 | 
			
		||||
            normals.append(normal)
 | 
			
		||||
        for result in compute_separating_axes(self.shape1, self.shape2):
 | 
			
		||||
 | 
			
		||||
        for normal in self.shape1.get_normals() + self.shape2.get_normals():
 | 
			
		||||
            add_if_not_exists(normal)
 | 
			
		||||
            axis, (min1, max1), (min2, max2) = result
 | 
			
		||||
 | 
			
		||||
        def get_min_max(normal, vertices):
 | 
			
		||||
            _min = float('inf')
 | 
			
		||||
            _max = float('-inf')
 | 
			
		||||
            for vertex in vertices:
 | 
			
		||||
                proj = dot(*normal, *vertex)
 | 
			
		||||
                _min = min(_min, proj)
 | 
			
		||||
                _max = max(_max, proj)
 | 
			
		||||
            return _min, _max
 | 
			
		||||
            # length of the gap between the two projections
 | 
			
		||||
            gap = max(min2 - max1, min1 - max2)
 | 
			
		||||
            gaps.append(gap)
 | 
			
		||||
 | 
			
		||||
        overlaps = []
 | 
			
		||||
        for normal in normals:
 | 
			
		||||
            min1, max1 = get_min_max(normal,
 | 
			
		||||
                                     self.shape1.get_translated_vertices())
 | 
			
		||||
            min2, max2 = get_min_max(normal,
 | 
			
		||||
                                     self.shape2.get_translated_vertices())
 | 
			
		||||
            self.draw_axis(surface, axis, gap, min1, max1, min2, max2)
 | 
			
		||||
 | 
			
		||||
            overlap = max(min2 - max1, min1 - max2)
 | 
			
		||||
            overlaps.append(overlap)
 | 
			
		||||
 | 
			
		||||
            self.draw_axis(surface, normal, overlap,
 | 
			
		||||
                           min1, max1, min2, max2)
 | 
			
		||||
 | 
			
		||||
        colliding = all(overlap < 0 for overlap in overlaps)
 | 
			
		||||
        colliding = all(gap < 0 for gap in gaps)
 | 
			
		||||
        if colliding:
 | 
			
		||||
            text_screen(surface, WHITE, (10, HEIGHT - 20),
 | 
			
		||||
                        f"Min Distance to Resolve: {max(overlaps)}")
 | 
			
		||||
                        f"Min Distance to Resolve: {max(gaps)}")
 | 
			
		||||
 | 
			
		||||
        self.shape1.draw(surface, YELLOW, 1)
 | 
			
		||||
        self.shape2.draw(surface, RED if colliding else WHITE, 1)
 | 
			
		||||
 | 
			
		||||
    def draw_axis(self, surface, normal, gap,  min1, max1, min2, max2):
 | 
			
		||||
        """Draws a separating axis with shape projections, and separating line.
 | 
			
		||||
        Uses colours to clearly indicate whether the projections overlap."""
 | 
			
		||||
 | 
			
		||||
        # since the normal's origin is 0,0 (center of the screen), calcuate
 | 
			
		||||
        # an offset to move all drawing off to one side - in this case 300px.
 | 
			
		||||
        offset = scale(*rnormal(*normal), 300)
 | 
			
		||||
 | 
			
		||||
        overlapping = gap < 0
 | 
			
		||||
 | 
			
		||||
        if not overlapping:
 | 
			
		||||
            # how far from the center of the gap is from the origin
 | 
			
		||||
            dist = scale(*normal, (min1 if min1 > min2 else min2) - 0.5 * gap)
 | 
			
		||||
 | 
			
		||||
            # the separating line
 | 
			
		||||
            line(surface, CYAN,
 | 
			
		||||
                 add(*dist, *offset),
 | 
			
		||||
                 add(*dist, *scale(*lnormal(*normal), 1000)))
 | 
			
		||||
 | 
			
		||||
        # axis, extending off the screen in both directions
 | 
			
		||||
        line(surface, WHITE,
 | 
			
		||||
             add(*offset, *scale(*reverse(*normal), 1000)),
 | 
			
		||||
             add(*offset, *scale(*normal, 1000)))
 | 
			
		||||
 | 
			
		||||
        # projection 1
 | 
			
		||||
        line(surface, RED if overlapping else GREEN,
 | 
			
		||||
             add(*offset, *scale(*normal, min1)),
 | 
			
		||||
             add(*offset, *scale(*normal, max1)), 8)
 | 
			
		||||
 | 
			
		||||
        # projection 2
 | 
			
		||||
        line(surface, RED if overlapping else GREEN,
 | 
			
		||||
             add(*offset, *scale(*normal, min2)),
 | 
			
		||||
             add(*offset, *scale(*normal, max2)), 8)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user