1571 lines
49 KiB
Python
1571 lines
49 KiB
Python
# ----------------------------------------------------------------------------
|
|
# pyglet
|
|
# Copyright (c) 2006-2008 Alex Holkner
|
|
# Copyright (c) 2008-2021 pyglet contributors
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in
|
|
# the documentation and/or other materials provided with the
|
|
# distribution.
|
|
# * Neither the name of pyglet nor the names of its
|
|
# contributors may be used to endorse or promote products
|
|
# derived from this software without specific prior written
|
|
# permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
# ----------------------------------------------------------------------------
|
|
|
|
"""2D shapes.
|
|
|
|
This module provides classes for a variety of simplistic 2D shapes,
|
|
such as Rectangles, Circles, and Lines. These shapes are made
|
|
internally from OpenGL primitives, and provide excellent performance
|
|
when drawn as part of a :py:class:`~pyglet.graphics.Batch`.
|
|
Convenience methods are provided for positioning, changing color
|
|
and opacity, and rotation (where applicable). To create more
|
|
complex shapes than what is provided here, the lower level
|
|
graphics API is more appropriate.
|
|
See the :ref:`guide_graphics` for more details.
|
|
|
|
A simple example of drawing shapes::
|
|
|
|
import pyglet
|
|
from pyglet import shapes
|
|
|
|
window = pyglet.window.Window(960, 540)
|
|
batch = pyglet.graphics.Batch()
|
|
|
|
circle = shapes.Circle(700, 150, 100, color=(50, 225, 30), batch=batch)
|
|
square = shapes.Rectangle(200, 200, 200, 200, color=(55, 55, 255), batch=batch)
|
|
rectangle = shapes.Rectangle(250, 300, 400, 200, color=(255, 22, 20), batch=batch)
|
|
rectangle.opacity = 128
|
|
rectangle.rotation = 33
|
|
line = shapes.Line(100, 100, 100, 200, width=19, batch=batch)
|
|
line2 = shapes.Line(150, 150, 444, 111, width=4, color=(200, 20, 20), batch=batch)
|
|
star = shapes.Star(800, 400, 60, 40, num_spikes=20, color=(255, 255, 0), batch=batch)
|
|
|
|
@window.event
|
|
def on_draw():
|
|
window.clear()
|
|
batch.draw()
|
|
|
|
pyglet.app.run()
|
|
|
|
|
|
|
|
.. versionadded:: 1.5.4
|
|
"""
|
|
|
|
import math
|
|
|
|
import pyglet
|
|
|
|
from pyglet.gl import GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
|
|
from pyglet.gl import GL_TRIANGLES, GL_LINES, GL_BLEND
|
|
from pyglet.gl import glBlendFunc, glEnable, glDisable
|
|
from pyglet.graphics import shader, Batch, ShaderGroup
|
|
|
|
|
|
vertex_source = """#version 150 core
|
|
in vec2 position;
|
|
in vec4 colors;
|
|
|
|
out vec4 vertex_colors;
|
|
|
|
uniform WindowBlock
|
|
{
|
|
mat4 projection;
|
|
mat4 view;
|
|
} window;
|
|
|
|
void main()
|
|
{
|
|
gl_Position = window.projection * window.view * vec4(position, 0, 1);
|
|
vertex_colors = colors;
|
|
}
|
|
"""
|
|
|
|
fragment_source = """#version 150 core
|
|
in vec4 vertex_colors;
|
|
out vec4 final_color;
|
|
|
|
void main()
|
|
{
|
|
final_color = vertex_colors;
|
|
}
|
|
"""
|
|
|
|
|
|
def get_default_shader():
|
|
try:
|
|
return pyglet.gl.current_context.pyglet_shapes_default_shader
|
|
except AttributeError:
|
|
_default_vert_shader = pyglet.graphics.shader.Shader(vertex_source, 'vertex')
|
|
_default_frag_shader = pyglet.graphics.shader.Shader(fragment_source, 'fragment')
|
|
default_shader_program = pyglet.graphics.shader.ShaderProgram(_default_vert_shader, _default_frag_shader)
|
|
pyglet.gl.current_context.pyglet_shapes_default_shader = default_shader_program
|
|
return default_shader_program
|
|
|
|
|
|
class _ShapeGroup(ShaderGroup):
|
|
"""Shared Shape rendering Group.
|
|
|
|
The group is automatically coalesced with other shape groups
|
|
sharing the same parent group and blend parameters.
|
|
"""
|
|
|
|
def __init__(self, blend_src, blend_dest, parent=None):
|
|
"""Create a Shape group.
|
|
|
|
The group is created internally. Usually you do not
|
|
need to explicitly create it.
|
|
|
|
:Parameters:
|
|
`blend_src` : int
|
|
OpenGL blend source mode; for example,
|
|
``GL_SRC_ALPHA``.
|
|
`blend_dest` : int
|
|
OpenGL blend destination mode; for example,
|
|
``GL_ONE_MINUS_SRC_ALPHA``.
|
|
`parent` : `~pyglet.graphics.Group`
|
|
Optional parent group.
|
|
"""
|
|
super().__init__(get_default_shader(), parent=parent)
|
|
self.blend_src = blend_src
|
|
self.blend_dest = blend_dest
|
|
|
|
def set_state(self):
|
|
self.program.bind()
|
|
glEnable(GL_BLEND)
|
|
glBlendFunc(self.blend_src, self.blend_dest)
|
|
|
|
def unset_state(self):
|
|
glDisable(GL_BLEND)
|
|
self.program.unbind()
|
|
|
|
def __eq__(self, other):
|
|
return (other.__class__ is self.__class__ and
|
|
self.parent == other.parent and
|
|
self.order == other.order and
|
|
self.blend_src == other.blend_src and
|
|
self.blend_dest == other.blend_dest and
|
|
self.program == other.program)
|
|
|
|
def __hash__(self):
|
|
return hash((id(self.parent), self.blend_src, self.blend_dest, self.order, self.program))
|
|
|
|
|
|
class _ShapeBase:
|
|
"""Base class for Shape objects"""
|
|
|
|
_rgb = (255, 255, 255)
|
|
_opacity = 255
|
|
_visible = True
|
|
_x = 0
|
|
_y = 0
|
|
_anchor_x = 0
|
|
_anchor_y = 0
|
|
_batch = None
|
|
_group = None
|
|
_vertex_list = None
|
|
|
|
def __del__(self):
|
|
if self._vertex_list is not None:
|
|
self._vertex_list.delete()
|
|
|
|
def _update_position(self):
|
|
raise NotImplementedError
|
|
|
|
def _update_color(self):
|
|
raise NotImplementedError
|
|
|
|
def draw(self):
|
|
"""Draw the shape at its current position.
|
|
|
|
Using this method is not recommended. Instead, add the
|
|
shape to a `pyglet.graphics.Batch` for efficient rendering.
|
|
"""
|
|
self._group.set_state_recursive()
|
|
self._vertex_list.draw(GL_TRIANGLES)
|
|
self._group.unset_state_recursive()
|
|
|
|
def delete(self):
|
|
self._vertex_list.delete()
|
|
self._vertex_list = None
|
|
|
|
@property
|
|
def x(self):
|
|
"""X coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._x
|
|
|
|
@x.setter
|
|
def x(self, value):
|
|
self._x = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def y(self):
|
|
"""Y coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._y
|
|
|
|
@y.setter
|
|
def y(self, value):
|
|
self._y = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def position(self):
|
|
"""The (x, y) coordinates of the shape, as a tuple.
|
|
|
|
:Parameters:
|
|
`x` : int or float
|
|
X coordinate of the sprite.
|
|
`y` : int or float
|
|
Y coordinate of the sprite.
|
|
"""
|
|
return self._x, self._y
|
|
|
|
@position.setter
|
|
def position(self, values):
|
|
self._x, self._y = values
|
|
self._update_position()
|
|
|
|
@property
|
|
def anchor_x(self):
|
|
"""The X coordinate of the anchor point
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._anchor_x
|
|
|
|
@anchor_x.setter
|
|
def anchor_x(self, value):
|
|
self._anchor_x = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def anchor_y(self):
|
|
"""The Y coordinate of the anchor point
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._anchor_y
|
|
|
|
@anchor_y.setter
|
|
def anchor_y(self, value):
|
|
self._anchor_y = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def anchor_position(self):
|
|
"""The (x, y) coordinates of the anchor point, as a tuple.
|
|
|
|
:Parameters:
|
|
`x` : int or float
|
|
X coordinate of the anchor point.
|
|
`y` : int or float
|
|
Y coordinate of the anchor point.
|
|
"""
|
|
return self._anchor_x, self._anchor_y
|
|
|
|
@anchor_position.setter
|
|
def anchor_position(self, values):
|
|
self._anchor_x, self._anchor_y = values
|
|
self._update_position()
|
|
|
|
@property
|
|
def color(self):
|
|
"""The shape color.
|
|
|
|
This property sets the color of the shape.
|
|
|
|
The color is specified as an RGB tuple of integers '(red, green, blue)'.
|
|
Each color component must be in the range 0 (dark) to 255 (saturated).
|
|
|
|
:type: (int, int, int)
|
|
"""
|
|
return self._rgb
|
|
|
|
@color.setter
|
|
def color(self, values):
|
|
self._rgb = tuple(map(int, values))
|
|
self._update_color()
|
|
|
|
@property
|
|
def opacity(self):
|
|
"""Blend opacity.
|
|
|
|
This property sets the alpha component of the color of the shape.
|
|
With the default blend mode (see the constructor), this allows the
|
|
shape to be drawn with fractional opacity, blending with the
|
|
background.
|
|
|
|
An opacity of 255 (the default) has no effect. An opacity of 128
|
|
will make the shape appear translucent.
|
|
|
|
:type: int
|
|
"""
|
|
return self._opacity
|
|
|
|
@opacity.setter
|
|
def opacity(self, value):
|
|
self._opacity = value
|
|
self._update_color()
|
|
|
|
@property
|
|
def visible(self):
|
|
"""True if the shape will be drawn.
|
|
|
|
:type: bool
|
|
"""
|
|
return self._visible
|
|
|
|
@visible.setter
|
|
def visible(self, value):
|
|
self._visible = value
|
|
self._update_position()
|
|
|
|
|
|
class Arc(_ShapeBase):
|
|
def __init__(self, x, y, radius, segments=None, angle=math.tau, start_angle=0,
|
|
closed=False, color=(255, 255, 255), batch=None, group=None):
|
|
"""Create an Arc.
|
|
|
|
The Arc's anchor point (x, y) defaults to it's center.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
X coordinate of the circle.
|
|
`y` : float
|
|
Y coordinate of the circle.
|
|
`radius` : float
|
|
The desired radius.
|
|
`segments` : int
|
|
You can optionally specify how many distinct line segments
|
|
the arc should be made from. If not specified it will be
|
|
automatically calculated using the formula:
|
|
`max(14, int(radius / 1.25))`.
|
|
`angle` : float
|
|
The angle of the arc, in radians. Defaults to tau (pi * 2),
|
|
which is a full circle.
|
|
`start_angle` : float
|
|
The start angle of the arc, in radians. Defaults to 0.
|
|
`closed` : bool
|
|
If True, the ends of the arc will be connected with a line.
|
|
defaults to False.
|
|
`color` : (int, int, int)
|
|
The RGB color of the circle, specified as a tuple of
|
|
three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the circle to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the circle.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._radius = radius
|
|
self._segments = segments or max(14, int(radius / 1.25))
|
|
self._num_verts = self._segments * 2 + (2 if closed else 0)
|
|
|
|
self._rgb = color
|
|
self._angle = angle
|
|
self._start_angle = start_angle
|
|
self._closed = closed
|
|
self._rotation = 0
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
|
|
self._vertex_list = self._batch.add(self._num_verts, GL_LINES, self._group, 'position2f', 'colors4Bn')
|
|
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
vertices = (0,) * self._segments * 4
|
|
else:
|
|
x = self._x + self._anchor_x
|
|
y = self._y + self._anchor_y
|
|
r = self._radius
|
|
tau_segs = self._angle / self._segments
|
|
start_angle = self._start_angle - math.radians(self._rotation)
|
|
|
|
# Calculate the outer points of the arc:
|
|
points = [(x + (r * math.cos((i * tau_segs) + start_angle)),
|
|
y + (r * math.sin((i * tau_segs) + start_angle))) for i in range(self._segments + 1)]
|
|
|
|
# Create a list of doubled-up points from the points:
|
|
vertices = []
|
|
for i in range(len(points) - 1):
|
|
line_points = *points[i], *points[i + 1]
|
|
vertices.extend(line_points)
|
|
|
|
if self._closed:
|
|
chord_points = *points[-1], *points[0]
|
|
vertices.extend(chord_points)
|
|
|
|
self._vertex_list.position[:] = vertices
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * self._num_verts
|
|
|
|
@property
|
|
def rotation(self):
|
|
"""Clockwise rotation of the arc, in degrees.
|
|
|
|
The arc will be rotated about its (anchor_x, anchor_y)
|
|
position.
|
|
|
|
:type: float
|
|
"""
|
|
return self._rotation
|
|
|
|
@rotation.setter
|
|
def rotation(self, rotation):
|
|
self._rotation = rotation
|
|
self._update_position()
|
|
|
|
def draw(self):
|
|
"""Draw the shape at its current position.
|
|
|
|
Using this method is not recommended. Instead, add the
|
|
shape to a `pyglet.graphics.Batch` for efficient rendering.
|
|
"""
|
|
self._vertex_list.draw(GL_LINES)
|
|
|
|
|
|
class Circle(_ShapeBase):
|
|
def __init__(self, x, y, radius, segments=None, color=(255, 255, 255), batch=None, group=None):
|
|
"""Create a circle.
|
|
|
|
The circle's anchor point (x, y) defaults to the center of the circle.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
X coordinate of the circle.
|
|
`y` : float
|
|
Y coordinate of the circle.
|
|
`radius` : float
|
|
The desired radius.
|
|
`segments` : int
|
|
You can optionally specify how many distinct triangles
|
|
the circle should be made from. If not specified it will
|
|
be automatically calculated based using the formula:
|
|
`max(14, int(radius / 1.25))`.
|
|
`color` : (int, int, int)
|
|
The RGB color of the circle, specified as a tuple of
|
|
three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the circle to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the circle.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._radius = radius
|
|
self._segments = segments or max(14, int(radius / 1.25))
|
|
self._rgb = color
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
|
|
self._vertex_list = self._batch.add(self._segments*3, GL_TRIANGLES, self._group, 'position2f', 'colors4Bn')
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
vertices = (0,) * self._segments * 6
|
|
else:
|
|
x = self._x + self._anchor_x
|
|
y = self._y + self._anchor_y
|
|
r = self._radius
|
|
tau_segs = math.pi * 2 / self._segments
|
|
|
|
# Calculate the outer points of the circle:
|
|
points = [(x + (r * math.cos(i * tau_segs)),
|
|
y + (r * math.sin(i * tau_segs))) for i in range(self._segments)]
|
|
|
|
# Create a list of triangles from the points:
|
|
vertices = []
|
|
for i, point in enumerate(points):
|
|
triangle = x, y, *points[i - 1], *point
|
|
vertices.extend(triangle)
|
|
|
|
self._vertex_list.position[:] = vertices
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * self._segments * 3
|
|
|
|
@property
|
|
def radius(self):
|
|
"""The radius of the circle.
|
|
|
|
:type: float
|
|
"""
|
|
return self._radius
|
|
|
|
@radius.setter
|
|
def radius(self, value):
|
|
self._radius = value
|
|
self._update_position()
|
|
|
|
|
|
class Ellipse(_ShapeBase):
|
|
def __init__(self, x, y, a, b, color=(255, 255, 255), batch=None, group=None):
|
|
"""Create an ellipse.
|
|
|
|
The ellipse's anchor point (x, y) defaults to the center of the ellipse.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
X coordinate of the ellipse.
|
|
`y` : float
|
|
Y coordinate of the ellipse.
|
|
`a` : float
|
|
Semi-major axes of the ellipse.
|
|
`b`: float
|
|
Semi-minor axes of the ellipse.
|
|
`color` : (int, int, int)
|
|
The RGB color of the ellipse. specify as a tuple of
|
|
three ints in the range of 0~255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the circle to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the circle.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._a = a
|
|
self._b = b
|
|
self._rgb = color
|
|
self._rotation = 0
|
|
self._segments = int(max(a, b) / 1.25)
|
|
self._num_verts = self._segments * 2
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
self._vertex_list = self._batch.add(self._num_verts, GL_LINES, self._group, 'position2f', 'colors4Bn')
|
|
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
vertices = (0,) * self._num_verts * 4
|
|
else:
|
|
x = self._x + self._anchor_x
|
|
y = self._y + self._anchor_y
|
|
tau_segs = math.pi * 2 / self._segments
|
|
|
|
# Calculate the points of the ellipse by formula:
|
|
points = [(x + self._a * math.cos(i * tau_segs),
|
|
y + self._b * math.sin(i * tau_segs)) for i in range(self._segments + 1)]
|
|
|
|
# Rotate all points:
|
|
if self._rotation:
|
|
r = -math.radians(self._rotation)
|
|
cr = math.cos(r)
|
|
sr = math.sin(r)
|
|
now_points = []
|
|
for point in points:
|
|
now_x = (point[0] - x) * cr - (point[1] - y) * sr + x
|
|
now_y = (point[1] - y) * cr + (point[0] - x) * sr + y
|
|
now_points.append((now_x, now_y))
|
|
points = now_points
|
|
|
|
# Create a list of lines from the points:
|
|
vertices = []
|
|
for i in range(len(points) - 1):
|
|
line_points = *points[i], *points[i + 1]
|
|
vertices.extend(line_points)
|
|
self._vertex_list.position[:] = vertices
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * self._num_verts
|
|
|
|
@property
|
|
def a(self):
|
|
"""The semi-major axes of the ellipse.
|
|
|
|
:type: float
|
|
"""
|
|
return self._a
|
|
|
|
@a.setter
|
|
def a(self, value):
|
|
self._a = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def b(self):
|
|
"""The semi-minor axes of the ellipse.
|
|
|
|
:type: float
|
|
"""
|
|
return self._b
|
|
|
|
@b.setter
|
|
def b(self, value):
|
|
self._b = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def rotation(self):
|
|
"""Clockwise rotation of the arc, in degrees.
|
|
|
|
The arc will be rotated about its (anchor_x, anchor_y)
|
|
position.
|
|
|
|
:type: float
|
|
"""
|
|
return self._rotation
|
|
|
|
@rotation.setter
|
|
def rotation(self, rotation):
|
|
self._rotation = rotation
|
|
self._update_position()
|
|
|
|
def draw(self):
|
|
"""Draw the shape at its current position.
|
|
|
|
Using this method is not recommended. Instead, add the
|
|
shape to a `pyglet.graphics.Batch` for efficient rendering.
|
|
"""
|
|
self._vertex_list.draw(GL_LINES)
|
|
|
|
|
|
class Sector(_ShapeBase):
|
|
def __init__(self, x, y, radius, segments=None, angle=math.tau, start_angle=0,
|
|
color=(255, 255, 255), batch=None, group=None):
|
|
"""Create a Sector of a circle.
|
|
|
|
The sector's anchor point (x, y) defaults to the center of the circle.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
X coordinate of the sector.
|
|
`y` : float
|
|
Y coordinate of the sector.
|
|
`radius` : float
|
|
The desired radius.
|
|
`segments` : int
|
|
You can optionally specify how many distinct triangles
|
|
the sector should be made from. If not specified it will
|
|
be automatically calculated based using the formula:
|
|
`max(14, int(radius / 1.25))`.
|
|
`angle` : float
|
|
The angle of the sector, in radians. Defaults to tau (pi * 2),
|
|
which is a full circle.
|
|
`start_angle` : float
|
|
The start angle of the sector, in radians. Defaults to 0.
|
|
`color` : (int, int, int)
|
|
The RGB color of the sector, specified as a tuple of
|
|
three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the sector to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the sector.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._radius = radius
|
|
self._segments = segments or max(14, int(radius / 1.25))
|
|
|
|
self._rgb = color
|
|
self._angle = angle
|
|
self._start_angle = start_angle
|
|
self._rotation = 0
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
|
|
self._vertex_list = self._batch.add(self._segments * 3, GL_TRIANGLES, self._group, 'position2f', 'colors4Bn')
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
vertices = (0,) * self._segments * 6
|
|
else:
|
|
x = self._x + self._anchor_x
|
|
y = self._y + self._anchor_y
|
|
r = self._radius
|
|
tau_segs = self._angle / self._segments
|
|
start_angle = self._start_angle - math.radians(self._rotation)
|
|
|
|
# Calculate the outer points of the sector.
|
|
points = [(x + (r * math.cos((i * tau_segs) + start_angle)),
|
|
y + (r * math.sin((i * tau_segs) + start_angle))) for i in range(self._segments + 1)]
|
|
|
|
# Create a list of triangles from the points
|
|
vertices = []
|
|
for i, point in enumerate(points[1:], start=1):
|
|
triangle = x, y, *points[i - 1], *point
|
|
vertices.extend(triangle)
|
|
|
|
self._vertex_list.position[:] = vertices
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * self._segments * 3
|
|
|
|
@property
|
|
def angle(self):
|
|
"""The angle of the sector.
|
|
|
|
:type: float
|
|
"""
|
|
return self._angle
|
|
|
|
@angle.setter
|
|
def angle(self, value):
|
|
self._angle = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def radius(self):
|
|
"""The radius of the sector.
|
|
|
|
:type: float
|
|
"""
|
|
return self._radius
|
|
|
|
@radius.setter
|
|
def radius(self, value):
|
|
self._radius = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def rotation(self):
|
|
"""Clockwise rotation of the sector, in degrees.
|
|
|
|
The sector will be rotated about its (anchor_x, anchor_y)
|
|
position.
|
|
|
|
:type: float
|
|
"""
|
|
return self._rotation
|
|
|
|
@rotation.setter
|
|
def rotation(self, rotation):
|
|
self._rotation = rotation
|
|
self._update_position()
|
|
|
|
|
|
class Line(_ShapeBase):
|
|
def __init__(self, x, y, x2, y2, width=1, color=(255, 255, 255), batch=None, group=None):
|
|
"""Create a line.
|
|
|
|
The line's anchor point defaults to the center of the line's
|
|
width on the X axis, and the Y axis.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
The first X coordinate of the line.
|
|
`y` : float
|
|
The first Y coordinate of the line.
|
|
`x2` : float
|
|
The second X coordinate of the line.
|
|
`y2` : float
|
|
The second Y coordinate of the line.
|
|
`width` : float
|
|
The desired width of the line.
|
|
`color` : (int, int, int)
|
|
The RGB color of the line, specified as a tuple of
|
|
three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the line to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the line.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._x2 = x2
|
|
self._y2 = y2
|
|
|
|
self._width = width
|
|
self._rotation = math.degrees(math.atan2(y2 - y, x2 - x))
|
|
self._rgb = color
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
self._vertex_list = self._batch.add(6, GL_TRIANGLES, self._group, 'position2f', 'colors4Bn')
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
self._vertex_list.position[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
else:
|
|
x1 = -self._anchor_y
|
|
y1 = self._anchor_x - self._width / 2
|
|
x = self._x
|
|
y = self._y
|
|
x2 = x1 + math.hypot(self._y2 - y, self._x2 - x)
|
|
y2 = y1 + self._width
|
|
|
|
r = math.atan2(self._y2 - y, self._x2 - x)
|
|
cr = math.cos(r)
|
|
sr = math.sin(r)
|
|
ax = x1 * cr - y1 * sr + x
|
|
ay = x1 * sr + y1 * cr + y
|
|
bx = x2 * cr - y1 * sr + x
|
|
by = x2 * sr + y1 * cr + y
|
|
cx = x2 * cr - y2 * sr + x
|
|
cy = x2 * sr + y2 * cr + y
|
|
dx = x1 * cr - y2 * sr + x
|
|
dy = x1 * sr + y2 * cr + y
|
|
self._vertex_list.position[:] = (ax, ay, bx, by, cx, cy, ax, ay, cx, cy, dx, dy)
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * 6
|
|
|
|
@property
|
|
def x2(self):
|
|
"""Second X coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._x2
|
|
|
|
@x2.setter
|
|
def x2(self, value):
|
|
self._x2 = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def y2(self):
|
|
"""Second Y coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._y2
|
|
|
|
@y2.setter
|
|
def y2(self, value):
|
|
self._y2 = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def position(self):
|
|
"""The (x, y, x2, y2) coordinates of the line, as a tuple.
|
|
|
|
:Parameters:
|
|
`x` : int or float
|
|
X coordinate of the line.
|
|
`y` : int or float
|
|
Y coordinate of the line.
|
|
`x2` : int or float
|
|
X2 coordinate of the line.
|
|
`y2` : int or float
|
|
Y2 coordinate of the line.
|
|
"""
|
|
return self._x, self._y, self._x2, self._y2
|
|
|
|
@position.setter
|
|
def position(self, values):
|
|
self._x, self._y, self._x2, self._y2 = values
|
|
self._update_position()
|
|
|
|
|
|
class Rectangle(_ShapeBase):
|
|
def __init__(self, x, y, width, height, color=(255, 255, 255), batch=None, group=None):
|
|
"""Create a rectangle or square.
|
|
|
|
The rectangle's anchor point defaults to the (x, y) coordinates,
|
|
which are at the bottom left.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
The X coordinate of the rectangle.
|
|
`y` : float
|
|
The Y coordinate of the rectangle.
|
|
`width` : float
|
|
The width of the rectangle.
|
|
`height` : float
|
|
The height of the rectangle.
|
|
`color` : (int, int, int)
|
|
The RGB color of the rectangle, specified as
|
|
a tuple of three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the rectangle to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the rectangle.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._width = width
|
|
self._height = height
|
|
self._rotation = 0
|
|
self._rgb = color
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
self._vertex_list = self._batch.add(6, GL_TRIANGLES, self._group, 'position2f', 'colors4Bn')
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
self._vertex_list.position[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
elif self._rotation:
|
|
x1 = -self._anchor_x
|
|
y1 = -self._anchor_y
|
|
x2 = x1 + self._width
|
|
y2 = y1 + self._height
|
|
x = self._x
|
|
y = self._y
|
|
|
|
r = -math.radians(self._rotation)
|
|
cr = math.cos(r)
|
|
sr = math.sin(r)
|
|
ax = x1 * cr - y1 * sr + x
|
|
ay = x1 * sr + y1 * cr + y
|
|
bx = x2 * cr - y1 * sr + x
|
|
by = x2 * sr + y1 * cr + y
|
|
cx = x2 * cr - y2 * sr + x
|
|
cy = x2 * sr + y2 * cr + y
|
|
dx = x1 * cr - y2 * sr + x
|
|
dy = x1 * sr + y2 * cr + y
|
|
self._vertex_list.position[:] = (ax, ay, bx, by, cx, cy, ax, ay, cx, cy, dx, dy)
|
|
else:
|
|
x1 = self._x - self._anchor_x
|
|
y1 = self._y - self._anchor_y
|
|
x2 = x1 + self._width
|
|
y2 = y1 + self._height
|
|
self._vertex_list.position[:] = (x1, y1, x2, y1, x2, y2, x1, y1, x2, y2, x1, y2)
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * 6
|
|
|
|
@property
|
|
def width(self):
|
|
"""The width of the rectangle.
|
|
|
|
:type: float
|
|
"""
|
|
return self._width
|
|
|
|
@width.setter
|
|
def width(self, value):
|
|
self._width = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def height(self):
|
|
"""The height of the rectangle.
|
|
|
|
:type: float
|
|
"""
|
|
return self._height
|
|
|
|
@height.setter
|
|
def height(self, value):
|
|
self._height = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def rotation(self):
|
|
"""Clockwise rotation of the rectangle, in degrees.
|
|
|
|
The Rectangle will be rotated about its (anchor_x, anchor_y)
|
|
position.
|
|
|
|
:type: float
|
|
"""
|
|
return self._rotation
|
|
|
|
@rotation.setter
|
|
def rotation(self, rotation):
|
|
self._rotation = rotation
|
|
self._update_position()
|
|
|
|
|
|
class BorderedRectangle(_ShapeBase):
|
|
def __init__(self, x, y, width, height, border=1, color=(255, 255, 255),
|
|
border_color=(100, 100, 100), batch=None, group=None):
|
|
"""Create a rectangle or square.
|
|
|
|
The rectangle's anchor point defaults to the (x, y) coordinates,
|
|
which are at the bottom left.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
The X coordinate of the rectangle.
|
|
`y` : float
|
|
The Y coordinate of the rectangle.
|
|
`width` : float
|
|
The width of the rectangle.
|
|
`height` : float
|
|
The height of the rectangle.
|
|
`border` : float
|
|
The thickness of the border.
|
|
`color` : (int, int, int)
|
|
The RGB color of the rectangle, specified as
|
|
a tuple of three ints in the range of 0-255.
|
|
`border_color` : (int, int, int)
|
|
The RGB color of the rectangle's border, specified as
|
|
a tuple of three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the rectangle to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the rectangle.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._width = width
|
|
self._height = height
|
|
self._rotation = 0
|
|
self._border = border
|
|
self._rgb = color
|
|
self._brgb = border_color
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
indices = [0, 1, 2, 0, 2, 3, 0, 4, 3, 4, 7, 3, 0, 1, 5, 0, 5, 4, 1, 2, 5, 5, 2, 6, 6, 2, 3, 6, 3, 7]
|
|
self._vertex_list = self._batch.add_indexed(8, GL_TRIANGLES, self._group, indices, 'position2f', 'colors4Bn')
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
self._vertex_list.position = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
elif self._rotation:
|
|
b = self._border
|
|
x = self._x
|
|
y = self._y
|
|
|
|
bx1 = -self._anchor_x
|
|
by1 = -self._anchor_y
|
|
bx2 = bx1 + self._width
|
|
by2 = by1 + self._height
|
|
ix1 = bx1 + b
|
|
iy1 = by1 + b
|
|
ix2 = bx2 - b
|
|
iy2 = by2 - b
|
|
|
|
r = -math.radians(self._rotation)
|
|
cr = math.cos(r)
|
|
sr = math.sin(r)
|
|
|
|
bax = bx1 * cr - by1 * sr + x
|
|
bay = bx1 * sr + by1 * cr + y
|
|
bbx = bx2 * cr - by1 * sr + x
|
|
bby = bx2 * sr + by1 * cr + y
|
|
bcx = bx2 * cr - by2 * sr + x
|
|
bcy = bx2 * sr + by2 * cr + y
|
|
bdx = bx1 * cr - by2 * sr + x
|
|
bdy = bx1 * sr + by2 * cr + y
|
|
|
|
iax = ix1 * cr - iy1 * sr + x
|
|
iay = ix1 * sr + iy1 * cr + y
|
|
ibx = ix2 * cr - iy1 * sr + x
|
|
iby = ix2 * sr + iy1 * cr + y
|
|
icx = ix2 * cr - iy2 * sr + x
|
|
icy = ix2 * sr + iy2 * cr + y
|
|
idx = ix1 * cr - iy2 * sr + x
|
|
idy = ix1 * sr + iy2 * cr + y
|
|
|
|
self._vertex_list.position[:] = (iax, iay, ibx, iby, icx, icy, idx, idy,
|
|
bax, bay, bbx, bby, bcx, bcy, bdx, bdy,)
|
|
else:
|
|
b = self._border
|
|
bx1 = self._x - self._anchor_x
|
|
by1 = self._y - self._anchor_y
|
|
bx2 = bx1 + self._width
|
|
by2 = by1 + self._height
|
|
ix1 = bx1 + b
|
|
iy1 = by1 + b
|
|
ix2 = bx2 - b
|
|
iy2 = by2 - b
|
|
self._vertex_list.position[:] = (ix1, iy1, ix2, iy1, ix2, iy2, ix1, iy2,
|
|
bx1, by1, bx2, by1, bx2, by2, bx1, by2,)
|
|
|
|
def _update_color(self):
|
|
opacity = int(self._opacity)
|
|
self._vertex_list.colors[:] = [*self._rgb, opacity] * 4 + [*self._brgb, opacity] * 4
|
|
|
|
@property
|
|
def width(self):
|
|
"""The width of the rectangle.
|
|
|
|
:type: float
|
|
"""
|
|
return self._width
|
|
|
|
@width.setter
|
|
def width(self, value):
|
|
self._width = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def height(self):
|
|
"""The height of the rectangle.
|
|
|
|
:type: float
|
|
"""
|
|
return self._height
|
|
|
|
@height.setter
|
|
def height(self, value):
|
|
self._height = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def rotation(self):
|
|
"""Clockwise rotation of the rectangle, in degrees.
|
|
|
|
The Rectangle will be rotated about its (anchor_x, anchor_y)
|
|
position.
|
|
|
|
:type: float
|
|
"""
|
|
return self._rotation
|
|
|
|
@rotation.setter
|
|
def rotation(self, value):
|
|
self._rotation = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def border_color(self):
|
|
"""The rectangle's border color.
|
|
|
|
This property sets the color of the border of a bordered rectangle.
|
|
|
|
The color is specified as an RGB tuple of integers '(red, green, blue)'.
|
|
Each color component must be in the range 0 (dark) to 255 (saturated).
|
|
|
|
:type: (int, int, int)
|
|
"""
|
|
return self._brgb
|
|
|
|
@border_color.setter
|
|
def border_color(self, values):
|
|
self._brgb = tuple(map(int, values))
|
|
self._update_color()
|
|
|
|
|
|
class Triangle(_ShapeBase):
|
|
def __init__(self, x, y, x2, y2, x3, y3, color=(255, 255, 255), batch=None, group=None):
|
|
"""Create a triangle.
|
|
|
|
The triangle's anchor point defaults to the first vertex point.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
The first X coordinate of the triangle.
|
|
`y` : float
|
|
The first Y coordinate of the triangle.
|
|
`x2` : float
|
|
The second X coordinate of the triangle.
|
|
`y2` : float
|
|
The second Y coordinate of the triangle.
|
|
`x3` : float
|
|
The third X coordinate of the triangle.
|
|
`y3` : float
|
|
The third Y coordinate of the triangle.
|
|
`color` : (int, int, int)
|
|
The RGB color of the triangle, specified as
|
|
a tuple of three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the triangle to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the triangle.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._x2 = x2
|
|
self._y2 = y2
|
|
self._x3 = x3
|
|
self._y3 = y3
|
|
self._rotation = 0
|
|
|
|
self._rgb = color
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
self._vertex_list = self._batch.add(3, GL_TRIANGLES, self._group, 'position2f', 'colors4Bn')
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
self._vertex_list.position[:] = (0, 0, 0, 0, 0, 0)
|
|
else:
|
|
anchor_x = self._anchor_x
|
|
anchor_y = self._anchor_y
|
|
x1 = self._x - anchor_x
|
|
y1 = self._y - anchor_y
|
|
x2 = self._x2 - anchor_x
|
|
y2 = self._y2 - anchor_y
|
|
x3 = self._x3 - anchor_x
|
|
y3 = self._y3 - anchor_y
|
|
self._vertex_list.position[:] = (x1, y1, x2, y2, x3, y3)
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * 3
|
|
|
|
@property
|
|
def x2(self):
|
|
"""Second X coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._x2
|
|
|
|
@x2.setter
|
|
def x2(self, value):
|
|
self._x2 = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def y2(self):
|
|
"""Second Y coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._y2
|
|
|
|
@y2.setter
|
|
def y2(self, value):
|
|
self._y2 = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def x3(self):
|
|
"""Third X coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._x3
|
|
|
|
@x3.setter
|
|
def x3(self, value):
|
|
self._x3 = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def y3(self):
|
|
"""Third Y coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._y3
|
|
|
|
@y3.setter
|
|
def y3(self, value):
|
|
self._y3 = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def position(self):
|
|
"""The (x, y, x2, y2, x3, y3) coordinates of the triangle, as a tuple.
|
|
|
|
:Parameters:
|
|
`x` : int or float
|
|
X coordinate of the triangle.
|
|
`y` : int or float
|
|
Y coordinate of the triangle.
|
|
`x2` : int or float
|
|
X2 coordinate of the triangle.
|
|
`y2` : int or float
|
|
Y2 coordinate of the triangle.
|
|
`x3` : int or float
|
|
X3 coordinate of the triangle.
|
|
`y3` : int or float
|
|
Y3 coordinate of the triangle.
|
|
"""
|
|
return self._x, self._y, self._x2, self._y2, self._x3, self._y3
|
|
|
|
@position.setter
|
|
def position(self, values):
|
|
self._x, self._y, self._x2, self._y2, self._x3, self._y3 = values
|
|
self._update_position()
|
|
|
|
|
|
class Star(_ShapeBase):
|
|
def __init__(self, x, y, outer_radius, inner_radius, num_spikes, rotation=0,
|
|
color=(255, 255, 255), batch=None, group=None) -> None:
|
|
"""Create a star.
|
|
|
|
The star's anchor point (x, y) defaults to the center of the star.
|
|
|
|
:Parameters:
|
|
`x` : float
|
|
The X coordinate of the star.
|
|
`y` : float
|
|
The Y coordinate of the star.
|
|
`outer_radius` : float
|
|
The desired outer radius of the star.
|
|
`inner_radius` : float
|
|
The desired inner radius of the star.
|
|
`num_spikes` : float
|
|
The desired number of spikes of the star.
|
|
`rotation` : float
|
|
The rotation of the star in degrees. A rotation of 0 degrees
|
|
will result in one spike lining up with the X axis in
|
|
positive direction.
|
|
`color` : (int, int, int)
|
|
The RGB color of the star, specified as
|
|
a tuple of three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the star to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the star.
|
|
"""
|
|
self._x = x
|
|
self._y = y
|
|
self._outer_radius = outer_radius
|
|
self._inner_radius = inner_radius
|
|
self._num_spikes = num_spikes
|
|
self._rgb = color
|
|
self._rotation = rotation
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
|
|
self._vertex_list = self._batch.add(self._num_spikes*6, GL_TRIANGLES, self._group, 'position2f', 'colors4Bn')
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
vertices = (0, 0) * self._num_spikes * 6
|
|
else:
|
|
x = self._x + self._anchor_x
|
|
y = self._y + self._anchor_y
|
|
r_i = self._inner_radius
|
|
r_o = self._outer_radius
|
|
|
|
# get angle covered by each line (= half a spike)
|
|
d_theta = math.pi / self._num_spikes
|
|
|
|
# phase shift rotation
|
|
phi = self._rotation / 180 * math.pi
|
|
|
|
# calculate alternating points on outer and outer circles
|
|
points = []
|
|
for i in range(self._num_spikes):
|
|
points.append((x + (r_o * math.cos(2*i * d_theta + phi)),
|
|
y + (r_o * math.sin(2*i * d_theta + phi))))
|
|
points.append((x + (r_i * math.cos((2*i+1) * d_theta + phi)),
|
|
y + (r_i * math.sin((2*i+1) * d_theta + phi))))
|
|
|
|
# create a list of doubled-up points from the points
|
|
vertices = []
|
|
for i, point in enumerate(points):
|
|
triangle = x, y, *points[i - 1], *point
|
|
vertices.extend(triangle)
|
|
|
|
self._vertex_list.position[:] = vertices
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * self._num_spikes * 6
|
|
|
|
@property
|
|
def outer_radius(self):
|
|
"""The outer radius of the star."""
|
|
return self._outer_radius
|
|
|
|
@outer_radius.setter
|
|
def outer_radius(self, value):
|
|
self._outer_radius = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def inner_radius(self):
|
|
"""The inner radius of the star."""
|
|
return self._inner_radius
|
|
|
|
@inner_radius.setter
|
|
def inner_radius(self, value):
|
|
self._inner_radius = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def num_spikes(self):
|
|
"""Number of spikes of the star."""
|
|
return self._num_spikes
|
|
|
|
@num_spikes.setter
|
|
def num_spikes(self, value):
|
|
self._num_spikes = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def rotation(self):
|
|
"""Rotation of the star, in degrees.
|
|
"""
|
|
return self._rotation
|
|
|
|
@rotation.setter
|
|
def rotation(self, rotation):
|
|
self._rotation = rotation
|
|
self._update_position()
|
|
|
|
|
|
class Polygon(_ShapeBase):
|
|
def __init__(self, *coordinates, color=(255, 255, 255), batch=None, group=None):
|
|
"""Create a convex polygon.
|
|
|
|
The polygon's anchor point defaults to the first vertex point.
|
|
|
|
:Parameters:
|
|
`coordinates` : List[[int, int]]
|
|
The coordinates for each point in the polygon.
|
|
`color` : (int, int, int)
|
|
The RGB color of the polygon, specified as
|
|
a tuple of three ints in the range of 0-255.
|
|
`batch` : `~pyglet.graphics.Batch`
|
|
Optional batch to add the polygon to.
|
|
`group` : `~pyglet.graphics.Group`
|
|
Optional parent group of the polygon.
|
|
"""
|
|
|
|
# len(self._coordinates) = the number of vertices and sides in the shape.
|
|
self._coordinates = list(coordinates)
|
|
|
|
self._rotation = 0
|
|
|
|
self._rgb = color
|
|
|
|
self._batch = batch or Batch()
|
|
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, group)
|
|
length = (len(self._coordinates) - 2) * 3
|
|
self._vertex_list = self._batch.add(length, GL_TRIANGLES, self._group, 'position2f', 'colors4Bn')
|
|
self._update_position()
|
|
self._update_color()
|
|
|
|
def _update_position(self):
|
|
if not self._visible:
|
|
self._vertex_list.vertices = tuple([0] * ((len(self._coordinates) - 2) * 6))
|
|
elif self._rotation:
|
|
# Adjust all coordinates by the anchor.
|
|
anchor_x = self._anchor_x
|
|
anchor_y = self._anchor_y
|
|
coords = [[x - anchor_x, y - anchor_y] for x, y in self._coordinates]
|
|
|
|
# Rotate the polygon around its first vertex.
|
|
x, y = self._coordinates[0]
|
|
r = -math.radians(self._rotation)
|
|
cr = math.cos(r)
|
|
sr = math.sin(r)
|
|
|
|
for i, c in enumerate(coords):
|
|
c = [c[0] - x, c[1] - y]
|
|
c = [c[0] * cr - c[1] * sr + x, c[0] * sr + c[1] * cr + y]
|
|
coords[i] = c
|
|
|
|
# Triangulate the convex polygon.
|
|
triangles = []
|
|
for n in range(len(coords) - 2):
|
|
triangles += [coords[0], coords[n + 1], coords[n + 2]]
|
|
|
|
# Flattening the list before setting vertices to it.
|
|
self._vertex_list.position[:] = tuple(value for coordinate in triangles for value in coordinate)
|
|
|
|
else:
|
|
# Adjust all coordinates by the anchor.
|
|
anchor_x = self._anchor_x
|
|
anchor_y = self._anchor_y
|
|
coords = [[x - anchor_x, y - anchor_y] for x, y in self._coordinates]
|
|
|
|
# Triangulate the convex polygon.
|
|
triangles = []
|
|
for n in range(len(coords) - 2):
|
|
triangles += [coords[0], coords[n + 1], coords[n + 2]]
|
|
|
|
# Flattening the list before setting vertices to it.
|
|
self._vertex_list.position[:] = tuple(value for coordinate in triangles for value in coordinate)
|
|
|
|
def _update_color(self):
|
|
self._vertex_list.colors[:] = [*self._rgb, int(self._opacity)] * ((len(self._coordinates) - 2) * 3)
|
|
|
|
@property
|
|
def x(self):
|
|
"""X coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._coordinates[0][0]
|
|
|
|
@x.setter
|
|
def x(self, value):
|
|
self._coordinates[0][0] = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def y(self):
|
|
"""Y coordinate of the shape.
|
|
|
|
:type: int or float
|
|
"""
|
|
return self._coordinates[0][1]
|
|
|
|
@y.setter
|
|
def y(self, value):
|
|
self._coordinates[0][1] = value
|
|
self._update_position()
|
|
|
|
@property
|
|
def position(self):
|
|
"""The (x, y) coordinates of the shape, as a tuple.
|
|
|
|
:Parameters:
|
|
`x` : int or float
|
|
X coordinate of the shape.
|
|
`y` : int or float
|
|
Y coordinate of the shape.
|
|
"""
|
|
return self._coordinates[0][0], self._coordinates[0][1]
|
|
|
|
@position.setter
|
|
def position(self, values):
|
|
self._coordinates[0][0], self._coordinates[0][1] = values
|
|
self._update_position()
|
|
|
|
@property
|
|
def rotation(self):
|
|
"""Clockwise rotation of the polygon, in degrees.
|
|
|
|
The Polygon will be rotated about its (anchor_x, anchor_y)
|
|
position.
|
|
|
|
:type: float
|
|
"""
|
|
return self._rotation
|
|
|
|
@rotation.setter
|
|
def rotation(self, rotation):
|
|
self._rotation = rotation
|
|
self._update_position()
|
|
|
|
|
|
__all__ = ('Arc', 'Circle', 'Ellipse', 'Line', 'Rectangle', 'BorderedRectangle', 'Triangle', 'Star', 'Polygon', 'Sector')
|