# HG changeset patch # User Jeremy Thurgood # Date 1304792886 -7200 # Node ID 35919d12b792a8e017e8e78d34f3fcbd9756b591 # Parent 1abb53ae1a6ae225baf03579f5d224834c1dc3e2 Path-based collision minimisation and axis-projection backout. diff -r 1abb53ae1a6a -r 35919d12b792 skaapsteker/physics.py --- a/skaapsteker/physics.py Sat May 07 17:29:10 2011 +0200 +++ b/skaapsteker/physics.py Sat May 07 20:28:06 2011 +0200 @@ -12,7 +12,8 @@ from . import options from .constants import EPSILON -from .utils import cadd, csub, cmul, cdiv, cclamp, cint, cneg, cabs +from .utils import (cadd, csub, cmul, cdiv, cclamp, cabsmax, cint, cneg, cabs, + rect_projection) class Sprite(pygame.sprite.Sprite): @@ -252,29 +253,87 @@ self._last_time = now return dt + + # def collide_sprite(self, dt, sprite): + # sprite.apply_velocity(dt) + # sprite_collides = sprite.collide_rect.colliderect + # collisions = [] + # for other in self._collision_groups[sprite.collision_layer]: + # if sprite_collides(other.collide_rect) \ + # and sprite.check_collides(other): + # collisions.append(other) + # if collisions: + # self._backout_collisions(sprite, collisions, dt) + # contact_rect = pygame.Rect( + # (sprite.collide_rect.left, sprite.collide_rect.bottom), + # (sprite.collide_rect.width, 1)) + # return contact_rect.colliderect + + + def get_sprite_collisions(self, dt, sprite): + sprite.apply_velocity(dt) + sprite_collides = sprite.collide_rect.colliderect + collisions = [] + for other in self._collision_groups[sprite.collision_layer]: + if (sprite_collides(other.collide_rect) + and sprite.check_collides(other)): + collisions.append(other) + return collisions + + + def path_collide(self, dt, sprite): + dts = [dt/10] * 9 + dts.append(dt - sum(dts)) + dtf_acc = 0 + collisions = [] + for dtf in dts: + dtf_acc += dtf + collisions = self.get_sprite_collisions(dtf, sprite) + for col in collisions: + if sprite.block and (col.floor or col.block): + return collisions, dtf_acc + return collisions, dt + + + def collide_sprite(self, dt, sprite): + initial_pos = sprite._float_pos + collisions = self.get_sprite_collisions(dt, sprite) + escape_vector = (0, 0) + if collisions: + # If we've collided, reset and try again with smaller time increments + sprite.update_position(initial_pos) + collisions, dtf = self.path_collide(dt, sprite) + for col in collisions: + if sprite.block and (col.floor or col.block): + escape_vector = cabsmax(escape_vector, rect_projection(sprite.collide_rect, col.collide_rect)) + sprite.collided(col) + sprite.update_position(cadd(sprite._float_pos, escape_vector)) + # if escape_vector[0] != 0: + # sprite.velocity = (0, sprite.velocity[1]) + # if escape_vector[1] != 0: + # sprite.velocity = (sprite.velocity[0], 1) + # self._backout_collisions(sprite, collisions, dtf) + contact_rect = pygame.Rect( + cadd(sprite.collide_rect.bottomleft, (1, 0)), + (sprite.collide_rect.width, 1)) + return contact_rect.colliderect + + def update_sprite_positions(self, dt): # position update and collision check (do last) for sprite in self._mobiles: - sprite.apply_velocity(dt) - sprite_collides = sprite.collide_rect.colliderect - collisions = [] - for other in self._collision_groups[sprite.collision_layer]: - if sprite_collides(other.collide_rect) \ - and sprite.check_collides(other): - collisions.append(other) - if collisions: - self._backout_collisions(sprite, collisions, dt) - contact_rect = pygame.Rect( - (sprite.collide_rect.left, sprite.collide_rect.bottom), - (sprite.collide_rect.width, 1)) - collides = contact_rect.colliderect - floors = [] + collides = self.collide_sprite(dt, sprite) # Are we currently in contact with the ground? + if not sprite.block: + continue + floors = [] sprite.on_solid = False for other in self._collision_groups[sprite.collision_layer]: if (other.floor or other.block) and collides(other.floor_rect): sprite.on_solid = True + if sprite.velocity[1] > 0: + sprite.velocity = (sprite.velocity[0], 0) floors.append(other) sprite.check_floors(floors) diff -r 1abb53ae1a6a -r 35919d12b792 skaapsteker/sprites/player.py --- a/skaapsteker/sprites/player.py Sat May 07 17:29:10 2011 +0200 +++ b/skaapsteker/sprites/player.py Sat May 07 20:28:06 2011 +0200 @@ -41,7 +41,6 @@ } self._inv_cache = {} # invisible fox image cache self._shield_cache = {} # shielded fox image cache - self._shield_image = 0 # shield image # State flags and such self.attacking = 0 self.running = False @@ -120,7 +119,8 @@ cur_pos = (0, 0) # TODO: can save a lot of calculation here by caching collision rects cand_image = images[int(self._animation_frame)] - cand_collide_rect = cand_image.get_bounding_rect(1).inflate(-2,-2) + # cand_collide_rect = cand_image.get_bounding_rect(1).inflate(-2,-2) + cand_collide_rect = cand_image.get_bounding_rect(1) cand_rect = cand_image.get_rect() cand_rect_offset = cand_rect.centerx - cand_collide_rect.centerx, cand_rect.bottom - cand_collide_rect.bottom cand_rect.midbottom = cur_pos[0] + cand_rect_offset[0], cur_pos[1] + cand_rect_offset[1] diff -r 1abb53ae1a6a -r 35919d12b792 skaapsteker/utils.py --- a/skaapsteker/utils.py Sat May 07 17:29:10 2011 +0200 +++ b/skaapsteker/utils.py Sat May 07 20:28:06 2011 +0200 @@ -27,8 +27,29 @@ cmul = mk_cop(operator.mul) cdiv = mk_cop(operator.div) cclamp = mk_cop(lambda a, b: max(min(a, b), -b)) +cabsmax = mk_cop(lambda a, b: a if abs(a) > abs(b) else b) cint = mk_cuop(int) cneg = mk_cuop(lambda a: -a) cabs = mk_cuop(abs) + +def rect_projection(rect1, rect2): + if not rect1.colliderect(rect2): + # No collision? + return (0, 0) + + if rect1.center[0] < rect2.center[0]: + x_projection = rect2.left - rect1.right + else: + x_projection = rect2.right - rect1.left + + if rect1.center[1] < rect2.center[1]: + y_projection = rect2.top - rect1.bottom + else: + y_projection = rect2.bottom - rect1.top + + if abs(x_projection) < abs(y_projection): + return (x_projection, 0) + return (0, y_projection) +