Better readability for SAT demo
This commit is contained in:
parent
36ce94d3de
commit
72860a3e6b
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user