Better readability for SAT demo
This commit is contained in:
parent
36ce94d3de
commit
72860a3e6b
@ -6,6 +6,40 @@ from ..math import *
|
|||||||
from ..draw 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:
|
class SeparatingAxisTheorem:
|
||||||
|
|
||||||
title = "Separating Axis Theorem"
|
title = "Separating Axis Theorem"
|
||||||
@ -19,6 +53,7 @@ class SeparatingAxisTheorem:
|
|||||||
self.generate_shape2()
|
self.generate_shape2()
|
||||||
|
|
||||||
def generate_shape2(self):
|
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
|
rotation = 45 if self.shape2 is None else self.shape2.rotation
|
||||||
self.shape2 = generate_polygon(*get_cursor_pos(),
|
self.shape2 = generate_polygon(*get_cursor_pos(),
|
||||||
rotation,
|
rotation,
|
||||||
@ -27,9 +62,11 @@ class SeparatingAxisTheorem:
|
|||||||
|
|
||||||
def handle_key_down(self, key):
|
def handle_key_down(self, key):
|
||||||
if key == pygame.K_a:
|
if key == pygame.K_a:
|
||||||
|
# remove a side from the polygon
|
||||||
self.shape2_sides = max(self.shape2_sides - 1, 3)
|
self.shape2_sides = max(self.shape2_sides - 1, 3)
|
||||||
self.generate_shape2()
|
self.generate_shape2()
|
||||||
if key == pygame.K_d:
|
if key == pygame.K_d:
|
||||||
|
# add a side to the polygon
|
||||||
self.shape2_sides = min(self.shape2_sides + 1, 31)
|
self.shape2_sides = min(self.shape2_sides + 1, 31)
|
||||||
self.generate_shape2()
|
self.generate_shape2()
|
||||||
|
|
||||||
@ -38,77 +75,65 @@ class SeparatingAxisTheorem:
|
|||||||
|
|
||||||
inputs = get_inputs()
|
inputs = get_inputs()
|
||||||
if inputs[pygame.K_q]:
|
if inputs[pygame.K_q]:
|
||||||
|
# rotate counter-clockwise by 90 degrees every second
|
||||||
self.shape2.rotate((1/60) * -90)
|
self.shape2.rotate((1/60) * -90)
|
||||||
if inputs[pygame.K_e]:
|
if inputs[pygame.K_e]:
|
||||||
|
# same as above, but clockwise
|
||||||
self.shape2.rotate((1/60) * 90)
|
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):
|
def render(self, surface):
|
||||||
self.handle_input()
|
self.handle_input()
|
||||||
|
|
||||||
normals = []
|
gaps = []
|
||||||
|
|
||||||
def add_if_not_exists(normal):
|
for result in compute_separating_axes(self.shape1, self.shape2):
|
||||||
for existing in normals:
|
|
||||||
if is_colinear(*existing, *normal):
|
|
||||||
return
|
|
||||||
normals.append(normal)
|
|
||||||
|
|
||||||
for normal in self.shape1.get_normals() + self.shape2.get_normals():
|
axis, (min1, max1), (min2, max2) = result
|
||||||
add_if_not_exists(normal)
|
|
||||||
|
|
||||||
def get_min_max(normal, vertices):
|
# length of the gap between the two projections
|
||||||
_min = float('inf')
|
gap = max(min2 - max1, min1 - max2)
|
||||||
_max = float('-inf')
|
gaps.append(gap)
|
||||||
for vertex in vertices:
|
|
||||||
proj = dot(*normal, *vertex)
|
|
||||||
_min = min(_min, proj)
|
|
||||||
_max = max(_max, proj)
|
|
||||||
return _min, _max
|
|
||||||
|
|
||||||
overlaps = []
|
self.draw_axis(surface, axis, gap, min1, max1, min2, max2)
|
||||||
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())
|
|
||||||
|
|
||||||
overlap = max(min2 - max1, min1 - max2)
|
colliding = all(gap < 0 for gap in gaps)
|
||||||
overlaps.append(overlap)
|
|
||||||
|
|
||||||
self.draw_axis(surface, normal, overlap,
|
|
||||||
min1, max1, min2, max2)
|
|
||||||
|
|
||||||
colliding = all(overlap < 0 for overlap in overlaps)
|
|
||||||
if colliding:
|
if colliding:
|
||||||
text_screen(surface, WHITE, (10, HEIGHT - 20),
|
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.shape1.draw(surface, YELLOW, 1)
|
||||||
self.shape2.draw(surface, RED if colliding else WHITE, 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