diff --git a/arcade/geometry.py b/arcade/geometry.py index e997ca4e9..4dc4866b1 100644 --- a/arcade/geometry.py +++ b/arcade/geometry.py @@ -1,3 +1,5 @@ +import os + shapely_exists = False try: @@ -6,8 +8,10 @@ except ImportError: pass +use_shapely = shapely_exists and not os.environ.get("DISABLE_SHAPELY") +print("Use shapely: " + str(use_shapely)) -if shapely_exists: +if use_shapely: from .geometry_shapely import are_polygons_intersecting # noqa: F401 from .geometry_shapely import is_point_in_polygon # noqa: F401 else: diff --git a/arcade/paths.py b/arcade/paths.py index 2d7505b81..511d03af6 100644 --- a/arcade/paths.py +++ b/arcade/paths.py @@ -2,11 +2,20 @@ Classic A-star algorithm for path finding. """ import sys +import os from arcade.types import Point from arcade import check_for_collision_with_list, SpriteList, Sprite from typing import Union, List, Tuple, Set, Optional -if 'shapely' in sys.modules: +shapely_exists = False +try: + import shapely # noqa: F401 + shapely_exists = True +except ImportError: + pass +use_shapely = shapely_exists and not os.environ.get("DISABLE_SHAPELY") +print("Use shapely for has_line_of_sight: " + str(use_shapely)) +if use_shapely: from .paths_shapely import has_line_of_sight # noqa: F401 else: from .paths_python import has_line_of_sight # noqa: F401 diff --git a/arcade/paths_python.py b/arcade/paths_python.py index 7e28bd8a0..bc82bfc90 100644 --- a/arcade/paths_python.py +++ b/arcade/paths_python.py @@ -27,6 +27,9 @@ def has_line_of_sight(point_1: Point, distance = get_distance(point_1[0], point_1[1], point_2[0], point_2[1]) steps = int(distance // check_resolution) + if distance == 0: + sprite_list = get_sprites_at_point(point_1, walls) + return not (len(sprite_list) > 0) for step in range(steps + 1): step_distance = step * check_resolution u = step_distance / distance diff --git a/benchmarks/bench-shapely-helper.sh b/benchmarks/bench-shapely-helper.sh new file mode 100644 index 000000000..3ae995d67 --- /dev/null +++ b/benchmarks/bench-shapely-helper.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +if [ "$2" == "disabled" ] +then + export DISABLE_SHAPELY=true +fi +python -m benchmarks.$1.bench \ No newline at end of file diff --git a/benchmarks/bench-shapely.sh b/benchmarks/bench-shapely.sh new file mode 100644 index 000000000..baeef08ee --- /dev/null +++ b/benchmarks/bench-shapely.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# On windows, can run via the bash you get with git: +# C:\Program Files\Git\bin\bash.exe + +__dirname="$(CDPATH= cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$__dirname" +cd .. + +bench_name="$1" + +python.exe --version + +hyperfine \ + --show-output \ + --export-markdown benchmarks/results.md \ + --warmup 1 --runs 2 \ + --parameter-list shapely 'enabled,disabled' \ + 'bash ./benchmarks/bench-shapely-helper.sh '"$bench_name"' {shapely}' diff --git a/benchmarks/collisions/bench.py b/benchmarks/collisions/bench.py index cfd5a6d4e..a3a5062dd 100644 --- a/benchmarks/collisions/bench.py +++ b/benchmarks/collisions/bench.py @@ -30,7 +30,7 @@ # like something I might create in a game. rng.seed(2) for i in range(0, WALLS_COUNT): - wall = arcade.SpriteSolidColor(rng.randint(WALL_DIM_MIN, WALL_DIM_MAX), rng.randint(WALL_DIM_MIN, WALL_DIM_MAX), arcade.color.BLACK) + wall = arcade.SpriteSolidColor(rng.randint(WALL_DIM_MIN, WALL_DIM_MAX), rng.randint(WALL_DIM_MIN, WALL_DIM_MAX), color=arcade.color.BLACK) wall.position = rng.randint(0, SCREEN_WIDTH), rng.randint(0, SCREEN_HEIGHT) walls.append(wall) diff --git a/benchmarks/line-of-sight/bench.py b/benchmarks/line-of-sight/bench.py new file mode 100644 index 000000000..5ca977335 --- /dev/null +++ b/benchmarks/line-of-sight/bench.py @@ -0,0 +1,60 @@ +import math +import arcade +import pyglet +import random +import time + +SCREEN_WIDTH = 800 +SCREEN_HEIGHT = 600 + +WALL_DIM_MIN = 10 +WALL_DIM_MAX = 200 +WALLS_COUNT = 10 + +SIMULATE_MINUTES = 0.1 +SIMULATE_FPS = 60 + +# Predictable randomization so that each benchmark is identical +rng = random.Random(0) + +walls = arcade.SpriteList(use_spatial_hash=True) + +window = arcade.Window() + +# Seed chosen manually to create a wall distribution that looked good enough, +# like something I might create in a game. +rng.seed(2) +for i in range(0, WALLS_COUNT): + wall = arcade.SpriteSolidColor(rng.randint(WALL_DIM_MIN, WALL_DIM_MAX), rng.randint(WALL_DIM_MIN, WALL_DIM_MAX), color=arcade.color.BLACK) + wall.position = rng.randint(0, SCREEN_WIDTH), rng.randint(0, SCREEN_HEIGHT) + walls.append(wall) + +# Check for line-of-sight +def check_ray(): + x = rng.randint(0, SCREEN_WIDTH) + y = rng.randint(0, SCREEN_HEIGHT) + hit = arcade.has_line_of_sight(origin, (x, y), walls) + return x, y, hit + +origin = (SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2) +rays = [] +for i in range(0, int(SIMULATE_MINUTES * 60 * SIMULATE_FPS)): + pyglet.clock.tick() + + window.switch_to() + window.dispatch_events() + + rays[:] = [check_ray() for x in range(1000)] + + window.dispatch_event('on_draw') + + window.clear(color=arcade.color.WHITE) + walls.draw() + + # only draw a few: + # A) so the benchmark isn't dominated by slow draw_line calls + # B) to validate that the line-of-sight checks are accurate + # C) so you get a visual indicator of framerate + for ray in rays[:10]: + arcade.draw_line(origin[0], origin[1], ray[0], ray[1], arcade.color.BLUE if ray[2] else arcade.color.RED, 2) + window.flip()