1
+ from array import array
2
+
1
3
import arcade
2
- import arcade .gl as gl
4
+ from arcade .gl import Buffer , BufferDescription , Geometry , Program
5
+ from arcade .math import Vec2
3
6
from arcade .texture_atlas .base import TextureAtlasBase
7
+ from arcade .types import Rect
4
8
5
9
6
10
class NinePatchTexture :
@@ -74,6 +78,7 @@ def __init__(
74
78
self ._initialized = False
75
79
self ._texture = texture
76
80
self ._custom_atlas = atlas
81
+ self ._geometry_cache : tuple [int , int , int , int , Rect ] | None = None
77
82
78
83
# pixel texture co-ordinate start and end of central box.
79
84
self ._left = left
@@ -84,37 +89,16 @@ def __init__(
84
89
self ._check_sizes ()
85
90
86
91
# Created in _init_deferred
87
- self ._program : gl .program .Program
88
- self ._geometry : gl .Geometry
92
+ self ._buffer : Buffer
93
+ self ._program : Program
94
+ self ._geometry : Geometry
89
95
self ._ctx : arcade .ArcadeContext
90
96
self ._atlas : TextureAtlasBase
91
97
try :
92
98
self ._init_deferred ()
93
99
except Exception :
94
100
pass
95
101
96
- def _init_deferred (self ):
97
- """Deferred initialization when lazy loaded"""
98
- self ._ctx = arcade .get_window ().ctx
99
- # TODO: Cache in context?
100
- self ._program = self ._ctx .load_program (
101
- vertex_shader = ":system:shaders/gui/nine_patch_vs.glsl" ,
102
- geometry_shader = ":system:shaders/gui/nine_patch_gs.glsl" ,
103
- fragment_shader = ":system:shaders/gui/nine_patch_fs.glsl" ,
104
- )
105
- # Configure texture channels
106
- self ._program .set_uniform_safe ("uv_texture" , 0 )
107
- self ._program ["sprite_texture" ] = 1
108
-
109
- # TODO: Cache in context?
110
- self ._geometry = self ._ctx .geometry ()
111
-
112
- # References for the texture
113
- self ._atlas = self ._custom_atlas or self ._ctx .default_atlas
114
- self ._add_to_atlas (self .texture )
115
-
116
- self ._initialized = True
117
-
118
102
def initialize (self ) -> None :
119
103
"""
120
104
Manually initialize the NinePatchTexture if it was lazy loaded.
@@ -141,7 +125,7 @@ def texture(self, texture: arcade.Texture):
141
125
self ._add_to_atlas (texture )
142
126
143
127
@property
144
- def program (self ) -> gl . program . Program :
128
+ def program (self ) -> Program :
145
129
"""Get or set the shader program.
146
130
147
131
Returns the default shader if no other shader is assigned.
@@ -152,7 +136,7 @@ def program(self) -> gl.program.Program:
152
136
return self ._program
153
137
154
138
@program .setter
155
- def program (self , program : gl . program . Program ):
139
+ def program (self , program : Program ):
156
140
if not self ._initialized :
157
141
raise RuntimeError ("The NinePatchTexture has not been initialized" )
158
142
@@ -241,26 +225,22 @@ def draw_rect(
241
225
if not self ._initialized :
242
226
self ._init_deferred ()
243
227
228
+ self ._create_geometry (rect )
229
+
244
230
if blend :
245
231
self ._ctx .enable_only (self ._ctx .BLEND )
246
232
else :
247
233
self ._ctx .disable (self ._ctx .BLEND )
248
234
249
- self .program .set_uniform_safe ("texture_id" , self ._atlas .get_texture_id (self ._texture ))
250
235
if pixelated :
251
236
self ._atlas .texture .filter = self ._ctx .NEAREST , self ._ctx .NEAREST
252
237
else :
253
238
self ._atlas .texture .filter = self ._ctx .LINEAR , self ._ctx .LINEAR
254
239
255
- self .program ["position" ] = rect .bottom_left
256
- self .program ["start" ] = self ._left , self ._bottom
257
- self .program ["end" ] = self .width - self ._right , self .height - self ._top
258
- self .program ["size" ] = rect .size
259
- self .program ["t_size" ] = self ._texture .size
260
-
261
240
self ._atlas .use_uv_texture (0 )
262
241
self ._atlas .texture .use (1 )
263
- self ._geometry .render (self ._program , vertices = 1 )
242
+
243
+ self ._geometry .render (self ._program )
264
244
265
245
if blend :
266
246
self ._ctx .disable (self ._ctx .BLEND )
@@ -282,3 +262,218 @@ def _check_sizes(self):
282
262
raise ValueError ("Left and right border must be smaller than texture width" )
283
263
if self ._bottom + self ._top > self ._texture .height :
284
264
raise ValueError ("Bottom and top border must be smaller than texture height" )
265
+
266
+ def _init_deferred (self ):
267
+ """Deferred initialization when lazy loaded"""
268
+ self ._ctx = arcade .get_window ().ctx
269
+ # TODO: Cache in context?
270
+ self ._program = self ._ctx .load_program (
271
+ vertex_shader = ":system:shaders/gui/nine_patch_vs.glsl" ,
272
+ fragment_shader = ":system:shaders/gui/nine_patch_fs.glsl" ,
273
+ )
274
+ # Configure texture channels
275
+ self ._program .set_uniform_safe ("uv_texture" , 0 )
276
+ self ._program ["sprite_texture" ] = 1
277
+
278
+ # 4 byte floats * 4 floats * 4 vertices * 9 patches
279
+ self ._buffer = self ._ctx .buffer (reserve = 576 )
280
+ # fmt: off
281
+ self ._ibo = self ._ctx .buffer (
282
+ data = array ("i" ,
283
+ [
284
+ # Triangulate the patches
285
+ # First rot
286
+ 0 , 1 , 2 ,
287
+ 3 , 1 , 2 ,
288
+
289
+ 4 , 5 , 6 ,
290
+ 7 , 5 , 6 ,
291
+
292
+ 8 , 9 , 10 ,
293
+ 11 , 9 , 10 ,
294
+
295
+ # Middle row
296
+ 12 , 13 , 14 ,
297
+ 15 , 13 , 14 ,
298
+
299
+ 16 , 17 , 18 ,
300
+ 19 , 17 , 18 ,
301
+
302
+ 20 , 21 , 22 ,
303
+ 23 , 21 , 22 ,
304
+
305
+ # Bottom row
306
+ 24 , 25 , 26 ,
307
+ 27 , 25 , 26 ,
308
+
309
+ 28 , 29 , 30 ,
310
+ 31 , 29 , 30 ,
311
+
312
+ 32 , 33 , 34 ,
313
+ 35 , 33 , 34 ,
314
+ ]
315
+ ),
316
+ )
317
+ # fmt: on
318
+ self ._geometry = self ._ctx .geometry (
319
+ content = [BufferDescription (self ._buffer , "2f 2f" , ["in_position" , "in_uv" ])],
320
+ index_buffer = self ._ibo ,
321
+ mode = self ._ctx .TRIANGLES ,
322
+ index_element_size = 4 ,
323
+ )
324
+
325
+ # References for the texture
326
+ self ._atlas = self ._custom_atlas or self ._ctx .default_atlas
327
+ self ._add_to_atlas (self .texture )
328
+
329
+ # NOTE: Important to create geometry after the texture is added to the atlas
330
+ # self._create_geometry(LBWH(0, 0, self.width, self.height))
331
+ self ._initialized = True
332
+
333
+ def _create_geometry (self , rect : Rect ):
334
+ """Create vertices for the 9-patch texture."""
335
+ # NOTE: This was ported from glsl geometry shader to python
336
+ # Simulate old uniforms
337
+ cache_key = (self ._left , self ._right , self ._bottom , self ._top , rect )
338
+ if cache_key == self ._geometry_cache :
339
+ return
340
+ self ._geometry_cache = cache_key
341
+
342
+ position = rect .bottom_left
343
+ start = Vec2 (self ._left , self ._bottom )
344
+ end = Vec2 (self .width - self ._right , self .height - self ._top )
345
+ size = rect .size
346
+ t_size = Vec2 (* self ._texture .size )
347
+ atlas_size = Vec2 (* self ._atlas .size )
348
+
349
+ # Patch points starting from upper left row by row
350
+ p1 = position + Vec2 (0.0 , size .y )
351
+ p2 = position + Vec2 (start .x , size .y )
352
+ p3 = position + Vec2 (size .x - (t_size .x - end .x ), size .y )
353
+ p4 = position + Vec2 (size .x , size .y )
354
+
355
+ y = size .y - (t_size .y - end .y )
356
+ p5 = position + Vec2 (0.0 , y )
357
+ p6 = position + Vec2 (start .x , y )
358
+ p7 = position + Vec2 (size .x - (t_size .x - end .x ), y )
359
+ p8 = position + Vec2 (size .x , y )
360
+
361
+ p9 = position + Vec2 (0.0 , start .y )
362
+ p10 = position + Vec2 (start .x , start .y )
363
+ p11 = position + Vec2 (size .x - (t_size .x - end .x ), start .y )
364
+ p12 = position + Vec2 (size .x , start .y )
365
+
366
+ p13 = position + Vec2 (0.0 , 0.0 )
367
+ p14 = position + Vec2 (start .x , 0.0 )
368
+ p15 = position + Vec2 (size .x - (t_size .x - end .x ), 0.0 )
369
+ p16 = position + Vec2 (size .x , 0.0 )
370
+
371
+ # <AtlasRegion
372
+ # x=1 y=1
373
+ # width=100 height=100
374
+ # uvs=(
375
+ # 0.001953125, 0.001953125,
376
+ # 0.197265625, 0.001953125,
377
+ # 0.001953125, 0.197265625,
378
+ # 0.197265625, 0.197265625,
379
+ # )
380
+ # Get texture coordinates
381
+ # vec2 uv0, uv1, uv2, uv3
382
+ region = self ._atlas .get_texture_region_info (self ._texture .atlas_name )
383
+ tex_coords = region .texture_coordinates
384
+ uv0 = Vec2 (tex_coords [0 ], tex_coords [1 ])
385
+ uv1 = Vec2 (tex_coords [2 ], tex_coords [3 ])
386
+ uv2 = Vec2 (tex_coords [4 ], tex_coords [5 ])
387
+ uv3 = Vec2 (tex_coords [6 ], tex_coords [7 ])
388
+
389
+ # Local corner offsets in pixels
390
+ left = start .x
391
+ right = t_size .x - end .x
392
+ top = t_size .y - end .y
393
+ bottom = start .y
394
+
395
+ # UV offsets to the inner rectangle in the patch
396
+ # This is the global texture coordinate offset in the entire atlas
397
+ c1 = Vec2 (left , top ) / atlas_size # Upper left corner
398
+ c2 = Vec2 (right , top ) / atlas_size # Upper right corner
399
+ c3 = Vec2 (left , bottom ) / atlas_size # Lower left corner
400
+ c4 = Vec2 (right , bottom ) / atlas_size # Lower right corner
401
+
402
+ # Texture coordinates for all the points in the patch
403
+ t1 = uv0
404
+ t2 = uv0 + Vec2 (c1 .x , 0.0 )
405
+ t3 = uv1 - Vec2 (c2 .x , 0.0 )
406
+ t4 = uv1
407
+
408
+ t5 = uv0 + Vec2 (0.0 , c1 .y )
409
+ t6 = uv0 + c1
410
+ t7 = uv1 + Vec2 (- c2 .x , c2 .y )
411
+ t8 = uv1 + Vec2 (0.0 , c2 .y )
412
+
413
+ t9 = uv2 - Vec2 (0.0 , c3 .y )
414
+ t10 = uv2 + Vec2 (c3 .x , - c3 .y )
415
+ t11 = uv3 - c4
416
+ t12 = uv3 - Vec2 (0.0 , c4 .y )
417
+
418
+ t13 = uv2
419
+ t14 = uv2 + Vec2 (c3 .x , 0.0 )
420
+ t15 = uv3 - Vec2 (c4 .x , 0.0 )
421
+ t16 = uv3
422
+
423
+ # fmt: off
424
+ primitives = [
425
+ # First row - two fixed corners + stretchy middle
426
+ # Upper left corner. Fixed size.
427
+ p1 , t1 ,
428
+ p5 , t5 ,
429
+ p2 , t2 ,
430
+ p6 , t6 ,
431
+ # Upper middle part stretches on x axis
432
+ p2 , t2 ,
433
+ p6 , t6 ,
434
+ p3 , t3 ,
435
+ p7 , t7 ,
436
+ # Upper right corner. Fixed size
437
+ p3 , t3 ,
438
+ p7 , t7 ,
439
+ p4 , t4 ,
440
+ p8 , t8 ,
441
+
442
+ # Middle row: Two stretchy sides + stretchy middle
443
+ # left border sketching on y axis
444
+ p5 , t5 ,
445
+ p9 , t9 ,
446
+ p6 , t6 ,
447
+ p10 , t10 ,
448
+ # Center stretchy area
449
+ p6 , t6 ,
450
+ p10 , t10 ,
451
+ p7 , t7 ,
452
+ p11 , t11 ,
453
+ # Right border. Stenches on y axis
454
+ p7 , t7 ,
455
+ p11 , t11 ,
456
+ p8 , t8 ,
457
+ p12 , t12 ,
458
+
459
+ # Bottom row: two fixed corners + stretchy middle
460
+ # Lower left corner. Fixed size.
461
+ p9 , t9 ,
462
+ p13 , t13 ,
463
+ p10 , t10 ,
464
+ p14 , t14 ,
465
+ # Lower middle part stretches on x axis
466
+ p10 , t10 ,
467
+ p14 , t14 ,
468
+ p11 , t11 ,
469
+ p15 , t15 ,
470
+ # Lower right corner. Fixed size
471
+ p11 , t11 ,
472
+ p15 , t15 ,
473
+ p12 , t12 ,
474
+ p16 , t16 ,
475
+ ]
476
+ # fmt: on
477
+
478
+ data = array ("f" , [coord for point in primitives for coord in point ])
479
+ self ._buffer .write (data .tobytes ())
0 commit comments