From 5cf9e0ceb3a2057c788ee874197c5062ea724623 Mon Sep 17 00:00:00 2001 From: Piper Thunstrom Date: Mon, 11 May 2020 22:03:42 -0400 Subject: [PATCH 1/2] Adds sprite_in_view method to replace old sprite_in_viewport. --- ppb/camera.py | 20 ++++++++++++++++++++ tests/test_camera.py | 40 +++++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/ppb/camera.py b/ppb/camera.py index b5927b2b..b86ccae9 100644 --- a/ppb/camera.py +++ b/ppb/camera.py @@ -13,6 +13,7 @@ from ppb_vector import Vector from ppb.sprites import RectangleShapeMixin +from ppb.sprites import Sprite class Camera(RectangleShapeMixin): @@ -110,6 +111,25 @@ def point_is_visible(self, point: Vector) -> bool: and self.bottom <= point.y <= self.top ) + def sprite_in_view(self, sprite: Sprite) -> bool: + """ + Determine if a given sprite is in view of the camera. + + Does not guarantee that the sprite will be rendered, only that it + exists in the visible space. + + :param sprite: The sprite to check + :type: Sprite + :return: Whether the sprite is in the space in view of the camera. + :rtype: bool + """ + width = max(self.right, sprite.right) - min(self.left, sprite.left) + height = max(self.top, sprite.top) - min(self.bottom, sprite.bottom) + max_width = self.width + sprite.width + max_height = self.height + sprite.height + print(f"W: {width}, H: {height}, MW: {max_width}, MH: {max_height}") + return width < max_width and height < max_height + def translate_point_to_screen(self, point: Vector) -> Vector: """ Convert a vector from game position to screen position. diff --git a/tests/test_camera.py b/tests/test_camera.py index 4770b925..6b0cbf47 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -104,27 +104,25 @@ def test_camera_translate_point_to_game_space(camera, position, point, expected) assert camera.translate_point_to_game_space(point) == expected -# @pytest.mark.skip(reason="Test for old camera. Will want to restore this functionality in new camera.") -# def test_sprite_in_viewport(): -# # Added the expected pixel ratio due to change in default breaking this test. -# # 80 is the legacy value. -# cam = OldCamera(viewport=(0, 0, 800, 600), pixel_ratio=80) -# -# class Thing(Sprite): -# def __init__(self, position=Vector(2, 2)): -# super().__init__() -# self.size = 2 -# self.position = position -# -# sprite_in = Thing(Vector(-3, -1)) -# sprite_half_in = Thing(Vector(5, -2)) -# sprite_out = Thing(Vector(2, 5)) -# -# assert not cam.in_frame(sprite_out) -# assert cam.in_frame(sprite_in) -# assert cam.in_frame(sprite_half_in) -# -# +@pytest.mark.parametrize("input_position, expected", [ + [Vector(-3, 1), True], # Fully inside the camera's view + [Vector(5, -2), True], # partially inside the camera's view + [Vector(2, 6), False], # well outside the Camera's view. + [Vector(6, 0), False], # Outside with edges touching (horizontal) + [Vector(0, 4.75), False], # Outside with edges touching (vertical) +]) +def test_sprite_in_view(camera, input_position, expected): + + class Thing(Sprite): + size = 2 + position = input_position + + test_sprite = Thing() + print(f"Sprite: {test_sprite.position}, {test_sprite.width}, {test_sprite.height}") + print(f"Camera: {camera.position}, {camera.width}, {camera.height}") + assert camera.sprite_in_view(test_sprite) == expected + + # @pytest.mark.skip("Old camera test. Will probably want to rewrite this in the future to support new camera.") # @given( # vp_width=st.integers(min_value=1), From 0fa83e3a98c79f00e1f603d3e1ed580de698aebb Mon Sep 17 00:00:00 2001 From: Piper Thunstrom Date: Tue, 12 May 2020 19:26:39 -0400 Subject: [PATCH 2/2] Adds support for non-rectangular sprites to Camera.sprite_in_view. --- ppb/camera.py | 20 ++++++++++++++++++++ tests/test_camera.py | 15 +++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ppb/camera.py b/ppb/camera.py index b86ccae9..cab12e5d 100644 --- a/ppb/camera.py +++ b/ppb/camera.py @@ -16,6 +16,20 @@ from ppb.sprites import Sprite +def _sprite_has_rectangular_region(sprite): + """ + A replacement function for the ugly compound in sprite_in_view + """ + return ( + hasattr(sprite, "width") + and hasattr(sprite, "height") + and hasattr(sprite, "left") + and hasattr(sprite, "right") + and hasattr(sprite, "top") + and hasattr(sprite, "bottom") + ) + + class Camera(RectangleShapeMixin): """ A simple Camera. @@ -118,11 +132,17 @@ def sprite_in_view(self, sprite: Sprite) -> bool: Does not guarantee that the sprite will be rendered, only that it exists in the visible space. + A sprite without area (size=0 or lacking width, height, or any of the + sides accessors) behave as :method:`point_is_visible`. + :param sprite: The sprite to check :type: Sprite :return: Whether the sprite is in the space in view of the camera. :rtype: bool """ + if not _sprite_has_rectangular_region(sprite): + return self.point_is_visible(sprite.position) + width = max(self.right, sprite.right) - min(self.left, sprite.left) height = max(self.top, sprite.top) - min(self.bottom, sprite.bottom) max_width = self.width + sprite.width diff --git a/tests/test_camera.py b/tests/test_camera.py index 6b0cbf47..1d1e2302 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -7,6 +7,7 @@ from ppb import Sprite from ppb import Vector from ppb.camera import Camera +from ppb.sprites import BaseSprite from .utils import vectors @@ -118,8 +119,18 @@ class Thing(Sprite): position = input_position test_sprite = Thing() - print(f"Sprite: {test_sprite.position}, {test_sprite.width}, {test_sprite.height}") - print(f"Camera: {camera.position}, {camera.width}, {camera.height}") + assert camera.sprite_in_view(test_sprite) == expected + + +@pytest.mark.parametrize("input_position, expected", [ + [Vector(0, 0), True], + [Vector(5, 0), True], + [Vector(0, 3.75), True], + [Vector(10, 10), False] +]) +def test_sprite_in_view_no_dimensions(camera, input_position, expected): + test_sprite = BaseSprite(position=input_position) + assert camera.sprite_in_view(test_sprite) == expected