diff --git a/README.rst b/README.rst index 2569042d..16245427 100644 --- a/README.rst +++ b/README.rst @@ -130,7 +130,7 @@ Dependencies / Requirements Basically Pymunk have been made to be as easy to install and distribute as possible, usually `pip install` will take care of everything for you. -- Python (Runs on CPython 3.6 and later and Pypy3) +- Python (Runs on CPython 3.8 (might run on earlier pythons as well) and later and Pypy3) - Chipmunk (Prebuilt and included when using binary wheels) - CFFI (will be installed automatically by Pip) - Setuptools (should be included with Pip) diff --git a/docs/src/showcase.rst b/docs/src/showcase.rst index 7d606da7..fc48ce1b 100644 --- a/docs/src/showcase.rst +++ b/docs/src/showcase.rst @@ -260,6 +260,22 @@ Pymunk has been used or referenced in a number of scientific papers. List of papers which has used or mentioned Pymunk: +#. Pan, Hainan, Bailiang Chen, Kaihong Huang, Junkai Ren, Xieyuanli Chen, and Huimin Lu. + "Deep Reinforcement Learning for Flipper Control of Tracked Robots." + arXiv preprint arXiv:2306.10352 (2023). + +#. Kim, Juhyun, Víctor de Lorenzo, and Ángel Goñi‐Moreno. + "Pressure‐dependent growth controls 3D architecture of Pseudomonas putida microcolonies." + Environmental Microbiology Reports (2023). + +#. Tanaka, Koki. + "Development of Granular Jamming Soft Robots From Boundary Constrained to Interconnected Systems." + PhD diss., Illinois Institute of Technology, 2023. + +#. Huang, Bingling, and Yan Jin. + "Social learning in self-organizing systems for complex assembly tasks." + Advanced Engineering Informatics 57 (2023): 102109. + #. Yang, Yulong, Weihua Cao, Linwei Guo, Chao Gan, and Min Wu. "Reinforcement Learning with Reward Shaping and Hybrid Exploration in Sparse Reward Scenes." In 2023 IEEE 6th International Conference on Industrial Cyber-Physical Systems (ICPS), pp. 1-6. IEEE, 2023. diff --git a/dump/colors_logo.py b/dump/colors_logo.py new file mode 100644 index 00000000..1e525754 --- /dev/null +++ b/dump/colors_logo.py @@ -0,0 +1,169 @@ +""" +An example of the determinism of pymunk by coloring balls according to their +position, and then respawning them to verify each ball ends up in the same +place. Inspired by Pymunk user Nam Dao. +""" + + +import os +import random + +import pygame + +import pymunk +import pymunk.pygame_util + + +def new_space(): + space = pymunk.Space() + space.gravity = 0, 900 + static_body = space.static_body + walls = [ + pymunk.Segment(static_body, (0, -500), (600, -500), 100), # top - + pymunk.Segment(static_body, (-30, -500), (-50, 650), 100), # left | + pymunk.Segment(static_body, (-50, 650), (600, 650), 100), # bottom - + pymunk.Segment(static_body, (650, 650), (630, -500), 100), # right | + ] + for wall in walls: + wall.elasticity = 0.9 + + space.add(*walls) + + random.seed(0) + return space + + +def set_colors(color_dict, logo_img, space): + color_dict.clear() + w = logo_img.get_width() + h = logo_img.get_height() + logo_img.lock() + for shape in space.shapes: + if not isinstance(shape, pymunk.Circle): + continue + r = shape.body.position.x / 600 * 255 + g = max((shape.body.position.y - 400) / 200 * 255, 0) + if r < 0 or r > 255 or g < 0 or g > 255: + print(shape.body.position) + shape.color = (255, 255, 255, 255) + continue + shape.color = (r, g, 150, 255) + p = shape.body.position + x = int(p.x) - (600 - w) // 2 + y = int(p.y - 600 + h + 40) + if x >= 0 and x < w and y > 0 and y < h: + color = logo_img.get_at([x, y]) + if color.a > 200: + shape.color = color.r, color.g, color.b, 255 + color_dict[shape.data] = shape.color + logo_img.unlock() + + +pygame.init() +screen = pygame.display.set_mode((600, 600)) +clock = pygame.time.Clock() +font = pygame.font.SysFont("Arial", 14) +text = font.render( + "Press r to reset and respawn all balls." + " Press c to set color of each ball according to its position.", + True, + pygame.Color("darkgray"), +) + +logo_img = pygame.image.load( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "pymunk_logo_sphinx.png") +) +box_surf = pygame.Surface((6, 6)) +box_surf.fill(pygame.Color("white")) +draw_options = pymunk.pygame_util.DrawOptions(screen) + +color_dict = {} +space = new_space() +cnt = max_cnt = 3000 + +while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_p: + pygame.image.save(screen, "colors.png") + elif event.type == pygame.KEYDOWN and event.key == pygame.K_r: + if not color_dict: + set_colors(color_dict, logo_img, space) + space = new_space() + cnt = max_cnt + elif event.type == pygame.KEYDOWN and event.key == pygame.K_c: + set_colors(color_dict, logo_img, space) + elif event.type == pygame.KEYDOWN and event.key == pygame.K_x: + exp_p = pymunk.Vec2d(300, 450) + query_info = space.point_query(exp_p, 50, shape_filter=pymunk.ShapeFilter()) + print(len(query_info)) + for info in query_info: + if info.shape is None: + continue + impulse = ( + (info.shape.body.position - exp_p).normalized() + * (1 / info.distance) + * 100000 + ) + + info.shape.body.apply_impulse_at_local_point(impulse, (0, 0)) + + if cnt > 0: + for _ in range(15): + cnt -= 1 + if cnt < 1: + continue + body = pymunk.Body(1, 1) + x = random.randint(550, 570) + y = random.randint(30, 100) + body.position = x, y + shape = pymunk.Circle(body, 2.5) + # shape.mass = 1 + shape.data = cnt + shape.elasticity = 0.9 + if color_dict != None and cnt in color_dict: + shape.color = color_dict[cnt] + space.add(body, shape) + elif cnt == -10 and False: + cnt -= 1 + exp_p = pymunk.Vec2d(300, 450) + query_info = space.point_query(exp_p, 50, shape_filter=pymunk.ShapeFilter()) + print(len(query_info)) + for info in query_info: + if info.shape is None: + continue + impulse = ( + (info.shape.body.position - exp_p).normalized() + * (1 / info.distance) + * 25000 + ) + impulse = impulse.x, abs(impulse.y) + info.shape.body.apply_impulse_at_local_point(impulse, (0, 0)) + + ### Update physics + dt = 1.0 / 60.0 + for _ in range(1): + space.step(dt / 1) + + ### Draw stuff + screen.fill(pygame.Color("white")) + + color = pygame.Color("blue") + for shape in space.shapes: + if not isinstance(shape, pymunk.Circle): + continue + if hasattr(shape, "color"): + color = shape.color + + box_surf.fill(color) + screen.blit(box_surf, shape.body.position) + + screen.blit(text, (25, 2)) + + ### Flip screen + pygame.display.flip() + clock.tick(50) + pygame.display.set_caption("fps: " + str(clock.get_fps())) diff --git a/dump/colors_video.py b/dump/colors_video.py new file mode 100644 index 00000000..157f4366 --- /dev/null +++ b/dump/colors_video.py @@ -0,0 +1,279 @@ +""" +Fun example +""" + + +import math +import os +import random +import time + +import pygame + +import pymunk +import pymunk.pygame_util + + +def new_space(): + space = pymunk.Space() + space.gravity = 0, 900 + static_body = space.static_body + walls = [ + # pymunk.Segment(static_body, (-30, -30), (630, -30), 50), # top - + pymunk.Segment(static_body, (-30, 150), (630, 150), 50), # top - + pymunk.Segment(static_body, (-30, -30), (-30, 630), 50), # left | + pymunk.Segment(static_body, (-30, 630), (630, 630), 50), # bottom - + pymunk.Segment(static_body, (630, 630), (630, -30), 50), # right | + ] + y = 1 + # for x in range(0, 600, 100): + # y += 1 + # y %= 2 + # walls.append( + # pymunk.Segment( + # static_body, (x, 600 - y * 50), (x + 100, 600 - (y + 1) % 2 * 50), 1 + # ) + # ) + # walls.append(pymunk.Segment(static_body, (200, 600), (300, 500), 1)) + # walls.append(pymunk.Segment(static_body, (300, 500), (400, 600), 1)) + # walls.append(pymunk.Segment(static_body, (250, 500), (350, 500), 50)) + # walls.append(pymunk.Segment(static_body, (250, 500), (350, 500), 50)) + box_body = pymunk.Body(body_type=pymunk.Body.STATIC) + box_body.position = 300 + 1, 600 - 20 - 16 - 50 + 1 + space.add(box_body) + walls.append(pymunk.Poly.create_box(box_body, (200 - 1, 32 - 1))) + for wall in walls: + wall.elasticity = 1 + wall.friction = 1.0 + + space.add(*walls) + + random.seed(0) + return space + + +def limit_velocity(body, gravity, damping, dt): + max_velocity = 1000 + pymunk.Body.update_velocity(body, gravity, damping, dt) + l = body.velocity.length + if l > max_velocity: + scale = max_velocity / l + body.velocity = body.velocity * scale + + +def spawn(logo_img, space): + cnt = 0 + r = 2 + w = logo_img.get_width() + h = logo_img.get_height() + offset_x = 300 - w / 2 + offset_y = 300 - h / 2 + logo_img.lock() + for x in range(0, w, r * 2): + for y in range(0, h, r * 2): + color = logo_img.get_at([x, y]) + if color.a > 200 and color.r + color.g + color.b < 250 + 250 + 250: + cnt += 1 + body = pymunk.Body() + body.position = x + offset_x + random.random() / 5, y + offset_y + + # body.velocity_func = limit_velocity + + shape = pymunk.Circle(body, r * 0.999) + # shape = pymunk.Poly.create_box(body, (r * 2, r * 2)) + shape.mass = 1 + shape.elasticity = 0.99999 + # shape.friction = 1 + shape.color = color.r, color.g, color.b, 255 + space.add(body, shape) + impulse = (body.position - pymunk.Vec2d(300, 300)) * 1 + impulse = ( + pymunk.Vec2d(random.random() - 0.5, random.random() - 0.5) * 20000 + ) + # body.apply_impulse_at_local_point(impulse, (0, 0)) + print(f"total cnt {cnt}") + return r + + +def draw_ticks(screen: pygame.surface.Surface, font: pygame.font.Font, started=False): + if not started: + t = 0 + else: + try: + t = time.time() - draw_ticks.start_time + except: + draw_ticks.start_time = time.time() + t = 0 + try: + draw_ticks.slowmotion += 1 + draw_ticks.slowmotion %= 6 + except: + draw_ticks.slowmotion = 0 + if dt < 1 / 60 / 4 and draw_ticks.slowmotion < 30 and False: + msg = f"00:{draw_ticks.total_dt:05.2f} (SLOW MOTION)" + else: + msg = f" 00:{t/6:05.2f} " + text = font.render( + msg, + False, + (255, 255, 255, 255), + (75, 75, 75, 255), + # (0, 0, 0, 50), + ) + text = pygame.transform.scale_by(text, 4) + # screen.blit(text, (25, 600 - text.get_height() - 400)) + screen.blit(text, (300 - text.get_width() / 2, 600 - text.get_height() - 20 - 50)) + + +def draw_border(screen): + pygame.draw.lines( + screen, + (75, 75, 75, 255), + False, + # [(0, 0), (0, 600), (600, 600), (600, -50), (0, -50)], + # [(0, 0), (0, 600), (600, 600), (600, 180), (192, 180)], + [(0, 0), (0, 600), (600, 600), (600, 180), (0, 180)], + 40, + ) + + +def get_dt(steps): + steps += 1 + dt = 1.0 / 60.0 / (1 + 200**2 / steps**2) + dt = 1.0 / 60.0 / (1 + 50**2 / steps**2) / 4 + dt = 1 / 60 / 6 + return dt, steps + + +pygame.init() +screen = pygame.display.set_mode((600, 600)) +clock = pygame.time.Clock() +font = pygame.font.SysFont("lucidaconsole", 8) + + +logo_img = pygame.image.load( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "pymunk_logo_sphinx.png") +) + +draw_options = pymunk.pygame_util.DrawOptions(screen) + + +space = new_space() +space2 = new_space() +radius = spawn(logo_img, space) + +radius = spawn(logo_img, space2) +box_surf = pygame.Surface((radius * 2, radius * 2)) + + +positions = [] +total_steps = 750 +steps = 0 +while total_steps > 0: + total_steps -= 1 + dt, steps = get_dt(steps) + space2.step(dt) + step_positions = [] + for shape in space2.shapes: + if not isinstance(shape, pymunk.Circle): + continue + step_positions.append( + ( + shape.color, + shape.body.position - (shape.radius, shape.radius), + shape.body.angle * 360 / math.pi / 2, + ) + ) + positions.append((step_positions, dt)) + +paused = True +while paused: + for event in pygame.event.get(): + if ( + event.type == pygame.QUIT + or event.type == pygame.KEYDOWN + and event.key == pygame.K_ESCAPE + ): + exit() + elif event.type == pygame.KEYDOWN: + paused = False + screen.fill(pygame.Color("white")) + draw_border(screen) + draw_ticks(screen, font, 0) + pygame.display.flip() + clock.tick(60) + pygame.display.set_caption("fps: " + str(clock.get_fps())) + +positions.reverse() +for step_positions, dt in positions: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + exit() + + screen.fill(pygame.Color("white")) + + for color, position, angle in step_positions: + box_surf.fill(color) + # pygame.draw.rect(box_surf, color, (radius, radius, radius * 2, radius * 2)) + # rotated = pygame.transform.rotate(box_surf, angle) + screen.blit(box_surf, position) + # screen.blit(box_surf, position) + draw_border(screen) + draw_ticks(screen, font, dt) + + pygame.display.flip() + clock.tick(60) + pygame.display.set_caption("fps: " + str(clock.get_fps())) + +steps = 0 +while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_p: + pygame.image.save(screen, "colors_video.png") + elif event.type == pygame.KEYDOWN and event.key == pygame.K_x: + exp_p = pymunk.Vec2d(300, 550) + query_info = space.point_query(exp_p, 50, shape_filter=pymunk.ShapeFilter()) + print(len(query_info)) + for info in query_info: + if info.shape is None: + continue + impulse = ( + (info.shape.body.position - exp_p).normalized() + * (1 / info.distance) + * 100000 + ) + + info.shape.body.apply_impulse_at_local_point(impulse, (0, 0)) + + dt, steps = get_dt(steps) + space.step(dt) + + screen.fill(pygame.Color("white")) + + for shape in space.shapes: + if isinstance(shape, pymunk.Poly): + continue + vs = [] + for v in shape.get_vertices(): + x, y = v.rotated(shape.body.angle) + shape.body.position + vs.append((int(x), int(y))) + pygame.draw.polygon(screen, (155, 0, 0, 255), vs) + continue + elif not isinstance(shape, pymunk.Circle): + continue + color = shape.color + + box_surf.fill(color) + screen.blit(box_surf, shape.body.position - (shape.radius, shape.radius)) + draw_border(screen) + draw_ticks(screen, font, dt) + + pygame.display.flip() + clock.tick(60) + pygame.display.set_caption("fps: " + str(clock.get_fps())) diff --git a/dump/colors_video_particles.py b/dump/colors_video_particles.py new file mode 100644 index 00000000..157f4366 --- /dev/null +++ b/dump/colors_video_particles.py @@ -0,0 +1,279 @@ +""" +Fun example +""" + + +import math +import os +import random +import time + +import pygame + +import pymunk +import pymunk.pygame_util + + +def new_space(): + space = pymunk.Space() + space.gravity = 0, 900 + static_body = space.static_body + walls = [ + # pymunk.Segment(static_body, (-30, -30), (630, -30), 50), # top - + pymunk.Segment(static_body, (-30, 150), (630, 150), 50), # top - + pymunk.Segment(static_body, (-30, -30), (-30, 630), 50), # left | + pymunk.Segment(static_body, (-30, 630), (630, 630), 50), # bottom - + pymunk.Segment(static_body, (630, 630), (630, -30), 50), # right | + ] + y = 1 + # for x in range(0, 600, 100): + # y += 1 + # y %= 2 + # walls.append( + # pymunk.Segment( + # static_body, (x, 600 - y * 50), (x + 100, 600 - (y + 1) % 2 * 50), 1 + # ) + # ) + # walls.append(pymunk.Segment(static_body, (200, 600), (300, 500), 1)) + # walls.append(pymunk.Segment(static_body, (300, 500), (400, 600), 1)) + # walls.append(pymunk.Segment(static_body, (250, 500), (350, 500), 50)) + # walls.append(pymunk.Segment(static_body, (250, 500), (350, 500), 50)) + box_body = pymunk.Body(body_type=pymunk.Body.STATIC) + box_body.position = 300 + 1, 600 - 20 - 16 - 50 + 1 + space.add(box_body) + walls.append(pymunk.Poly.create_box(box_body, (200 - 1, 32 - 1))) + for wall in walls: + wall.elasticity = 1 + wall.friction = 1.0 + + space.add(*walls) + + random.seed(0) + return space + + +def limit_velocity(body, gravity, damping, dt): + max_velocity = 1000 + pymunk.Body.update_velocity(body, gravity, damping, dt) + l = body.velocity.length + if l > max_velocity: + scale = max_velocity / l + body.velocity = body.velocity * scale + + +def spawn(logo_img, space): + cnt = 0 + r = 2 + w = logo_img.get_width() + h = logo_img.get_height() + offset_x = 300 - w / 2 + offset_y = 300 - h / 2 + logo_img.lock() + for x in range(0, w, r * 2): + for y in range(0, h, r * 2): + color = logo_img.get_at([x, y]) + if color.a > 200 and color.r + color.g + color.b < 250 + 250 + 250: + cnt += 1 + body = pymunk.Body() + body.position = x + offset_x + random.random() / 5, y + offset_y + + # body.velocity_func = limit_velocity + + shape = pymunk.Circle(body, r * 0.999) + # shape = pymunk.Poly.create_box(body, (r * 2, r * 2)) + shape.mass = 1 + shape.elasticity = 0.99999 + # shape.friction = 1 + shape.color = color.r, color.g, color.b, 255 + space.add(body, shape) + impulse = (body.position - pymunk.Vec2d(300, 300)) * 1 + impulse = ( + pymunk.Vec2d(random.random() - 0.5, random.random() - 0.5) * 20000 + ) + # body.apply_impulse_at_local_point(impulse, (0, 0)) + print(f"total cnt {cnt}") + return r + + +def draw_ticks(screen: pygame.surface.Surface, font: pygame.font.Font, started=False): + if not started: + t = 0 + else: + try: + t = time.time() - draw_ticks.start_time + except: + draw_ticks.start_time = time.time() + t = 0 + try: + draw_ticks.slowmotion += 1 + draw_ticks.slowmotion %= 6 + except: + draw_ticks.slowmotion = 0 + if dt < 1 / 60 / 4 and draw_ticks.slowmotion < 30 and False: + msg = f"00:{draw_ticks.total_dt:05.2f} (SLOW MOTION)" + else: + msg = f" 00:{t/6:05.2f} " + text = font.render( + msg, + False, + (255, 255, 255, 255), + (75, 75, 75, 255), + # (0, 0, 0, 50), + ) + text = pygame.transform.scale_by(text, 4) + # screen.blit(text, (25, 600 - text.get_height() - 400)) + screen.blit(text, (300 - text.get_width() / 2, 600 - text.get_height() - 20 - 50)) + + +def draw_border(screen): + pygame.draw.lines( + screen, + (75, 75, 75, 255), + False, + # [(0, 0), (0, 600), (600, 600), (600, -50), (0, -50)], + # [(0, 0), (0, 600), (600, 600), (600, 180), (192, 180)], + [(0, 0), (0, 600), (600, 600), (600, 180), (0, 180)], + 40, + ) + + +def get_dt(steps): + steps += 1 + dt = 1.0 / 60.0 / (1 + 200**2 / steps**2) + dt = 1.0 / 60.0 / (1 + 50**2 / steps**2) / 4 + dt = 1 / 60 / 6 + return dt, steps + + +pygame.init() +screen = pygame.display.set_mode((600, 600)) +clock = pygame.time.Clock() +font = pygame.font.SysFont("lucidaconsole", 8) + + +logo_img = pygame.image.load( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "pymunk_logo_sphinx.png") +) + +draw_options = pymunk.pygame_util.DrawOptions(screen) + + +space = new_space() +space2 = new_space() +radius = spawn(logo_img, space) + +radius = spawn(logo_img, space2) +box_surf = pygame.Surface((radius * 2, radius * 2)) + + +positions = [] +total_steps = 750 +steps = 0 +while total_steps > 0: + total_steps -= 1 + dt, steps = get_dt(steps) + space2.step(dt) + step_positions = [] + for shape in space2.shapes: + if not isinstance(shape, pymunk.Circle): + continue + step_positions.append( + ( + shape.color, + shape.body.position - (shape.radius, shape.radius), + shape.body.angle * 360 / math.pi / 2, + ) + ) + positions.append((step_positions, dt)) + +paused = True +while paused: + for event in pygame.event.get(): + if ( + event.type == pygame.QUIT + or event.type == pygame.KEYDOWN + and event.key == pygame.K_ESCAPE + ): + exit() + elif event.type == pygame.KEYDOWN: + paused = False + screen.fill(pygame.Color("white")) + draw_border(screen) + draw_ticks(screen, font, 0) + pygame.display.flip() + clock.tick(60) + pygame.display.set_caption("fps: " + str(clock.get_fps())) + +positions.reverse() +for step_positions, dt in positions: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + exit() + + screen.fill(pygame.Color("white")) + + for color, position, angle in step_positions: + box_surf.fill(color) + # pygame.draw.rect(box_surf, color, (radius, radius, radius * 2, radius * 2)) + # rotated = pygame.transform.rotate(box_surf, angle) + screen.blit(box_surf, position) + # screen.blit(box_surf, position) + draw_border(screen) + draw_ticks(screen, font, dt) + + pygame.display.flip() + clock.tick(60) + pygame.display.set_caption("fps: " + str(clock.get_fps())) + +steps = 0 +while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_p: + pygame.image.save(screen, "colors_video.png") + elif event.type == pygame.KEYDOWN and event.key == pygame.K_x: + exp_p = pymunk.Vec2d(300, 550) + query_info = space.point_query(exp_p, 50, shape_filter=pymunk.ShapeFilter()) + print(len(query_info)) + for info in query_info: + if info.shape is None: + continue + impulse = ( + (info.shape.body.position - exp_p).normalized() + * (1 / info.distance) + * 100000 + ) + + info.shape.body.apply_impulse_at_local_point(impulse, (0, 0)) + + dt, steps = get_dt(steps) + space.step(dt) + + screen.fill(pygame.Color("white")) + + for shape in space.shapes: + if isinstance(shape, pymunk.Poly): + continue + vs = [] + for v in shape.get_vertices(): + x, y = v.rotated(shape.body.angle) + shape.body.position + vs.append((int(x), int(y))) + pygame.draw.polygon(screen, (155, 0, 0, 255), vs) + continue + elif not isinstance(shape, pymunk.Circle): + continue + color = shape.color + + box_surf.fill(color) + screen.blit(box_surf, shape.body.position - (shape.radius, shape.radius)) + draw_border(screen) + draw_ticks(screen, font, dt) + + pygame.display.flip() + clock.tick(60) + pygame.display.set_caption("fps: " + str(clock.get_fps())) diff --git a/dump/so.py b/dump/so.py index 36bf945e..68e5ca93 100644 --- a/dump/so.py +++ b/dump/so.py @@ -1,153 +1,78 @@ -"""This example lets you dynamically create static walls and dynamic balls - -""" -__docformat__ = "reStructuredText" +import random import pygame import pymunk import pymunk.pygame_util -pm = pymunk - - -def main(): - pygame.init() - screen = pygame.display.set_mode((600, 600)) - clock = pygame.time.Clock() - running = True - draw_options = pymunk.pygame_util.DrawOptions(screen) - draw_options.flags = pymunk.SpaceDebugDrawOptions.DRAW_SHAPES - draw_options.flags |= pymunk.SpaceDebugDrawOptions.DRAW_COLLISION_POINTS - - ### Physics stuff +def new_space(color_dict): space = pymunk.Space() - + space.gravity = 0, 900 static_body = space.static_body walls = [ - pymunk.Segment(static_body, (0, 0), (0, 150), 0.0), - pymunk.Segment(static_body, (0, 150), (150, 150), 0.0), - pymunk.Segment(static_body, (150, 150), (150, 50), 0.0), - pymunk.Segment(static_body, (150, 50), (100, 50), 0.0), - pymunk.Segment(static_body, (100, 50), (100, 0), 0.0), - pymunk.Segment(static_body, (100, 0), (0, 0), 0.0), + pymunk.Segment(static_body, (0, 0), (0, 600), 10), + pymunk.Segment(static_body, (0, 600), (600, 600), 10), + pymunk.Segment(static_body, (600, 600), (600, 0), 10), + pymunk.Segment(static_body, (600, 0), (0, 0), 10), + pymunk.Segment(static_body, (200, 300), (600, 150), 3), ] - for wall in walls: - wall.collision_type = 3 + space.add(*walls) - x_offset = 5 - y_offset = 5 - sensor_depth = 50 - # body = pymunk.Body(body_type=pymunk.Body.KINEMATIC) - body = pymunk.Body(1, 2) - sensor_shape = pymunk.Poly( - body, - [ - (-5 + x_offset, 0 + y_offset), - (-20 + x_offset, sensor_depth + y_offset), - (20 + x_offset, sensor_depth + y_offset), - (5 + x_offset, 0 + y_offset), - ], - ) - sensor_shape.sensor = False - sensor_shape.collision_type = 1 - - obj = {} - - def sensor_pre_solve(arbiter: pymunk.Arbiter, space, data): - if sensor_shape in arbiter.shapes: - obj["last_set"] = arbiter.contact_point_set - # for point in arbiter.contact_point_set.points: - # obj["last_contact_points"].append((point.point_a, pygame.Color('red')) - # obj["last_contact_points"].append((point.point_b, pygame.Color('green')) - return True - - sensor_collision_handler = space.add_collision_handler(1, 3) - sensor_collision_handler.pre_solve = sensor_pre_solve - space.add(body, sensor_shape) - - while running: - for event in pygame.event.get(): - if event.type == pygame.QUIT: - running = False - elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: - running = False - mouse_pos = pygame.mouse.get_pos() - - body.position = mouse_pos - body.position -= pymunk.Vec2d(10, 50) - body.angle = 1 - body.velocity = 0, 0 - body.angular_velocity = 0 - - ### Update physics - obj["last_set"] = None - dt = 1.0 / 60.0 - - space.step(dt) - - # point set - - # cpBool swapped = arb->swapped; - # cpVect n = arb->n; - # set.normal = (swapped ? cpvneg(n) : n); - - # for(int i=0; ibody_a->p, arb->contacts[i].r1); - # cpVect p2 = cpvadd(arb->body_b->p, arb->contacts[i].r2); - - # set.points[i].pointA = (swapped ? p2 : p1); - # set.points[i].pointB = (swapped ? p1 : p2); - # set.points[i].distance = cpvdot(cpvsub(p2, p1), n); - # } - - # debug collision draw - # cpSpaceDebugDrawSegmentImpl draw_seg = options->drawSegment; - # cpDataPointer data = options->data; - - # for (int i = 0; i < arbiters->num; i++) - # { - # cpArbiter *arb = (cpArbiter *)arbiters->arr[i]; - # cpVect n = arb->n; - - # for (int j = 0; j < arb->count; j++) - # { - # cpVect p1 = cpvadd(arb->body_a->p, arb->contacts[j].r1); - # cpVect p2 = cpvadd(arb->body_b->p, arb->contacts[j].r2); - - # cpFloat d = 2.0f; - # cpVect a = cpvadd(p1, cpvmult(n, -d)); - # cpVect b = cpvadd(p2, cpvmult(n, d)); - - # a = cpTransformPoint(options->transform, a); - # b = cpTransformPoint(options->transform, b); - # draw_seg(a, b, color, data); - # } - # } - - ### Draw stuff - screen.fill(pygame.Color("white")) - space.debug_draw(draw_options) - if obj["last_set"] is not None: - for point in obj["last_set"].points: - - n = obj["last_set"].normal - # print(n, point.distance.get_distance()) - a = point.point_a + point.distance * n - b = point.point_b # - point.distance * n - # print(point.distance, point.point_a.get_distance(point.point_b)) - # pygame.draw.circle(screen, pygame.Color("red"), a, 5) # sensor - pygame.draw.circle(screen, pygame.Color("blue"), b, 3) - - ### Flip screen - pygame.display.flip() - clock.tick(50) - pygame.display.set_caption("fps: " + str(clock.get_fps())) - - -if __name__ == "__main__": - - main() + random.seed(0) + for i in range(500): + body = pymunk.Body() + x = random.randint(270, 450) + y = random.randint(30, 100) + body.position = x, y + shape = pymunk.Circle(body, 7) + shape.mass = 1 + shape.data = i + if color_dict != None: + shape.color = color_dict[i] + space.add(body, shape) + + return space + + +pygame.init() +screen = pygame.display.set_mode((600, 600)) +clock = pygame.time.Clock() + +draw_options = pymunk.pygame_util.DrawOptions(screen) + +color_dict = None +space = pymunk.Space() + +while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: + exit() + elif event.type == pygame.KEYDOWN and event.key == pygame.K_r: + space = new_space(color_dict) + elif event.type == pygame.KEYDOWN and event.key == pygame.K_c: + color_dict = {} + for shape in space.shapes: + if not isinstance(shape, pymunk.Circle): + continue + r = shape.body.position.x / 600 * 255 + g = max((shape.body.position.y - 400) / 200 * 255, 0) + shape.color = (r, g, 150, 255) + color_dict[shape.data] = shape.color + + ### Update physics + dt = 1.0 / 60.0 + + space.step(dt) + + ### Draw stuff + screen.fill(pygame.Color("white")) + space.debug_draw(draw_options) + + ### Flip screen + pygame.display.flip() + clock.tick(50) + pygame.display.set_caption("fps: " + str(clock.get_fps())) diff --git a/pymunk/body.py b/pymunk/body.py index e5ef61fd..3854fcb0 100644 --- a/pymunk/body.py +++ b/pymunk/body.py @@ -528,11 +528,16 @@ def apply_force_at_world_point( ) -> None: """Add the force force to body as if applied from the world point. - People are sometimes confused by the difference between a force and - an impulse. An impulse is a very large force applied over a very + An impulse is a very large force applied over a very short period of time. Some examples are a ball hitting a wall or cannon firing. Chipmunk treats impulses as if they occur instantaneously by adding directly to the velocity of an object. + + Applying the force on the other hand is for things which have effect + over time, such as gravity or air resistance. The force is proprtional + to the time step (meaning it is multiplied with the dt argument to the + step method). + Both impulses and forces are affected the mass of an object. Doubling the mass of the object will halve the effect. """ diff --git a/pymunk/examples/arrows.py b/pymunk/examples/arrows.py index bb116c28..496943e4 100644 --- a/pymunk/examples/arrows.py +++ b/pymunk/examples/arrows.py @@ -1,7 +1,7 @@ """Showcase of flying arrows that can stick to objects in a somewhat realistic looking way. """ -import sys +import math from typing import List import pygame @@ -146,7 +146,10 @@ def main(): mouse_position = pymunk.pygame_util.from_pygame( Vec2d(*pygame.mouse.get_pos()), screen ) - cannon_body.angle = (mouse_position - cannon_body.position).angle + cannon_body.angle = max( + -math.pi / 2, + min(0, (mouse_position - cannon_body.position).angle), + ) # move the unfired arrow together with the cannon arrow_body.position = cannon_body.position + Vec2d( cannon_shape.radius + 40, 0 @@ -167,7 +170,7 @@ def main(): # around even when fired straight up. Might not be as accurate, but # maybe look better. drag_force_magnitude = ( - (1 - abs(dot)) * flight_speed ** 2 * drag_constant * flying_arrow.mass + (1 - abs(dot)) * flight_speed**2 * drag_constant * flying_arrow.mass ) arrow_tail_position = flying_arrow.position + Vec2d(-50, 0).rotated( flying_arrow.angle @@ -222,4 +225,4 @@ def main(): if __name__ == "__main__": - sys.exit(main()) + main() diff --git a/pymunk/examples/constraints.py b/pymunk/examples/constraints.py index 3eeb2f62..1032cf02 100644 --- a/pymunk/examples/constraints.py +++ b/pymunk/examples/constraints.py @@ -210,7 +210,7 @@ def main(): (0, 0), shape.body.world_to_local(nearest), ) - mouse_joint.max_force = 50000 + mouse_joint.max_force = 7500 mouse_joint.error_bias = (1 - 0.15) ** 60 space.add(mouse_joint) @@ -249,4 +249,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main()