changeset 627:35919d12b792

Path-based collision minimisation and axis-projection backout.
author Jeremy Thurgood <firxen@gmail.com>
date Sat, 07 May 2011 20:28:06 +0200
parents 1abb53ae1a6a
children 1fdfc7f03d98
files skaapsteker/physics.py skaapsteker/sprites/player.py skaapsteker/utils.py
diffstat 3 files changed, 97 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- 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)
 
--- 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]
--- 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)
+