pyglet 2.1.dev4__py3-none-any.whl → 2.1.dev5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyglet/__init__.py +1 -1
- pyglet/__init__.pyi +32 -1
- pyglet/event.py +1 -1
- pyglet/experimental/multitexture_sprite.py +520 -0
- pyglet/graphics/__init__.py +49 -8
- pyglet/graphics/shader.py +99 -28
- pyglet/image/__init__.py +3 -3
- pyglet/shapes.py +691 -524
- {pyglet-2.1.dev4.dist-info → pyglet-2.1.dev5.dist-info}/METADATA +1 -1
- {pyglet-2.1.dev4.dist-info → pyglet-2.1.dev5.dist-info}/RECORD +12 -11
- {pyglet-2.1.dev4.dist-info → pyglet-2.1.dev5.dist-info}/LICENSE +0 -0
- {pyglet-2.1.dev4.dist-info → pyglet-2.1.dev5.dist-info}/WHEEL +0 -0
pyglet/__init__.py
CHANGED
pyglet/__init__.pyi
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
+
from typing import Sequence
|
|
3
4
|
|
|
4
5
|
from . import app as app
|
|
5
6
|
from . import clock as clock
|
|
@@ -31,6 +32,36 @@ value: str
|
|
|
31
32
|
|
|
32
33
|
@dataclass
|
|
33
34
|
class Options:
|
|
34
|
-
|
|
35
|
+
audio: Sequence[str]
|
|
36
|
+
debug_font: bool
|
|
37
|
+
debug_gl: bool
|
|
38
|
+
debug_gl_trace: bool
|
|
39
|
+
debug_gl_trace_args: bool
|
|
40
|
+
debug_gl_shaders: bool
|
|
41
|
+
debug_graphics_batch: bool
|
|
42
|
+
debug_lib: bool
|
|
43
|
+
debug_media: bool
|
|
44
|
+
debug_texture: bool
|
|
45
|
+
debug_trace: bool
|
|
46
|
+
debug_trace_args: bool
|
|
47
|
+
debug_trace_depth: int
|
|
48
|
+
debug_trace_flush: bool
|
|
49
|
+
debug_win32: bool
|
|
50
|
+
debug_input: bool
|
|
51
|
+
debug_x11: bool
|
|
52
|
+
shadow_window: bool
|
|
53
|
+
vsync: bool | None
|
|
54
|
+
xsync: bool
|
|
55
|
+
xlib_fullscreen_override_redirect: bool
|
|
56
|
+
search_local_libs: bool
|
|
57
|
+
win32_gdi_font: bool
|
|
58
|
+
headless: bool
|
|
59
|
+
headless_device: int
|
|
60
|
+
win32_disable_shaping: bool
|
|
61
|
+
dw_legacy_naming: bool
|
|
62
|
+
win32_disable_xinput: bool
|
|
63
|
+
com_mta: bool
|
|
64
|
+
osx_alt_loop: bool
|
|
65
|
+
shader_bind_management: bool
|
|
35
66
|
|
|
36
67
|
options: Options
|
pyglet/event.py
CHANGED
|
@@ -152,7 +152,7 @@ class EventDispatcher:
|
|
|
152
152
|
_event_stack: tuple | list = ()
|
|
153
153
|
|
|
154
154
|
@classmethod
|
|
155
|
-
def register_event_type(cls: Self, name: str) -> str:
|
|
155
|
+
def register_event_type(cls: type[Self], name: str) -> str:
|
|
156
156
|
"""Register an event type with the dispatcher.
|
|
157
157
|
|
|
158
158
|
Before dispatching events, they must first be registered by name.
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"""Allows for a somewhat generic multitextured sprite.
|
|
2
|
+
|
|
3
|
+
This sprite behaves just like regular sprites.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import pyglet
|
|
9
|
+
from pyglet.gl import glActiveTexture, GL_TEXTURE0, glBindTexture, glEnable, GL_BLEND, glBlendFunc, glDisable, \
|
|
10
|
+
glGetIntegerv, GLint, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_TRIANGLES, GL_MAX_TEXTURE_IMAGE_UNITS
|
|
11
|
+
|
|
12
|
+
class MultiTextureSpriteGroup(pyglet.graphics.Group):
|
|
13
|
+
"""Shared Multi-texture Sprite rendering Group.
|
|
14
|
+
|
|
15
|
+
The Group defines custom ``__eq__`` and ``__hash__`` methods, and so will be
|
|
16
|
+
automatically coalesced with other MultiTexture Sprite Groups sharing the same
|
|
17
|
+
parent Group, Textures, and blend parameters.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, textures: dict[str, Texture], blend_src: int, blend_dest: int,
|
|
21
|
+
program: ShaderProgram | None = None, parent: Group | None = None) -> None:
|
|
22
|
+
"""Create a sprite group for multiple textures and samplers.
|
|
23
|
+
|
|
24
|
+
All textures must share the same target type. The group is created internally
|
|
25
|
+
when a :py:class:`~pyglet.multitexture_sprite.MultiTextureSprite` is created;
|
|
26
|
+
applications usually do not need to explicitly create it.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
textures:
|
|
30
|
+
A dictionary of textures, with the keys being the GLSL sampler name.
|
|
31
|
+
blend_src:
|
|
32
|
+
OpenGL blend source mode; for example, ``GL_SRC_ALPHA``.
|
|
33
|
+
blend_dest:
|
|
34
|
+
OpenGL blend destination mode; for example, ``GL_ONE_MINUS_SRC_ALPHA``.
|
|
35
|
+
program:
|
|
36
|
+
A custom ShaderProgram.
|
|
37
|
+
parent:
|
|
38
|
+
Optional parent group.
|
|
39
|
+
"""
|
|
40
|
+
super().__init__(parent=parent)
|
|
41
|
+
self._textures = textures
|
|
42
|
+
self.blend_src = blend_src
|
|
43
|
+
self.blend_dest = blend_dest
|
|
44
|
+
self.program = program
|
|
45
|
+
|
|
46
|
+
def set_state(self) -> None:
|
|
47
|
+
"""Called before this group is drawn to setup the shader state.
|
|
48
|
+
|
|
49
|
+
The shader program is activated than then all textures are bound and the
|
|
50
|
+
blend mode is setup.
|
|
51
|
+
"""
|
|
52
|
+
self.program.use()
|
|
53
|
+
|
|
54
|
+
for idx, name in enumerate(self._textures):
|
|
55
|
+
self.program[name] = idx
|
|
56
|
+
|
|
57
|
+
for i, texture in enumerate(self._textures.values()):
|
|
58
|
+
glActiveTexture(GL_TEXTURE0 + i)
|
|
59
|
+
glBindTexture(texture.target, texture.id)
|
|
60
|
+
|
|
61
|
+
glEnable(GL_BLEND)
|
|
62
|
+
glBlendFunc(self.blend_src, self.blend_dest)
|
|
63
|
+
|
|
64
|
+
def unset_state(self) -> None:
|
|
65
|
+
"""Called after all draw calls for the group have been made.
|
|
66
|
+
|
|
67
|
+
When done the blend mode is disabled, the shader program is deactivated,
|
|
68
|
+
and all textures are deactivated.
|
|
69
|
+
"""
|
|
70
|
+
glDisable(GL_BLEND)
|
|
71
|
+
self.program.stop()
|
|
72
|
+
glActiveTexture(GL_TEXTURE0)
|
|
73
|
+
|
|
74
|
+
def __repr__(self) -> str:
|
|
75
|
+
return f'{self.__class__.__name__}({[(name,texture) for name,texture in self._textures.items()]})'
|
|
76
|
+
|
|
77
|
+
def __eq__(self, other: object | Group | MultiTextureSpriteGroup) -> bool:
|
|
78
|
+
"""Determines if this group is the same as the other group.
|
|
79
|
+
|
|
80
|
+
The method is broken out like this to make it easier to follow and read.
|
|
81
|
+
"""
|
|
82
|
+
if other.__class__ is not self.__class__:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
if len(self._textures) != len(other._textures):
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
for name, texture in self._textures.items():
|
|
89
|
+
if name not in other._textures:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
if texture.id != other._textures[name].id or texture.target != other._textures[name].target:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
# Made it this far so just check the remainder
|
|
96
|
+
return (self.program is other.program and
|
|
97
|
+
self.blend_src == other.blend_src and
|
|
98
|
+
self.blend_dest == other.blend_dest)
|
|
99
|
+
|
|
100
|
+
def __hash__(self) -> int:
|
|
101
|
+
return hash((self.parent, self.program,
|
|
102
|
+
self.blend_src,
|
|
103
|
+
self.blend_dest) +
|
|
104
|
+
tuple([texture.id for texture in self._textures.values()]) +
|
|
105
|
+
tuple([texture.target for texture in self._textures.values()]))
|
|
106
|
+
|
|
107
|
+
# Allows the default shader to pick the appropriate sampler for the fragment shader
|
|
108
|
+
_SAMPLER_TYPES = {
|
|
109
|
+
pyglet.gl.GL_TEXTURE_2D: "sampler2D",
|
|
110
|
+
pyglet.gl.GL_TEXTURE_2D_ARRAY: "sampler2DArray"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Allows the default shader to grab the correct coords based on texture type
|
|
114
|
+
_SAMPLER_COORDS = {
|
|
115
|
+
pyglet.gl.GL_TEXTURE_2D: ".xy",
|
|
116
|
+
pyglet.gl.GL_TEXTURE_2D_ARRAY: ""
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def _get_default_mt_shader(images: dict[str, Texture]) -> ShaderProgram:
|
|
120
|
+
"""Creates the default multi-texture shader based on the dict of textures passed in.
|
|
121
|
+
|
|
122
|
+
The default shader program will 'overlay' each texture layer on top of each other taking
|
|
123
|
+
into account of the alpha channel of each texture. Textures can be either normal
|
|
124
|
+
2D textures or 2D texture arrays. The maximum number of textures you can layer is
|
|
125
|
+
determined by the maximum number of samplers you can have in a fragment shader.
|
|
126
|
+
"""
|
|
127
|
+
max_tex = GLint()
|
|
128
|
+
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, max_tex)
|
|
129
|
+
assert len(images) <= max_tex.value, f"The default multi-texture shader only supports up to a max of {max_tex.value} textures."
|
|
130
|
+
|
|
131
|
+
# Generate the default vertex shader
|
|
132
|
+
in_tex_coords = '\n'.join([f"in vec3 {name}_coords;" for name in images.keys()])
|
|
133
|
+
out_tex_coords = '\n'.join([f"out vec3 {name}_coords_frag;" for name in images.keys()])
|
|
134
|
+
tex_coords_assignments = '\n'.join([f"{name}_coords_frag = {name}_coords;" for name in images.keys()])
|
|
135
|
+
|
|
136
|
+
vertex_source = f"""
|
|
137
|
+
#version 150 core
|
|
138
|
+
|
|
139
|
+
in vec3 translate;
|
|
140
|
+
in vec4 colors;
|
|
141
|
+
in vec2 scale;
|
|
142
|
+
in vec3 position;
|
|
143
|
+
in float rotation;
|
|
144
|
+
|
|
145
|
+
{in_tex_coords}
|
|
146
|
+
|
|
147
|
+
out vec4 vertex_colors;
|
|
148
|
+
|
|
149
|
+
{out_tex_coords}
|
|
150
|
+
|
|
151
|
+
uniform WindowBlock {{
|
|
152
|
+
mat4 projection;
|
|
153
|
+
mat4 view;
|
|
154
|
+
}} window;
|
|
155
|
+
mat4 m_scale = mat4(1.0);
|
|
156
|
+
mat4 m_rotation = mat4(1.0);
|
|
157
|
+
mat4 m_translate = mat4(1.0);
|
|
158
|
+
|
|
159
|
+
void main()
|
|
160
|
+
{{
|
|
161
|
+
m_scale[0][0] = scale.x;
|
|
162
|
+
m_scale[1][1] = scale.y;
|
|
163
|
+
m_translate[3][0] = translate.x;
|
|
164
|
+
m_translate[3][1] = translate.y;
|
|
165
|
+
m_translate[3][2] = translate.z;
|
|
166
|
+
m_rotation[0][0] = cos(-radians(rotation));
|
|
167
|
+
m_rotation[0][1] = sin(-radians(rotation));
|
|
168
|
+
m_rotation[1][0] = -sin(-radians(rotation));
|
|
169
|
+
m_rotation[1][1] = cos(-radians(rotation));
|
|
170
|
+
gl_Position = window.projection * window.view * m_translate * m_rotation * m_scale * vec4(position, 1.0);
|
|
171
|
+
vertex_colors = colors;
|
|
172
|
+
|
|
173
|
+
{tex_coords_assignments}
|
|
174
|
+
}}
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
in_tex_coords = '\n'.join([f"in vec3 {name}_coords_frag;" for name in images.keys()])
|
|
178
|
+
uniform_samplers = '\n'.join([f"uniform {_SAMPLER_TYPES[tex.target]} {name};" for name,tex in images.items()])
|
|
179
|
+
tex_operations = '\n'.join([f" color = layer(texture({name}, {name}_coords_frag{_SAMPLER_COORDS[tex.target]}), color);" for name,tex in images.items()])
|
|
180
|
+
fragment_source = f"""
|
|
181
|
+
#version 150 core
|
|
182
|
+
|
|
183
|
+
in vec4 vertex_colors;
|
|
184
|
+
{in_tex_coords}
|
|
185
|
+
|
|
186
|
+
out vec4 final_colors;
|
|
187
|
+
|
|
188
|
+
{uniform_samplers}
|
|
189
|
+
|
|
190
|
+
vec4 layer(vec4 foreground, vec4 background) {{
|
|
191
|
+
return foreground * foreground.a + background * (1.0 - foreground.a);
|
|
192
|
+
}}
|
|
193
|
+
|
|
194
|
+
void main() {{
|
|
195
|
+
vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
|
|
196
|
+
{tex_operations}
|
|
197
|
+
final_colors = color * vertex_colors;
|
|
198
|
+
}}
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
return pyglet.gl.current_context.create_program((vertex_source, 'vertex'), (fragment_source, 'fragment'))
|
|
202
|
+
|
|
203
|
+
class MultiTextureSprite(pyglet.sprite.Sprite):
|
|
204
|
+
"""Creates a multi-textured sprite.
|
|
205
|
+
|
|
206
|
+
Multi-textured sprites behave just like regular sprites except they can
|
|
207
|
+
contain multiple texture layers. The default behavior is to overlay each
|
|
208
|
+
layer on top of each other. Each texture layer can be either a static image
|
|
209
|
+
or an animation. If the default behavior is not desired then a custom
|
|
210
|
+
shader program can be supplied overriding the default program.
|
|
211
|
+
|
|
212
|
+
The following complete example loads a 2 layer sprite and draws it to the
|
|
213
|
+
screen::
|
|
214
|
+
|
|
215
|
+
import pyglet
|
|
216
|
+
|
|
217
|
+
batch = pyglet.graphics.Batch()
|
|
218
|
+
|
|
219
|
+
logo_image = pyglet.image.load('logo.png')
|
|
220
|
+
kitten_image = pyglet.image.load('kitten.png')
|
|
221
|
+
sprite = pyglet.experimental.MultiTextureSprite({'kitten': kitten_image, 'logo': logo_image},
|
|
222
|
+
x=50, y=50, batch=batch)
|
|
223
|
+
|
|
224
|
+
window = pyglet.window.Window()
|
|
225
|
+
|
|
226
|
+
@window.event
|
|
227
|
+
def on_draw():
|
|
228
|
+
batch.draw()
|
|
229
|
+
|
|
230
|
+
pyglet.app.run()
|
|
231
|
+
|
|
232
|
+
If a custom program is provided then several assumptions are made by the
|
|
233
|
+
MultiTextureSprite class.
|
|
234
|
+
|
|
235
|
+
* The vertex shader is expected to have each texture coordinates passed in
|
|
236
|
+
with variables named ``<name>_coords`` as vec3 types where ``<name>`` is
|
|
237
|
+
the name of the layer given to the constructor.
|
|
238
|
+
* The fragment shader is expected to have samplers of the appropriate type
|
|
239
|
+
with variables named ``<name>`` where ``<name>`` is the name of the layer
|
|
240
|
+
given to the constructor.
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
group_class = MultiTextureSpriteGroup
|
|
244
|
+
|
|
245
|
+
def __init__(self,
|
|
246
|
+
images: dict[str, AbstractImage | Animation],
|
|
247
|
+
x: float = 0, y: float = 0, z: float = 0,
|
|
248
|
+
blend_src: int = GL_SRC_ALPHA,
|
|
249
|
+
blend_dest: int = GL_ONE_MINUS_SRC_ALPHA,
|
|
250
|
+
batch: Batch | None = None,
|
|
251
|
+
group: Group | None = None,
|
|
252
|
+
subpixel: bool = False,
|
|
253
|
+
program: ShaderProgram | None = None) -> None:
|
|
254
|
+
"""Create a Sprite instance.
|
|
255
|
+
|
|
256
|
+
If the texture layers are different sizes then the largest texture by area is picked for
|
|
257
|
+
size of the sprite and then the other layers are scaled to that size.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
images:
|
|
261
|
+
A dict object with the key being the name of the texture and the
|
|
262
|
+
value is either an Animation or AbstractImage. Currently
|
|
263
|
+
each item must be of the same size.
|
|
264
|
+
x:
|
|
265
|
+
X coordinate of the sprite.
|
|
266
|
+
y:
|
|
267
|
+
Y coordinate of the sprite.
|
|
268
|
+
z:
|
|
269
|
+
Z coordinate of the sprite.
|
|
270
|
+
blend_src:
|
|
271
|
+
OpenGL blend source mode. The default is suitable for
|
|
272
|
+
compositing sprites drawn from back-to-front.
|
|
273
|
+
blend_dest:
|
|
274
|
+
OpenGL blend destination mode. The default is suitable for
|
|
275
|
+
compositing sprites drawn from back-to-front.
|
|
276
|
+
batch:
|
|
277
|
+
Optional batch to add the sprite to.
|
|
278
|
+
group:
|
|
279
|
+
Optional parent group of the sprite.
|
|
280
|
+
subpixel:
|
|
281
|
+
Allow floating-point coordinates for the sprite. By default,
|
|
282
|
+
coordinates are restricted to integer values.
|
|
283
|
+
|
|
284
|
+
program:
|
|
285
|
+
A specific shader program to initialize the sprite with. The
|
|
286
|
+
default multi-texture overlay shader will be used if one is
|
|
287
|
+
not provided.
|
|
288
|
+
"""
|
|
289
|
+
# Ensure the images are textures and load them up into a dict.
|
|
290
|
+
self._textures = {}
|
|
291
|
+
self._texture = None
|
|
292
|
+
for name, img in images.items():
|
|
293
|
+
if isinstance(img, pyglet.image.Animation):
|
|
294
|
+
# Grab the first frame
|
|
295
|
+
self._textures[name] = img.frames[0].image.get_texture()
|
|
296
|
+
else:
|
|
297
|
+
self._textures[name] = img.get_texture()
|
|
298
|
+
|
|
299
|
+
if not self._texture or (self._textures[name].height * self._textures[name].width) > (self._texture.height * self._texture.width):
|
|
300
|
+
self._texture = self._textures[name]
|
|
301
|
+
|
|
302
|
+
if not program:
|
|
303
|
+
self._program = _get_default_mt_shader(self._textures)
|
|
304
|
+
else:
|
|
305
|
+
self._program = program
|
|
306
|
+
|
|
307
|
+
# Right now don't call super unfortunently so we have to do everything ourselves
|
|
308
|
+
self._x = x
|
|
309
|
+
self._y = y
|
|
310
|
+
self._z = z
|
|
311
|
+
|
|
312
|
+
# Go through the images and find all animations
|
|
313
|
+
self._animations = {}
|
|
314
|
+
for name, img in images.items():
|
|
315
|
+
if isinstance(img, pyglet.image.Animation):
|
|
316
|
+
# Setup all of the animation things
|
|
317
|
+
# The key needs to match the key for self._textures so we change it out as needed
|
|
318
|
+
self._animations[name] = { "animation": img, "frame_idx": 0, "next_dt": img.frames[0].duration }
|
|
319
|
+
if img.frames[0].duration:
|
|
320
|
+
pyglet.clock.schedule_once(self._animate, self._animations[name]["next_dt"], name)
|
|
321
|
+
|
|
322
|
+
self._batch = batch
|
|
323
|
+
self._blend_src = blend_src
|
|
324
|
+
self._blend_dest = blend_dest
|
|
325
|
+
self._user_group = group
|
|
326
|
+
self._group = self.get_sprite_group()
|
|
327
|
+
self._subpixel = subpixel
|
|
328
|
+
# FIXME: This is to satisfy the main sprite code and should be done better
|
|
329
|
+
#self._texture = list(self._textures.values())[0].get_texture()
|
|
330
|
+
self._create_vertex_list()
|
|
331
|
+
|
|
332
|
+
def delete(self) -> None:
|
|
333
|
+
"""Force immediate removal of the sprite from video memory.
|
|
334
|
+
|
|
335
|
+
This method removes any scheduled animation calls and then calls
|
|
336
|
+
pyglet.sprite.Sprite.delete method.
|
|
337
|
+
"""
|
|
338
|
+
if self._animations:
|
|
339
|
+
pyglet.clock.unschedule(self._animate)
|
|
340
|
+
|
|
341
|
+
super().delete()
|
|
342
|
+
|
|
343
|
+
def get_sprite_group(self) -> MultiTextureSpriteGroup:
|
|
344
|
+
"""Creates and returns a group to be used to render the sprite.
|
|
345
|
+
|
|
346
|
+
This is used internally to create a consolidated group for rendering.
|
|
347
|
+
"""
|
|
348
|
+
return self.group_class(self._textures, self._blend_src, self._blend_dest, self._program, self._user_group)
|
|
349
|
+
|
|
350
|
+
def _animate(self, dt, key) -> None:
|
|
351
|
+
if key in self._animations:
|
|
352
|
+
animation = self._animations[key]
|
|
353
|
+
animation["frame_idx"] += 1
|
|
354
|
+
if animation["frame_idx"] >= len(animation["animation"].frames):
|
|
355
|
+
animation["frame_idx"] = 0
|
|
356
|
+
self.dispatch_event('on_animation_end')
|
|
357
|
+
if self._vertex_list is None:
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
frame = animation["animation"].frames[animation["frame_idx"]]
|
|
361
|
+
|
|
362
|
+
if frame.duration is not None:
|
|
363
|
+
duration = frame.duration - (animation["next_dt"] - dt)
|
|
364
|
+
duration = min(max(0, duration), frame.duration)
|
|
365
|
+
animation["next_dt"] = duration
|
|
366
|
+
self._set_multi_texture(key, frame.image.get_texture())
|
|
367
|
+
pyglet.clock.schedule_once(self._animate, animation["next_dt"], key)
|
|
368
|
+
|
|
369
|
+
def _set_multi_texture(self, key, new_tex: Texture) -> None:
|
|
370
|
+
if new_tex.id is not self._textures[key].id:
|
|
371
|
+
# Need to make a shallow copy to allow the batch object
|
|
372
|
+
# to correctly split this sprite from other sprite's groups.
|
|
373
|
+
# if not then you will be modifing all othe the other sprites
|
|
374
|
+
# textures dict object as well.
|
|
375
|
+
self._textures = self._textures.copy()
|
|
376
|
+
self._textures[key] = new_tex
|
|
377
|
+
self._vertex_list.delete()
|
|
378
|
+
self._group = self.get_sprite_group()
|
|
379
|
+
self._create_vertex_list()
|
|
380
|
+
else:
|
|
381
|
+
self._textures[key] = new_tex
|
|
382
|
+
getattr(self._vertex_list,f"{key}_coords")[:] = self._textures[key].tex_coords
|
|
383
|
+
|
|
384
|
+
def _create_vertex_list(self) -> None:
|
|
385
|
+
"""
|
|
386
|
+
Override so we can send over texture coords for each texture being used.
|
|
387
|
+
"""
|
|
388
|
+
tex_coords = {}
|
|
389
|
+
for name, tex in self._textures.items():
|
|
390
|
+
tex_coords[f"{name}_coords"] = ('f', tex.tex_coords)
|
|
391
|
+
|
|
392
|
+
self._vertex_list = self._program.vertex_list_indexed(
|
|
393
|
+
4, GL_TRIANGLES, [0, 1, 2, 0, 2, 3], self._batch, self._group,
|
|
394
|
+
position=('f', self._get_vertices()),
|
|
395
|
+
colors=('Bn', (*self._rgb, int(self._opacity)) * 4),
|
|
396
|
+
translate=('f', (self._x, self._y, self._z) * 4),
|
|
397
|
+
scale=('f', (self._scale * self._scale_x, self._scale * self._scale_y) * 4),
|
|
398
|
+
rotation=('f', (self._rotation,) * 4),
|
|
399
|
+
**tex_coords)
|
|
400
|
+
|
|
401
|
+
def set_frame_index(self, name: str, frame_idx: int) -> None:
|
|
402
|
+
"""Set the current Animation frame for the requested texture layer
|
|
403
|
+
|
|
404
|
+
If the texture layer isn't an animation then this method has no effect.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
name:
|
|
408
|
+
The dict key given for the texture layer in the constructor
|
|
409
|
+
frame_idx:
|
|
410
|
+
The frame index to set for the given layer.
|
|
411
|
+
"""
|
|
412
|
+
if name in self._animations:
|
|
413
|
+
animation = self._animations[name]
|
|
414
|
+
if frame_idx < len(animation["animation"].frames):
|
|
415
|
+
animation["frame_idx"] = frame_idx
|
|
416
|
+
frame = animation["animation"].frames[animation["frame_idx"]]
|
|
417
|
+
self._set_multi_texture(name, frame.image.get_texture())
|
|
418
|
+
|
|
419
|
+
def get_frame_index(self, name: str) -> int:
|
|
420
|
+
"""Get the current Animation frame for the requested texture layer
|
|
421
|
+
|
|
422
|
+
If the texture layer isn't an animation then this method always returns 0.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
name:
|
|
426
|
+
The dict key given for the texture layer in the constructor
|
|
427
|
+
"""
|
|
428
|
+
if name in self._animations:
|
|
429
|
+
animation = self._animations[name]
|
|
430
|
+
return animation["frame_idx"]
|
|
431
|
+
|
|
432
|
+
return 0
|
|
433
|
+
|
|
434
|
+
def get_layer(self, name: str) -> AbstractImage | Animation | None:
|
|
435
|
+
"""Return the requested layer. If it is not found then None is returned
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
name:
|
|
439
|
+
The dict key given for the texture layer in the constructor.
|
|
440
|
+
"""
|
|
441
|
+
if name in self._animations:
|
|
442
|
+
return self._animations[name]
|
|
443
|
+
elif name in self._textures:
|
|
444
|
+
return self._textures[name]
|
|
445
|
+
|
|
446
|
+
return None
|
|
447
|
+
|
|
448
|
+
def set_layer(self, name: str, img: AbstractImage | Animation) -> None:
|
|
449
|
+
"""Sets the layer to the new image or animation.
|
|
450
|
+
|
|
451
|
+
This method has no effect if name is not a valid layer. Note: if you
|
|
452
|
+
want to swap out a layer which is an animation then this will cause
|
|
453
|
+
all other animated layers for this sprite to pause until the swap is done.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
name:
|
|
457
|
+
The dict key given for the texture layer in the constructor
|
|
458
|
+
"""
|
|
459
|
+
if name in self._animations:
|
|
460
|
+
# Need to stop all animations temporarly so we can swap the layer out
|
|
461
|
+
pyglet.clock.unschedule(self._animate)
|
|
462
|
+
self._animations.pop(name)
|
|
463
|
+
|
|
464
|
+
# Grab the texture and replace what was there
|
|
465
|
+
tex = None
|
|
466
|
+
if isinstance(img, pyglet.image.Animation):
|
|
467
|
+
# Add the animation and schedule it based on pause
|
|
468
|
+
self._animations[name] = { "animation": img, "frame_idx": 0, "next_dt": img.frames[0].duration }
|
|
469
|
+
if img.frames[0].duration and not self._paused:
|
|
470
|
+
pyglet.clock.schedule_once(self._animate, self._animations[name]["next_dt"], name)
|
|
471
|
+
|
|
472
|
+
tex = img.frames[0].image.get_texture()
|
|
473
|
+
else:
|
|
474
|
+
tex = img.get_texture()
|
|
475
|
+
|
|
476
|
+
if tex:
|
|
477
|
+
self._set_multi_texture(name, tex)
|
|
478
|
+
|
|
479
|
+
if (tex.width * tex.height) > (self._texture.width * self._texture.height):
|
|
480
|
+
self._texture = tex
|
|
481
|
+
# Only update if we actually changed the "base" texture
|
|
482
|
+
self._update_position()
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@property
|
|
486
|
+
def frame_index(self) -> None:
|
|
487
|
+
raise NotImplementedError("MultiTextureSprite does not support the frame_index property. Use get_frame_index instead.")
|
|
488
|
+
|
|
489
|
+
@frame_index.setter
|
|
490
|
+
def frame_index(self, index: int) -> None:
|
|
491
|
+
raise NotImplementedError("MultiTextureSprite does not support the frame_index property. Use set_frame_index instead.")
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def image(self) -> None:
|
|
495
|
+
raise NotImplementedError("MultiTextureSprite does not support the image property. Use get_layer instead.")
|
|
496
|
+
|
|
497
|
+
@image.setter
|
|
498
|
+
def image(self, img: AbstractImage | Animation) -> None:
|
|
499
|
+
raise NotImplementedError("MultiTextureSprite does not support the image property. Use set_layer instead.")
|
|
500
|
+
|
|
501
|
+
@property
|
|
502
|
+
def paused(self) -> bool:
|
|
503
|
+
return self._paused
|
|
504
|
+
|
|
505
|
+
@paused.setter
|
|
506
|
+
def paused(self, pause: bool) -> None:
|
|
507
|
+
if not hasattr(self, '_animations') or pause == self._paused:
|
|
508
|
+
return
|
|
509
|
+
|
|
510
|
+
self._paused = pause
|
|
511
|
+
|
|
512
|
+
if pause:
|
|
513
|
+
pyglet.clock.unschedule(self._animate)
|
|
514
|
+
else:
|
|
515
|
+
# Kick off all of the animations again
|
|
516
|
+
for name, animation in self._animations.items():
|
|
517
|
+
frame = animation["animation"].frames[animation["frame_idx"]]
|
|
518
|
+
if frame.duration:
|
|
519
|
+
animation["next_dt"] = frame.duration
|
|
520
|
+
pyglet.clock.schedule_once(self._animate, self._animations[name]["next_dt"], name)
|
pyglet/graphics/__init__.py
CHANGED
|
@@ -201,6 +201,37 @@ _fragment_source: str = """#version 330 core
|
|
|
201
201
|
}
|
|
202
202
|
"""
|
|
203
203
|
|
|
204
|
+
# Default blit source
|
|
205
|
+
_blit_vertex_source: str = """#version 330 core
|
|
206
|
+
in vec3 position;
|
|
207
|
+
in vec3 tex_coords;
|
|
208
|
+
out vec3 texture_coords;
|
|
209
|
+
|
|
210
|
+
uniform WindowBlock
|
|
211
|
+
{
|
|
212
|
+
mat4 projection;
|
|
213
|
+
mat4 view;
|
|
214
|
+
} window;
|
|
215
|
+
|
|
216
|
+
void main()
|
|
217
|
+
{
|
|
218
|
+
gl_Position = window.projection * window.view * vec4(position, 1.0);
|
|
219
|
+
|
|
220
|
+
texture_coords = tex_coords;
|
|
221
|
+
}
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
_blit_fragment_source: str = """#version 330 core
|
|
225
|
+
in vec3 texture_coords;
|
|
226
|
+
out vec4 final_colors;
|
|
227
|
+
|
|
228
|
+
uniform sampler2D our_texture;
|
|
229
|
+
|
|
230
|
+
void main()
|
|
231
|
+
{
|
|
232
|
+
final_colors = texture(our_texture, texture_coords.xy);
|
|
233
|
+
}
|
|
234
|
+
"""
|
|
204
235
|
|
|
205
236
|
def get_default_batch() -> Batch:
|
|
206
237
|
"""Batch used globally for objects that have no Batch specified."""
|
|
@@ -222,6 +253,16 @@ def get_default_shader() -> ShaderProgram:
|
|
|
222
253
|
pyglet.gl.current_context.object_space.pyglet_graphics_default_shader = _shader_program
|
|
223
254
|
return pyglet.gl.current_context.object_space.pyglet_graphics_default_shader
|
|
224
255
|
|
|
256
|
+
def get_default_blit_shader() -> ShaderProgram:
|
|
257
|
+
"""A default basic shader for blitting, provides no blending."""
|
|
258
|
+
try:
|
|
259
|
+
return pyglet.gl.current_context.object_space.pyglet_graphics_default_blit_shader
|
|
260
|
+
except AttributeError:
|
|
261
|
+
_vertex_shader = shader.Shader(_blit_vertex_source, 'vertex')
|
|
262
|
+
_fragment_shader = shader.Shader(_blit_fragment_source, 'fragment')
|
|
263
|
+
_shader_program = shader.ShaderProgram(_vertex_shader, _fragment_shader)
|
|
264
|
+
pyglet.gl.current_context.object_space.pyglet_graphics_default_blit_shader = _shader_program
|
|
265
|
+
return pyglet.gl.current_context.object_space.pyglet_graphics_default_blit_shader
|
|
225
266
|
|
|
226
267
|
_domain_class_map: dict[tuple[bool, bool], type[vertexdomain.VertexDomain]] = {
|
|
227
268
|
# Indexed, Instanced : Domain
|
|
@@ -449,24 +490,24 @@ class Batch:
|
|
|
449
490
|
|
|
450
491
|
def _dump_draw_list(self) -> None:
|
|
451
492
|
def dump(group: Group, indent: str = '') -> None:
|
|
452
|
-
print(indent, 'Begin group', group)
|
|
493
|
+
print(indent, 'Begin group', group)
|
|
453
494
|
domain_map = self.group_map[group]
|
|
454
495
|
for domain in domain_map.values():
|
|
455
|
-
print(indent, ' ', domain)
|
|
496
|
+
print(indent, ' ', domain)
|
|
456
497
|
for start, size in zip(*domain.allocator.get_allocated_regions()):
|
|
457
|
-
print(indent, ' ', 'Region %d size %d:' % (start, size))
|
|
498
|
+
print(indent, ' ', 'Region %d size %d:' % (start, size))
|
|
458
499
|
for key, attribute in domain.attribute_names.items():
|
|
459
|
-
print(indent, ' ', end=' ')
|
|
500
|
+
print(indent, ' ', end=' ')
|
|
460
501
|
try:
|
|
461
502
|
region = attribute.get_region(attribute.buffer, start, size)
|
|
462
|
-
print(key, region.array[:])
|
|
503
|
+
print(key, region.array[:])
|
|
463
504
|
except: # noqa: E722
|
|
464
|
-
print(key, '(unmappable)')
|
|
505
|
+
print(key, '(unmappable)')
|
|
465
506
|
for child in self.group_children.get(group, ()):
|
|
466
507
|
dump(child, indent + ' ')
|
|
467
|
-
print(indent, 'End group', group)
|
|
508
|
+
print(indent, 'End group', group)
|
|
468
509
|
|
|
469
|
-
print(f'Draw list for {self!r}:')
|
|
510
|
+
print(f'Draw list for {self!r}:')
|
|
470
511
|
for group in self.top_groups:
|
|
471
512
|
dump(group)
|
|
472
513
|
|