492 lines
17 KiB
Python
492 lines
17 KiB
Python
import itertools, random, os, glob
|
|
from math import sin, cos, atan2, radians, sqrt, pi
|
|
|
|
import pygame
|
|
from pygame import Surface, PixelArray, Color, Rect, draw, gfxdraw
|
|
from pygame.mixer import get_num_channels, Channel
|
|
from pygame.locals import *
|
|
|
|
from .Vector import Vector
|
|
|
|
def clamp(n, min_n, max_n):
|
|
if n < min_n:
|
|
return min_n
|
|
elif n > max_n:
|
|
return max_n
|
|
else:
|
|
return n
|
|
|
|
def get_step(start, end, speed):
|
|
x0, y0 = start
|
|
x1, y1 = end
|
|
angle = atan2(x1 - x0, y1 - y0)
|
|
return Vector(speed * sin(angle), speed * cos(angle))
|
|
|
|
def get_step_relative(start, end, step):
|
|
return get_step(start, end, get_distance(start, end) * step)
|
|
|
|
def get_segments(start, end, count):
|
|
rel_step = get_step_relative(start, end, 1 / float(count))
|
|
segs = [[Vector(start[0], start[1])]]
|
|
for ii in range(count):
|
|
seg_end = Vector(segs[-1][0].x + rel_step.x, segs[-1][0].y + rel_step.y)
|
|
segs[-1].append(seg_end)
|
|
if ii < count - 1:
|
|
segs.append([seg_end])
|
|
return segs
|
|
|
|
def get_points_on_line(start, end, count):
|
|
rel_step = get_step_relative(start, end, 1 / float(count - 1))
|
|
points = [Vector(start[0], start[1])]
|
|
for ii in range(count - 2):
|
|
points.append(Vector(points[-1][0] + rel_step[0], points[-1][1] + rel_step[1]))
|
|
points.append(Vector(end[0], end[1]))
|
|
return points
|
|
|
|
def get_angle(start, end, transpose=False):
|
|
"""counterclockwise, 0 is down"""
|
|
angle = atan2(end[0] - start[0], end[1] - start[1])
|
|
if transpose:
|
|
angle = -angle - pi
|
|
return angle
|
|
|
|
def get_endpoint(start, angle, magnitude, translate_angle=True):
|
|
"""clockwise, 0 is up"""
|
|
x0, y0 = start
|
|
dx, dy = get_delta(angle, magnitude, translate_angle)
|
|
return Vector(x0 + dx, y0 + dy)
|
|
|
|
def get_delta(angle, magnitude, translate_angle=True):
|
|
if translate_angle:
|
|
angle = radians(angle)
|
|
return Vector(sin(angle) * magnitude, -cos(angle) * magnitude)
|
|
|
|
def reflect_angle(angle, wall):
|
|
return wall - angle
|
|
|
|
def rotate_2d(point, center, angle, translate_angle=True):
|
|
if translate_angle:
|
|
angle = radians(angle)
|
|
x, y = point
|
|
cx, cy = center
|
|
return cos(angle) * (x - cx) - sin(angle) * (y - cy) + cx, \
|
|
sin(angle) * (x - cx) + cos(angle) * (y - cy) + cy
|
|
|
|
def get_points_on_circle(center, radius, count, offset=0):
|
|
angle_step = 360.0 / count
|
|
points = []
|
|
current_angle = 0
|
|
for _ in range(count):
|
|
points.append(get_point_on_circle(center, radius,
|
|
current_angle + offset))
|
|
current_angle += angle_step
|
|
return points
|
|
|
|
def get_point_on_circle(center, radius, angle, translate_angle=True):
|
|
if translate_angle:
|
|
angle = radians(angle)
|
|
return Vector(center[0] + sin(angle) * radius,
|
|
center[1] - cos(angle) * radius)
|
|
|
|
def get_range_steps(start, end, count):
|
|
for ii in range(count):
|
|
yield start + (end - start) * ii / float(count - 1)
|
|
|
|
def get_percent_way(iterable):
|
|
for ii in range(len(iterable)):
|
|
yield iterable[ii], float(ii) / (len(iterable) - 1)
|
|
|
|
def mirrored(iterable, full=False, tail=True):
|
|
for ii, item in enumerate(itertools.chain(iterable, reversed(iterable))):
|
|
if not full and ii == len(iterable):
|
|
continue
|
|
elif not tail and ii == len(iterable) * 2 - 1:
|
|
continue
|
|
else:
|
|
yield item
|
|
|
|
def get_distance(p0, p1):
|
|
return sqrt((p0[0] - p1[0]) ** 2 + (p0[1] - p1[1]) ** 2)
|
|
|
|
def place_in_rect(rect, incoming, contain=True, *args):
|
|
while True:
|
|
incoming.center = random.randint(0, rect.w), random.randint(0, rect.h)
|
|
if not contain or rect.contains(incoming):
|
|
collides = False
|
|
for inner in args:
|
|
if inner.colliderect(incoming):
|
|
collides = True
|
|
break
|
|
if not collides:
|
|
break
|
|
|
|
def constrain_dimensions_2d(vector, container):
|
|
dw = vector[0] - container[0]
|
|
dh = vector[1] - container[1]
|
|
if dw > 0 or dh > 0:
|
|
if dw > dh:
|
|
size = Vector(container[0], int(round(container[0] / vector[0] * vector[1])))
|
|
else:
|
|
size = Vector(int(round(container[1] / vector[1] * vector[0])), container[1])
|
|
else:
|
|
size = Vector(vector[0], vector[1])
|
|
return size
|
|
|
|
# from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c
|
|
def get_intersection(p0, p1, p2, p3):
|
|
x0, y0 = p0
|
|
x1, y1 = p1
|
|
x2, y2 = p2
|
|
x3, y3 = p3
|
|
a0 = y1 - y0
|
|
b0 = x0 - x1
|
|
c0 = x1 * y0 - x0 * y1
|
|
r2 = a0 * x2 + b0 * y2 + c0
|
|
r3 = a0 * x3 + b0 * y3 + c0
|
|
if r2 != 0 and r3 != 0 and r2 * r3 > 0:
|
|
return None
|
|
a1 = y3 - y2
|
|
b1 = x2 - x3
|
|
c1 = x3 * y2 - x2 * y3
|
|
r0 = a1 * x0 + b1 * y0 + c1
|
|
r1 = a1 * x1 + b1 * y1 + c1
|
|
if r0 != 0 and r1 != 0 and r0 * r1 > 0:
|
|
return None
|
|
denominator = a0 * b1 - a1 * b0
|
|
if denominator == 0:
|
|
return (x0 + x1 + x2 + x3) / 4, (y0 + y1 + y2 + y3) / 4
|
|
if denominator < 0:
|
|
offset = -denominator / 2
|
|
else:
|
|
offset = denominator / 2
|
|
numerator = b0 * c1 - b1 * c0
|
|
x = ((-1, 1)[numerator < 0] * offset + numerator) / denominator
|
|
numerator = a1 * c0 - a0 * c1
|
|
y = ((-1, 1)[numerator < 0] * offset + numerator) / denominator
|
|
return x, y
|
|
|
|
def collide_line_with_rect(rect, p0, p1):
|
|
for line in ((rect.topleft, rect.topright),
|
|
(rect.topright, rect.bottomright),
|
|
(rect.bottomright, rect.bottomleft),
|
|
(rect.bottomleft, rect.topleft)):
|
|
if get_intersection(p0, p1, *line):
|
|
return True
|
|
|
|
def get_random_number_in_range(start, end):
|
|
return random.random() * (end - start) + start
|
|
|
|
def get_value_in_range(start, end, position, reverse=False):
|
|
if reverse:
|
|
position = 1 - position
|
|
return (end - start) * position + start
|
|
|
|
def get_boxed_surface(surface, background=None, border=None, border_width=1,
|
|
padding=0):
|
|
if padding:
|
|
if isinstance(padding, int):
|
|
padding = [padding] * 2
|
|
padding = [x * 2 for x in padding]
|
|
rect = surface.get_rect()
|
|
padded_surface = Surface(rect.inflate(padding).size, SRCALPHA)
|
|
if background is not None:
|
|
padded_surface.fill(background)
|
|
rect.center = padded_surface.get_rect().center
|
|
padded_surface.blit(surface, rect)
|
|
surface = padded_surface
|
|
if border is not None:
|
|
if isinstance(border_width, int):
|
|
border_width = [border_width] * 2
|
|
border_width = [x * 2 for x in border_width]
|
|
rect = surface.get_rect()
|
|
bordered_surface = Surface(rect.inflate(border_width).size, SRCALPHA)
|
|
bordered_surface.fill(border)
|
|
rect.center = bordered_surface.get_rect().center
|
|
bordered_surface.fill((255, 255, 255, 255), rect)
|
|
bordered_surface.blit(surface, rect, None, BLEND_RGBA_MIN)
|
|
surface = bordered_surface
|
|
return surface
|
|
|
|
def render_box(font=None, text=None, antialias=True, color=(0, 0, 0), background=None, border=None,
|
|
border_width=1, padding=0, width=None, height=None):
|
|
if font is not None:
|
|
surface = font.render(text, antialias, color, background)
|
|
if width is not None or height is not None:
|
|
if width is None:
|
|
width = surface.get_width()
|
|
if height is None:
|
|
height = surface.get_height()
|
|
container = Surface((width, height), SRCALPHA)
|
|
if background is not None:
|
|
container.fill(background)
|
|
text_rect = surface.get_rect()
|
|
text_rect.center = container.get_rect().center
|
|
container.blit(surface, text_rect)
|
|
surface = container
|
|
else:
|
|
surface = pygame.Surface((width, height), SRCALPHA)
|
|
surface.fill(background)
|
|
return get_boxed_surface(surface, background, border, border_width, padding)
|
|
|
|
def get_wrapped_text_surface(font, text, width, antialias=True, color=(0, 0, 0),
|
|
background=None, border=None, border_width=1,
|
|
padding=0, align="left"):
|
|
lines = []
|
|
height = 0
|
|
for chunk in text.split("\n"):
|
|
line_text = ""
|
|
ii = 0
|
|
finished = False
|
|
if chunk.startswith("\*") and chunk.endswith("\*"):
|
|
chunk = chunk.replace("\*", "*")
|
|
elif chunk.startswith("*") and chunk.endswith("*"):
|
|
chunk = chunk[1:-1]
|
|
font.set_italic(True)
|
|
words = chunk.split(" ")
|
|
while not finished:
|
|
line_width = font.size(line_text + " " + words[ii])[0]
|
|
if line_width > width or ii == len(words) - 1:
|
|
if ii == len(words) - 1 and line_width <= width:
|
|
if line_text != "":
|
|
line_text += " "
|
|
line_text += words[ii]
|
|
finished = True
|
|
line = font.render(line_text, antialias, color)
|
|
height += line.get_height()
|
|
lines.append(line)
|
|
line_text = ""
|
|
else:
|
|
line_text += " " + words[ii]
|
|
ii += 1
|
|
font.set_italic(False)
|
|
top = 0
|
|
surface = Surface((width, height), pygame.SRCALPHA)
|
|
if background:
|
|
surface.fill(background)
|
|
rect = surface.get_rect()
|
|
for line in lines:
|
|
line_rect = line.get_rect()
|
|
line_rect.top = top
|
|
if align == "center":
|
|
line_rect.centerx = rect.centerx
|
|
surface.blit(line, line_rect)
|
|
top += line_rect.h
|
|
return get_boxed_surface(surface, background, border, border_width, padding)
|
|
|
|
def replace_color(surface, color, replacement):
|
|
pixels = PixelArray(surface)
|
|
pixels.replace(color, replacement)
|
|
del pixels
|
|
|
|
def get_color_swapped_surface(surface, color, replacement):
|
|
swapped = surface.copy()
|
|
replace_color(swapped, color, replacement)
|
|
return swapped
|
|
|
|
def get_busy_channel_count():
|
|
count = 0
|
|
for index in range(get_num_channels()):
|
|
count += Channel(index).get_busy()
|
|
return count
|
|
|
|
def get_hue_shifted_surface(base, offset):
|
|
surface = base.copy()
|
|
pixels = PixelArray(surface)
|
|
color = Color(0, 0, 0)
|
|
for x in range(surface.get_width()):
|
|
for y in range(surface.get_height()):
|
|
rgb = surface.unmap_rgb(pixels[x][y])
|
|
h, s, l, a = Color(*rgb).hsla
|
|
if a and surface.get_colorkey() != rgb:
|
|
color.hsla = (int(h) + offset) % 360, int(s), int(l), int(a)
|
|
pixels[x][y] = color
|
|
del pixels
|
|
return surface
|
|
|
|
def get_inverted_color(color):
|
|
return Color(255 - color[0], 255 - color[1], 255 - color[2])
|
|
|
|
def get_inverted_surface(base):
|
|
surface = base.copy()
|
|
pixels = PixelArray(surface)
|
|
for x in range(surface.get_width()):
|
|
for y in range(surface.get_height()):
|
|
color = Color(*surface.unmap_rgb(pixels[x][y]))
|
|
if color.hsla[3]:
|
|
pixels[x][y] = get_inverted_color(color)
|
|
del pixels
|
|
return surface
|
|
|
|
def fill_tile(surface, tile, rect=None, flags=0):
|
|
w, h = tile.get_size()
|
|
surface.set_clip(rect)
|
|
for x in range(0, surface.get_width(), w):
|
|
for y in range(0, surface.get_height(), h):
|
|
surface.blit(tile, (x, y), None, flags)
|
|
surface.set_clip(None)
|
|
|
|
def load_frames(path, transparency=False, ppa=True, key=None, query=None):
|
|
if os.path.isfile(path):
|
|
paths = [path]
|
|
else:
|
|
if query:
|
|
paths = sorted(glob.glob(os.path.join(path, query)))
|
|
else:
|
|
paths = [os.path.join(path, name) for name in sorted(os.listdir(path))]
|
|
frames = []
|
|
for path in paths:
|
|
img = pygame.image.load(path)
|
|
if transparency:
|
|
if ppa:
|
|
frame = img.convert_alpha()
|
|
else:
|
|
frame = fill_colorkey(img, key)
|
|
else:
|
|
frame = img.convert()
|
|
frames.append(frame)
|
|
return frames
|
|
|
|
def fill_colorkey(img, key=None):
|
|
if key is None:
|
|
key = 255, 0, 255
|
|
img = img.convert_alpha()
|
|
frame = Surface(img.get_size())
|
|
frame.fill(key)
|
|
frame.set_colorkey(key)
|
|
frame.blit(img, (0, 0))
|
|
return frame
|
|
|
|
def get_shadowed_text(text, font, offset, color, antialias=True, shadow_color=(0, 0, 0),
|
|
colorkey=(255, 0, 255)):
|
|
foreground = font.render(text, antialias, color)
|
|
background = font.render(text, antialias, shadow_color)
|
|
alpha = SRCALPHA if antialias else 0
|
|
surface = Surface((foreground.get_width() + abs(offset[0]),
|
|
foreground.get_height() + abs(offset[1])), alpha)
|
|
if not antialias:
|
|
surface.set_colorkey(colorkey)
|
|
surface.fill(colorkey)
|
|
surface.blit(background, ((abs(offset[0]) + offset[0]) / 2,
|
|
(abs(offset[1]) + offset[1]) / 2))
|
|
surface.blit(foreground, ((abs(offset[0]) - offset[0]) / 2,
|
|
(abs(offset[1]) - offset[1]) / 2))
|
|
return surface
|
|
|
|
def get_blinds_rects(w, h, step=.05, count=4):
|
|
blinds = []
|
|
blind_h = int(round(h / float(count)))
|
|
for ii in range(1, count + 1):
|
|
blinds.append(Rect(0, blind_h * ii, w, 0))
|
|
inflate_h = int(round(blind_h * step))
|
|
if inflate_h < 1:
|
|
inflate_h = 1
|
|
rects = []
|
|
while blinds[0].h < blind_h:
|
|
rects.append([])
|
|
for blind in blinds:
|
|
bottom = blind.bottom
|
|
blind.inflate_ip(0, inflate_h)
|
|
blind.bottom = bottom
|
|
rects[-1].append(blind.copy())
|
|
return rects
|
|
|
|
def get_blinds_frames(surface, step=.05, count=4, fill=(0, 0, 0, 0)):
|
|
frames = []
|
|
rects = []
|
|
h = int(round(surface.get_height() / float(count)))
|
|
for ii in range(1, count + 1):
|
|
rects.append(Rect(0, h * ii, surface.get_width(), 0))
|
|
bar_h = int(round(h * step))
|
|
if bar_h < 1:
|
|
bar_h = 1
|
|
while rects[0].h < h:
|
|
frame = surface.copy()
|
|
for rect in rects:
|
|
bottom = rect.bottom
|
|
rect.inflate_ip(0, bar_h)
|
|
rect.bottom = bottom
|
|
frame.fill(fill, rect)
|
|
frames.append(frame)
|
|
return frames
|
|
|
|
def get_hsla_color(hue, saturation=100, lightness=50, alpha=100):
|
|
color = Color(0, 0, 0, 0)
|
|
color.hsla = hue % 360, saturation, lightness, alpha
|
|
return color
|
|
|
|
def get_random_hsla_color(hue_range=(0, 359), saturation_range=(0, 100),
|
|
lightness_range=(0, 100), alpha_range=(100, 100)):
|
|
return get_hsla_color(
|
|
random.randint(*hue_range), random.randint(*saturation_range), random.randint(*lightness_range),
|
|
random.randint(*alpha_range))
|
|
|
|
def get_hsva_color(hue, saturation=100, value=100, alpha=100):
|
|
color = Color(0, 0, 0, 0)
|
|
color.hsva = hue % 360, saturation, value, alpha
|
|
return color
|
|
|
|
def get_lightened_color(color, lightness):
|
|
h, s, _, a = color.hsla
|
|
return get_hsla_color(h, s, lightness, a)
|
|
|
|
def get_glow_frames(radius, segments, colors=[(0, 0, 0), (255, 255, 255)], minsize=4, transparency=True):
|
|
frames = []
|
|
radius = int(round(radius))
|
|
sizes = [int(round(minsize + float(ii) / (segments - 1) * (radius - minsize))) for ii in range(segments)]
|
|
if transparency:
|
|
alpha_step = 255.0 / segments
|
|
alpha = alpha_step
|
|
else:
|
|
alpha = 255
|
|
for color_offset in range(len(colors)):
|
|
frame = Surface([radius * 2] * 2, SRCALPHA if transparency else None)
|
|
if transparency:
|
|
alpha = alpha_step
|
|
for segment_ii, segment_radius in enumerate(reversed(sizes)):
|
|
color = Color(*(colors[(color_offset + segment_ii) % len(colors)] + (int(round(alpha)),)))
|
|
gfxdraw.filled_circle(frame, radius, radius, int(round(segment_radius)), color)
|
|
if transparency:
|
|
alpha += alpha_step
|
|
frames.append(frame)
|
|
return frames
|
|
|
|
# http://www.pygame.org/wiki/BezierCurve
|
|
def compute_bezier_points(vertices, numPoints=60):
|
|
points = []
|
|
b0x = vertices[0][0]
|
|
b0y = vertices[0][1]
|
|
b1x = vertices[1][0]
|
|
b1y = vertices[1][1]
|
|
b2x = vertices[2][0]
|
|
b2y = vertices[2][1]
|
|
b3x = vertices[3][0]
|
|
b3y = vertices[3][1]
|
|
ax = -b0x + 3 * b1x + -3 * b2x + b3x
|
|
ay = -b0y + 3 * b1y + -3 * b2y + b3y
|
|
bx = 3 * b0x + -6 * b1x + 3 * b2x
|
|
by = 3 * b0y + -6 * b1y + 3 * b2y
|
|
cx = -3 * b0x + 3 * b1x
|
|
cy = -3 * b0y + 3 * b1y
|
|
dx = b0x
|
|
dy = b0y
|
|
numSteps = numPoints - 1
|
|
h = 1.0 / numSteps
|
|
pointX = dx
|
|
pointY = dy
|
|
firstFDX = ax * h ** 3 + bx * h ** 2 + cx * h
|
|
firstFDY = ay * h ** 3 + by * h ** 2 + cy * h
|
|
secondFDX = 6 * ax * h ** 3 + 2 * bx * h ** 2
|
|
secondFDY = 6 * ay * h ** 3 + 2 * by * h ** 2
|
|
thirdFDX = 6 * ax * h ** 3
|
|
thirdFDY = 6 * ay * h ** 3
|
|
points.append(Vector(pointX, pointY))
|
|
for i in range(numSteps):
|
|
pointX += firstFDX
|
|
pointY += firstFDY
|
|
firstFDX += secondFDX
|
|
firstFDY += secondFDY
|
|
secondFDX += thirdFDX
|
|
secondFDY += thirdFDY
|
|
points.append(Vector(pointX, pointY))
|
|
return points
|