pgfw/pgfw/extension.py

393 lines
14 KiB
Python

import itertools, random
from math import sin, cos, atan2, radians, sqrt, pi
from pygame import Surface, PixelArray, Color, Rect, draw
from pygame.mixer import get_num_channels, Channel
from pygame.locals import *
from .Vector import Vector
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 = [[start]]
for ii in range(count):
seg_end = segs[-1][0][0] + rel_step[0], segs[-1][0][1] + rel_step[1]
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):
angle = atan2(end[1] - start[1], end[0] - start[0])
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
# 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, color,
background=None, border=None, border_width=1,
padding=0):
words = text.split()
if words:
line_text = ""
lines = []
height = 0
ii = 0
finished = False
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, background)
height += line.get_height()
lines.append(line)
line_text = ""
else:
line_text += " " + words[ii]
ii += 1
top = 0
surface = Surface((width, height), SRCALPHA)
if background:
surface.fill(background)
rect = surface.get_rect()
for line in lines:
line_rect = line.get_rect()
line_rect.midtop = rect.centerx, top
surface.blit(line, line_rect)
top += line_rect.h
else:
surface = Surface((0, 0), SRCALPHA)
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):
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))
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_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)
# 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