|
| 1 | +import math |
| 2 | +import random |
| 3 | + |
1 | 4 | import pytest |
2 | 5 | from arcade import hitbox |
3 | 6 |
|
@@ -174,3 +177,69 @@ def get_adjusted_points(self): |
174 | 177 | # The skew turns the box's vertical edges into diagonals |
175 | 178 | axes = SkewedHitBox(points)._get_axes() |
176 | 179 | assert _axis_directions(axes) == {(0.707107, -0.707107)} |
| 180 | + |
| 181 | + |
| 182 | +def test_radius(): |
| 183 | + """The radius is the distance from the position to the farthest point""" |
| 184 | + hb = hitbox.HitBox(points) # (0, 0) to (10, 10), so (10, 10) is farthest |
| 185 | + assert hb._get_radius() == pytest.approx(200**0.5, rel=1e-5) |
| 186 | + # Never smaller than the true distance, so rounding can't cause misses |
| 187 | + assert hb._get_radius() >= 200**0.5 |
| 188 | + |
| 189 | + # Cached, and not changed by moving |
| 190 | + radius = hb._get_radius() |
| 191 | + assert hb._radius == radius |
| 192 | + hb.position = (100.0, -50.0) |
| 193 | + assert hb._get_radius() == radius |
| 194 | + |
| 195 | + # Recalculated when the scale changes, including negative scales |
| 196 | + hb.scale = (2.0, -0.5) |
| 197 | + assert hb._radius is None |
| 198 | + assert hb._get_radius() == pytest.approx((20**2 + 5**2) ** 0.5, rel=1e-5) |
| 199 | + |
| 200 | + |
| 201 | +def test_radius_rotatable(): |
| 202 | + """Rotation doesn't change the radius""" |
| 203 | + rot = hitbox.HitBox(octagon).create_rotatable() |
| 204 | + radius = rot._get_radius() |
| 205 | + assert radius == pytest.approx(20**0.5, rel=1e-5) |
| 206 | + for angle in (30.0, 45.0, 90.0, 217.0): |
| 207 | + rot.angle = angle |
| 208 | + assert rot._get_radius() == radius |
| 209 | + farthest = max( |
| 210 | + ((x - rot.position[0]) ** 2 + (y - rot.position[1]) ** 2) ** 0.5 |
| 211 | + for x, y in rot.get_adjusted_points() |
| 212 | + ) |
| 213 | + assert farthest <= radius |
| 214 | + |
| 215 | + |
| 216 | +def test_radius_empty(): |
| 217 | + assert hitbox.HitBox([])._get_radius() == pytest.approx(0.0, abs=1e-5) |
| 218 | + |
| 219 | + |
| 220 | +def test_radius_subclass_override(): |
| 221 | + """The radius comes from get_adjusted_points() if a subclass overrides it.""" |
| 222 | + |
| 223 | + class StretchedHitBox(hitbox.HitBox): |
| 224 | + def get_adjusted_points(self): |
| 225 | + px, py = self.position |
| 226 | + return [(x * 3 + px, y + py) for x, y in self.points] |
| 227 | + |
| 228 | + hb = StretchedHitBox(points, position=(5.0, 5.0)) |
| 229 | + assert hb._get_radius() == pytest.approx((30**2 + 10**2) ** 0.5, rel=1e-5) |
| 230 | + |
| 231 | + |
| 232 | +def test_radius_covers_rounding(): |
| 233 | + """Rounding in the adjusted points must never put a point outside the radius""" |
| 234 | + rng = random.Random(0) |
| 235 | + for _ in range(2000): |
| 236 | + pts = [(rng.uniform(-60, 60), rng.uniform(-60, 60)) for _ in range(rng.randint(3, 8))] |
| 237 | + rot = hitbox.HitBox(pts).create_rotatable(angle=rng.uniform(0, 360)) |
| 238 | + rot.scale = (rng.uniform(-3, 3), rng.uniform(-3, 3)) |
| 239 | + far = rng.choice([1.0, 1e3, 1e5, 1e7]) |
| 240 | + rot.position = (rng.uniform(-far, far), rng.uniform(-far, far)) |
| 241 | + |
| 242 | + radius = rot._get_radius() |
| 243 | + px, py = rot.position |
| 244 | + for x, y in rot.get_adjusted_points(): |
| 245 | + assert math.hypot(x - px, y - py) <= radius |
0 commit comments