245 lines
8.2 KiB
Python
245 lines
8.2 KiB
Python
|
# ----------------------------------------------------------------------------
|
||
|
# pymunk
|
||
|
# Copyright (c) 2007-2016 Victor Blomqvist
|
||
|
#
|
||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
# of this software and associated documentation files (the "Software"), to deal
|
||
|
# in the Software without restriction, including without limitation the rights
|
||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
# copies of the Software, and to permit persons to whom the Software is
|
||
|
# furnished to do so, subject to the following conditions:
|
||
|
#
|
||
|
# The above copyright notice and this permission notice shall be included in
|
||
|
# all copies or substantial portions of the Software.
|
||
|
#
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
# SOFTWARE.
|
||
|
# ----------------------------------------------------------------------------
|
||
|
|
||
|
"""This submodule contains helper functions to help with quick prototyping
|
||
|
using pymunk together with pygame.
|
||
|
|
||
|
Intended to help with debugging and prototyping, not for actual production use
|
||
|
in a full application. The methods contained in this module is opinionated
|
||
|
about your coordinate system and not in any way optimized.
|
||
|
"""
|
||
|
|
||
|
__docformat__ = "reStructuredText"
|
||
|
|
||
|
__all__ = [
|
||
|
"DrawOptions",
|
||
|
"get_mouse_pos",
|
||
|
"to_pygame",
|
||
|
"from_pygame",
|
||
|
# "lighten",
|
||
|
"positive_y_is_up",
|
||
|
]
|
||
|
|
||
|
from typing import Sequence, Tuple
|
||
|
|
||
|
import numpy as np
|
||
|
import pygame
|
||
|
import pymunk
|
||
|
from pymunk.space_debug_draw_options import SpaceDebugColor
|
||
|
from pymunk.vec2d import Vec2d
|
||
|
|
||
|
positive_y_is_up: bool = False
|
||
|
"""Make increasing values of y point upwards.
|
||
|
|
||
|
When True::
|
||
|
|
||
|
y
|
||
|
^
|
||
|
| . (3, 3)
|
||
|
|
|
||
|
| . (2, 2)
|
||
|
|
|
||
|
+------ > x
|
||
|
|
||
|
When False::
|
||
|
|
||
|
+------ > x
|
||
|
|
|
||
|
| . (2, 2)
|
||
|
|
|
||
|
| . (3, 3)
|
||
|
v
|
||
|
y
|
||
|
|
||
|
"""
|
||
|
|
||
|
|
||
|
class DrawOptions(pymunk.SpaceDebugDrawOptions):
|
||
|
def __init__(self, surface: pygame.Surface) -> None:
|
||
|
"""Draw a pymunk.Space on a pygame.Surface object.
|
||
|
|
||
|
Typical usage::
|
||
|
|
||
|
>>> import pymunk
|
||
|
>>> surface = pygame.Surface((10,10))
|
||
|
>>> space = pymunk.Space()
|
||
|
>>> options = pymunk.pygame_util.DrawOptions(surface)
|
||
|
>>> space.debug_draw(options)
|
||
|
|
||
|
You can control the color of a shape by setting shape.color to the color
|
||
|
you want it drawn in::
|
||
|
|
||
|
>>> c = pymunk.Circle(None, 10)
|
||
|
>>> c.color = pygame.Color("pink")
|
||
|
|
||
|
See pygame_util.demo.py for a full example
|
||
|
|
||
|
Since pygame uses a coordinate system where y points down (in contrast
|
||
|
to many other cases), you either have to make the physics simulation
|
||
|
with Pymunk also behave in that way, or flip everything when you draw.
|
||
|
|
||
|
The easiest is probably to just make the simulation behave the same
|
||
|
way as Pygame does. In that way all coordinates used are in the same
|
||
|
orientation and easy to reason about::
|
||
|
|
||
|
>>> space = pymunk.Space()
|
||
|
>>> space.gravity = (0, -1000)
|
||
|
>>> body = pymunk.Body()
|
||
|
>>> body.position = (0, 0) # will be positioned in the top left corner
|
||
|
>>> space.debug_draw(options)
|
||
|
|
||
|
To flip the drawing its possible to set the module property
|
||
|
:py:data:`positive_y_is_up` to True. Then the pygame drawing will flip
|
||
|
the simulation upside down before drawing::
|
||
|
|
||
|
>>> positive_y_is_up = True
|
||
|
>>> body = pymunk.Body()
|
||
|
>>> body.position = (0, 0)
|
||
|
>>> # Body will be position in bottom left corner
|
||
|
|
||
|
:Parameters:
|
||
|
surface : pygame.Surface
|
||
|
Surface that the objects will be drawn on
|
||
|
"""
|
||
|
self.surface = surface
|
||
|
super().__init__()
|
||
|
|
||
|
def draw_circle(
|
||
|
self,
|
||
|
pos: Vec2d,
|
||
|
angle: float,
|
||
|
radius: float,
|
||
|
outline_color: SpaceDebugColor,
|
||
|
fill_color: SpaceDebugColor,
|
||
|
) -> None:
|
||
|
p = to_pygame(pos, self.surface)
|
||
|
|
||
|
pygame.draw.circle(self.surface, fill_color.as_int(), p, round(radius), 0)
|
||
|
pygame.draw.circle(self.surface, light_color(fill_color).as_int(), p, round(radius - 4), 0)
|
||
|
|
||
|
# circle_edge = pos + Vec2d(radius, 0).rotated(angle)
|
||
|
# p2 = to_pygame(circle_edge, self.surface)
|
||
|
# line_r = 2 if radius > 20 else 1
|
||
|
# pygame.draw.lines(self.surface, outline_color.as_int(), False, [p, p2], line_r)
|
||
|
|
||
|
def draw_segment(self, a: Vec2d, b: Vec2d, color: SpaceDebugColor) -> None:
|
||
|
p1 = to_pygame(a, self.surface)
|
||
|
p2 = to_pygame(b, self.surface)
|
||
|
|
||
|
pygame.draw.aalines(self.surface, color.as_int(), False, [p1, p2])
|
||
|
|
||
|
def draw_fat_segment(
|
||
|
self,
|
||
|
a: Tuple[float, float],
|
||
|
b: Tuple[float, float],
|
||
|
radius: float,
|
||
|
outline_color: SpaceDebugColor,
|
||
|
fill_color: SpaceDebugColor,
|
||
|
) -> None:
|
||
|
p1 = to_pygame(a, self.surface)
|
||
|
p2 = to_pygame(b, self.surface)
|
||
|
|
||
|
r = round(max(1, radius * 2))
|
||
|
pygame.draw.lines(self.surface, fill_color.as_int(), False, [p1, p2], r)
|
||
|
if r > 2:
|
||
|
orthog = [abs(p2[1] - p1[1]), abs(p2[0] - p1[0])]
|
||
|
if orthog[0] == 0 and orthog[1] == 0:
|
||
|
return
|
||
|
scale = radius / (orthog[0] * orthog[0] + orthog[1] * orthog[1]) ** 0.5
|
||
|
orthog[0] = round(orthog[0] * scale)
|
||
|
orthog[1] = round(orthog[1] * scale)
|
||
|
points = [
|
||
|
(p1[0] - orthog[0], p1[1] - orthog[1]),
|
||
|
(p1[0] + orthog[0], p1[1] + orthog[1]),
|
||
|
(p2[0] + orthog[0], p2[1] + orthog[1]),
|
||
|
(p2[0] - orthog[0], p2[1] - orthog[1]),
|
||
|
]
|
||
|
pygame.draw.polygon(self.surface, fill_color.as_int(), points)
|
||
|
pygame.draw.circle(
|
||
|
self.surface,
|
||
|
fill_color.as_int(),
|
||
|
(round(p1[0]), round(p1[1])),
|
||
|
round(radius),
|
||
|
)
|
||
|
pygame.draw.circle(
|
||
|
self.surface,
|
||
|
fill_color.as_int(),
|
||
|
(round(p2[0]), round(p2[1])),
|
||
|
round(radius),
|
||
|
)
|
||
|
|
||
|
def draw_polygon(
|
||
|
self,
|
||
|
verts: Sequence[Tuple[float, float]],
|
||
|
radius: float,
|
||
|
outline_color: SpaceDebugColor,
|
||
|
fill_color: SpaceDebugColor,
|
||
|
) -> None:
|
||
|
ps = [to_pygame(v, self.surface) for v in verts]
|
||
|
ps += [ps[0]]
|
||
|
|
||
|
radius = 2
|
||
|
pygame.draw.polygon(self.surface, light_color(fill_color).as_int(), ps)
|
||
|
|
||
|
if radius > 0:
|
||
|
for i in range(len(verts)):
|
||
|
a = verts[i]
|
||
|
b = verts[(i + 1) % len(verts)]
|
||
|
self.draw_fat_segment(a, b, radius, fill_color, fill_color)
|
||
|
|
||
|
def draw_dot(self, size: float, pos: Tuple[float, float], color: SpaceDebugColor) -> None:
|
||
|
p = to_pygame(pos, self.surface)
|
||
|
pygame.draw.circle(self.surface, color.as_int(), p, round(size), 0)
|
||
|
|
||
|
|
||
|
def get_mouse_pos(surface: pygame.Surface) -> Tuple[int, int]:
|
||
|
"""Get position of the mouse pointer in pymunk coordinates."""
|
||
|
p = pygame.mouse.get_pos()
|
||
|
return from_pygame(p, surface)
|
||
|
|
||
|
|
||
|
def to_pygame(p: Tuple[float, float], surface: pygame.Surface) -> Tuple[int, int]:
|
||
|
"""Convenience method to convert pymunk coordinates to pygame surface
|
||
|
local coordinates.
|
||
|
|
||
|
Note that in case positive_y_is_up is False, this function won't actually do
|
||
|
anything except converting the point to integers.
|
||
|
"""
|
||
|
if positive_y_is_up:
|
||
|
return round(p[0]), surface.get_height() - round(p[1])
|
||
|
else:
|
||
|
return round(p[0]), round(p[1])
|
||
|
|
||
|
|
||
|
def from_pygame(p: Tuple[float, float], surface: pygame.Surface) -> Tuple[int, int]:
|
||
|
"""Convenience method to convert pygame surface local coordinates to
|
||
|
pymunk coordinates
|
||
|
"""
|
||
|
return to_pygame(p, surface)
|
||
|
|
||
|
|
||
|
def light_color(color: SpaceDebugColor):
|
||
|
color = np.minimum(1.2 * np.float32([color.r, color.g, color.b, color.a]), np.float32([255]))
|
||
|
color = SpaceDebugColor(r=color[0], g=color[1], b=color[2], a=color[3])
|
||
|
return color
|