ncca-ngl 0.1.0__py3-none-any.whl → 0.1.1__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.
- {ncca_ngl-0.1.0.dist-info → ncca_ngl-0.1.1.dist-info}/METADATA +9 -9
- ncca_ngl-0.1.1.dist-info/RECORD +4 -0
- {ncca_ngl-0.1.0.dist-info → ncca_ngl-0.1.1.dist-info}/WHEEL +1 -2
- ncca/ngl/PrimData/pack_arrays.py +0 -20
- ncca/ngl/__init__.py +0 -100
- ncca/ngl/abstract_vao.py +0 -89
- ncca/ngl/base_mesh.py +0 -170
- ncca/ngl/base_mesh.pyi +0 -11
- ncca/ngl/bbox.py +0 -224
- ncca/ngl/bezier_curve.py +0 -75
- ncca/ngl/first_person_camera.py +0 -174
- ncca/ngl/image.py +0 -94
- ncca/ngl/log.py +0 -44
- ncca/ngl/mat2.py +0 -128
- ncca/ngl/mat3.py +0 -466
- ncca/ngl/mat4.py +0 -456
- ncca/ngl/multi_buffer_vao.py +0 -49
- ncca/ngl/obj.py +0 -416
- ncca/ngl/plane.py +0 -47
- ncca/ngl/primitives.py +0 -706
- ncca/ngl/pyside_event_handling_mixin.py +0 -318
- ncca/ngl/quaternion.py +0 -112
- ncca/ngl/random.py +0 -167
- ncca/ngl/shader.py +0 -229
- ncca/ngl/shader_lib.py +0 -536
- ncca/ngl/shader_program.py +0 -816
- ncca/ngl/simple_index_vao.py +0 -65
- ncca/ngl/simple_vao.py +0 -42
- ncca/ngl/text.py +0 -346
- ncca/ngl/texture.py +0 -75
- ncca/ngl/transform.py +0 -95
- ncca/ngl/util.py +0 -128
- ncca/ngl/vao_factory.py +0 -34
- ncca/ngl/vec2.py +0 -350
- ncca/ngl/vec2_array.py +0 -106
- ncca/ngl/vec3.py +0 -401
- ncca/ngl/vec3_array.py +0 -110
- ncca/ngl/vec4.py +0 -229
- ncca/ngl/vec4_array.py +0 -106
- ncca_ngl-0.1.0.dist-info/RECORD +0 -41
- ncca_ngl-0.1.0.dist-info/top_level.txt +0 -1
- {ncca_ngl-0.1.0.dist-info → ncca_ngl-0.1.1.dist-info}/licenses/LICENSE.txt +0 -0
ncca/ngl/simple_index_vao.py
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import OpenGL.GL as gl
|
|
3
|
-
|
|
4
|
-
from .abstract_vao import AbstractVAO, VertexData
|
|
5
|
-
from .log import logger
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class IndexVertexData(VertexData):
|
|
9
|
-
def __init__(self, data, size, indices, index_type, mode=gl.GL_STATIC_DRAW):
|
|
10
|
-
super().__init__(data, size, mode)
|
|
11
|
-
gl.GL_to_numpy_type = {
|
|
12
|
-
gl.GL_UNSIGNED_INT: np.uint32,
|
|
13
|
-
gl.GL_UNSIGNED_SHORT: np.uint16,
|
|
14
|
-
gl.GL_UNSIGNED_BYTE: np.uint8,
|
|
15
|
-
}
|
|
16
|
-
numpy_dtype = gl.GL_to_numpy_type.get(index_type)
|
|
17
|
-
if numpy_dtype is None:
|
|
18
|
-
logger.error("SimpleIndexVAO: Unsupported index type")
|
|
19
|
-
raise TypeError(f"Unsupported index type: {index_type}")
|
|
20
|
-
|
|
21
|
-
self.indices = np.array(indices, dtype=numpy_dtype)
|
|
22
|
-
self.index_type = index_type
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class SimpleIndexVAO(AbstractVAO):
|
|
26
|
-
def __init__(self, mode=gl.GL_TRIANGLES):
|
|
27
|
-
super().__init__(mode)
|
|
28
|
-
self.buffer = gl.glGenBuffers(1)
|
|
29
|
-
self.idx_buffer = gl.glGenBuffers(1)
|
|
30
|
-
self.index_type = gl.GL_UNSIGNED_INT
|
|
31
|
-
|
|
32
|
-
def draw(self):
|
|
33
|
-
if self.bound and self.allocated:
|
|
34
|
-
gl.glDrawElements(self.mode, self.indices_count, self.index_type, None)
|
|
35
|
-
else:
|
|
36
|
-
logger.error("SimpleIndexVAO not bound or not allocated")
|
|
37
|
-
|
|
38
|
-
def set_data(self, data):
|
|
39
|
-
if not isinstance(data, IndexVertexData):
|
|
40
|
-
logger.error("SimpleIndexVAO: Unsupported index type")
|
|
41
|
-
raise TypeError("data must be of type IndexVertexData")
|
|
42
|
-
|
|
43
|
-
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.buffer)
|
|
44
|
-
gl.glBufferData(gl.GL_ARRAY_BUFFER, data.data.nbytes, data.data, data.mode)
|
|
45
|
-
|
|
46
|
-
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.idx_buffer)
|
|
47
|
-
gl.glBufferData(
|
|
48
|
-
gl.GL_ELEMENT_ARRAY_BUFFER, data.indices.nbytes, data.indices, data.mode
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
self.allocated = True
|
|
52
|
-
self.indices_count = len(data.indices)
|
|
53
|
-
self.index_type = data.index_type
|
|
54
|
-
|
|
55
|
-
def remove_vao(self):
|
|
56
|
-
gl.glDeleteBuffers(1, [self.buffer])
|
|
57
|
-
gl.glDeleteBuffers(1, [self.idx_buffer])
|
|
58
|
-
gl.glDeleteVertexArrays(1, [self.id])
|
|
59
|
-
|
|
60
|
-
def get_buffer_id(self, index=0):
|
|
61
|
-
return self.buffer
|
|
62
|
-
|
|
63
|
-
def map_buffer(self, index=0, access_mode=gl.GL_READ_WRITE):
|
|
64
|
-
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.buffer)
|
|
65
|
-
return gl.glMapBuffer(gl.GL_ARRAY_BUFFER, access_mode)
|
ncca/ngl/simple_vao.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import OpenGL.GL as gl
|
|
2
|
-
|
|
3
|
-
from .abstract_vao import AbstractVAO, VertexData
|
|
4
|
-
from .log import logger
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class SimpleVAO(AbstractVAO):
|
|
8
|
-
def __init__(self, mode=gl.GL_TRIANGLES):
|
|
9
|
-
super().__init__(mode)
|
|
10
|
-
self.buffer = gl.glGenBuffers(1)
|
|
11
|
-
|
|
12
|
-
def draw(self):
|
|
13
|
-
if self.bound and self.allocated:
|
|
14
|
-
gl.glDrawArrays(self.mode, 0, self.indices_count)
|
|
15
|
-
else:
|
|
16
|
-
logger.error("SimpleVAO not bound or not allocated")
|
|
17
|
-
|
|
18
|
-
def set_data(self, data):
|
|
19
|
-
if not isinstance(data, VertexData):
|
|
20
|
-
logger.error("SimpleVAO: Invalid data type")
|
|
21
|
-
raise TypeError("data must be of type VertexData")
|
|
22
|
-
if not self.bound:
|
|
23
|
-
logger.error("SimpleVAO not bound")
|
|
24
|
-
raise RuntimeError("SimpleVAO not bound")
|
|
25
|
-
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.buffer)
|
|
26
|
-
gl.glBufferData(gl.GL_ARRAY_BUFFER, data.data.nbytes, data.data, data.mode)
|
|
27
|
-
self.allocated = True
|
|
28
|
-
self.indices_count = data.size
|
|
29
|
-
|
|
30
|
-
def num_indices(self):
|
|
31
|
-
return self.indices_count
|
|
32
|
-
|
|
33
|
-
def remove_vao(self):
|
|
34
|
-
gl.glDeleteBuffers(1, [self.buffer])
|
|
35
|
-
gl.glDeleteVertexArrays(1, [self.id])
|
|
36
|
-
|
|
37
|
-
def get_buffer_id(self, index=0):
|
|
38
|
-
return self.buffer
|
|
39
|
-
|
|
40
|
-
def map_buffer(self, index=0, access_mode=gl.GL_READ_WRITE):
|
|
41
|
-
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.buffer)
|
|
42
|
-
return gl.glMapBuffer(gl.GL_ARRAY_BUFFER, access_mode)
|
ncca/ngl/text.py
DELETED
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
A module for rendering text in OpenGL using pre-rendered font atlases.
|
|
3
|
-
|
|
4
|
-
This implementation uses the 'freetype-py' library to rasterize characters
|
|
5
|
-
from a given font file into a single texture atlas. This atlas is then
|
|
6
|
-
used by a set of shaders (vertex, geometry, and fragment) to render
|
|
7
|
-
text efficiently.
|
|
8
|
-
|
|
9
|
-
The process is as follows:
|
|
10
|
-
1. FontAtlas class:
|
|
11
|
-
- Loads a font file.
|
|
12
|
-
- Renders glyphs for a range of characters (ASCII ' ' to '~').
|
|
13
|
-
- Packs these glyphs into a single large texture atlas in memory.
|
|
14
|
-
- Calculates and stores metadata for each glyph (size, bearing,
|
|
15
|
-
advance, and UV coordinates within the atlas).
|
|
16
|
-
- Generates a single OpenGL texture for the atlas.
|
|
17
|
-
|
|
18
|
-
2. _Text class (exported as Text):
|
|
19
|
-
- Manages multiple fonts by creating and storing FontAtlas objects.
|
|
20
|
-
- Provides a `render_dynamic_text` method to draw text strings.
|
|
21
|
-
- `_build_instances`: For a given string, this method generates a
|
|
22
|
-
list of vertex attributes for each character. Each character is
|
|
23
|
-
represented as a single point with attributes for position, UVs,
|
|
24
|
-
and size.
|
|
25
|
-
- `render_dynamic_text`: This method sends the generated instance
|
|
26
|
-
data to the GPU and draws it using GL_POINTS.
|
|
27
|
-
|
|
28
|
-
3. Shaders:
|
|
29
|
-
- Vertex Shader: A simple pass-through shader that sends point
|
|
30
|
-
data to the geometry shader.
|
|
31
|
-
- Geometry Shader: Receives points and generates a textured quad
|
|
32
|
-
for each character on the fly.
|
|
33
|
-
- Fragment Shader: Samples the font atlas texture to color the
|
|
34
|
-
quad, effectively drawing the character.
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
from typing import Any, Dict, List
|
|
38
|
-
|
|
39
|
-
import freetype
|
|
40
|
-
import numpy as np
|
|
41
|
-
import OpenGL.GL as gl
|
|
42
|
-
|
|
43
|
-
from .log import logger
|
|
44
|
-
from .shader_lib import DefaultShader, ShaderLib
|
|
45
|
-
from .simple_vao import VertexData
|
|
46
|
-
from .vao_factory import VAOFactory, VAOType
|
|
47
|
-
from .vec3 import Vec3
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class FontAtlas:
|
|
51
|
-
"""
|
|
52
|
-
Manages the creation of a font texture atlas for efficient text rendering.
|
|
53
|
-
|
|
54
|
-
This class uses FreeType to render glyphs for a specified font and packs them
|
|
55
|
-
into a single texture. It also stores metadata for each glyph.
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
def __init__(self, font_path: str, font_size: int = 48, debug: bool = False):
|
|
59
|
-
"""
|
|
60
|
-
Initializes the FontAtlas.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
font_path: The file path to the font (e.g., a .ttf file).
|
|
64
|
-
font_size: The font size in pixels to be used for rendering the atlas.
|
|
65
|
-
debug: If True, saves the generated atlas as a PNG for debugging.
|
|
66
|
-
"""
|
|
67
|
-
try:
|
|
68
|
-
self.face = freetype.Face(font_path)
|
|
69
|
-
self.face.set_pixel_sizes(0, font_size)
|
|
70
|
-
self.font_size: int = font_size
|
|
71
|
-
self.glyphs: Dict[str, Dict[str, Any]] = {}
|
|
72
|
-
self.texture: int = 0
|
|
73
|
-
self.atlas_w: int = 0
|
|
74
|
-
self.atlas_h: int = 0
|
|
75
|
-
self.atlas: np.ndarray | None = None
|
|
76
|
-
self.build_atlas(debug)
|
|
77
|
-
|
|
78
|
-
except freetype.FT_Exception as e:
|
|
79
|
-
logger.error(f"{font_path} could not be loaded {e}")
|
|
80
|
-
|
|
81
|
-
def __str__(self) -> str:
|
|
82
|
-
"""Returns a string representation of the FontAtlas."""
|
|
83
|
-
return f"TextureID: {self.texture}, FontSize: {self.font_size}"
|
|
84
|
-
|
|
85
|
-
def build_atlas(self, debug: bool = False) -> None:
|
|
86
|
-
"""
|
|
87
|
-
Renders characters and packs them into a texture atlas.
|
|
88
|
-
|
|
89
|
-
This method iterates through ASCII characters 32-126, renders each one
|
|
90
|
-
using FreeType, and arranges them in a single large numpy array which
|
|
91
|
-
will later be used to create an OpenGL texture.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
debug: If True, saves the generated atlas as 'debug_atlas.png'.
|
|
95
|
-
"""
|
|
96
|
-
padding = 2 # Padding between glyphs in the atlas
|
|
97
|
-
atlas_w = 1024 # Fixed width for the atlas texture
|
|
98
|
-
x, y, row_h = 0, 0, 0
|
|
99
|
-
bitmaps_data = []
|
|
100
|
-
|
|
101
|
-
# Iterate through printable ASCII characters
|
|
102
|
-
for charcode in range(ord(" "), ord("~")):
|
|
103
|
-
self.face.load_char(chr(charcode), freetype.FT_LOAD_RENDER)
|
|
104
|
-
bmp = self.face.glyph.bitmap
|
|
105
|
-
w, h = bmp.width, bmp.rows
|
|
106
|
-
|
|
107
|
-
# Move to the next row if the current glyph doesn't fit
|
|
108
|
-
if x + w + padding > atlas_w:
|
|
109
|
-
x = 0
|
|
110
|
-
y += row_h + padding
|
|
111
|
-
row_h = 0
|
|
112
|
-
|
|
113
|
-
# Copy bitmap data as the buffer is overwritten for each glyph
|
|
114
|
-
if w > 0 and h > 0:
|
|
115
|
-
buffer_copy = np.array(bmp.buffer, dtype=np.ubyte).reshape(h, w)
|
|
116
|
-
bitmaps_data.append((buffer_copy, x, y))
|
|
117
|
-
|
|
118
|
-
# Store glyph metadata
|
|
119
|
-
self.glyphs[chr(charcode)] = {
|
|
120
|
-
"size": (w, h),
|
|
121
|
-
"bearing": (self.face.glyph.bitmap_left, self.face.glyph.bitmap_top),
|
|
122
|
-
"advance": self.face.glyph.advance.x >> 6, # Advance is in 1/64 pixels
|
|
123
|
-
"uv": (x, y, x + w, y + h), # UVs in pixel coordinates
|
|
124
|
-
}
|
|
125
|
-
x += w + padding
|
|
126
|
-
row_h = max(row_h, h)
|
|
127
|
-
|
|
128
|
-
atlas_h = y + row_h + padding
|
|
129
|
-
self.atlas_w, self.atlas_h = atlas_w, atlas_h
|
|
130
|
-
atlas = np.zeros((atlas_h, atlas_w), dtype=np.ubyte)
|
|
131
|
-
|
|
132
|
-
# Blit all the individual glyph bitmaps onto the atlas
|
|
133
|
-
for arr, dest_x, dest_y in bitmaps_data:
|
|
134
|
-
h, w = arr.shape
|
|
135
|
-
atlas[dest_y : dest_y + h, dest_x : dest_x + w] = arr
|
|
136
|
-
|
|
137
|
-
self.atlas = atlas
|
|
138
|
-
if debug:
|
|
139
|
-
from PIL import Image
|
|
140
|
-
|
|
141
|
-
img = Image.fromarray(self.atlas, mode="L")
|
|
142
|
-
img.save("debug_atlas.png")
|
|
143
|
-
print(f"Saved debug_atlas.png, size: {self.atlas.shape}")
|
|
144
|
-
|
|
145
|
-
def generate_texture(self) -> None:
|
|
146
|
-
"""Generates and configures the OpenGL texture for the font atlas."""
|
|
147
|
-
if self.atlas is None:
|
|
148
|
-
return
|
|
149
|
-
tex = gl.glGenTextures(1)
|
|
150
|
-
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
|
|
151
|
-
|
|
152
|
-
gl.glBindTexture(gl.GL_TEXTURE_2D, tex)
|
|
153
|
-
# Create a single-channel RED texture from our numpy atlas
|
|
154
|
-
gl.glTexImage2D(
|
|
155
|
-
gl.GL_TEXTURE_2D,
|
|
156
|
-
0,
|
|
157
|
-
gl.GL_RED,
|
|
158
|
-
self.atlas_w,
|
|
159
|
-
self.atlas_h,
|
|
160
|
-
0,
|
|
161
|
-
gl.GL_RED,
|
|
162
|
-
gl.GL_UNSIGNED_BYTE,
|
|
163
|
-
self.atlas,
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
|
167
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
|
168
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
|
|
169
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
|
|
170
|
-
|
|
171
|
-
# Use texture swizzling to use the RED channel as ALPHA.
|
|
172
|
-
# This allows us to color the font using a uniform in the shader,
|
|
173
|
-
# while using the glyph's intensity for transparency.
|
|
174
|
-
# We set the texture's RGB channels to 1.0, and the A channel to the
|
|
175
|
-
# value from the RED channel of the source.
|
|
176
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_SWIZZLE_R, gl.GL_ONE)
|
|
177
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_SWIZZLE_G, gl.GL_ONE)
|
|
178
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_SWIZZLE_B, gl.GL_ONE)
|
|
179
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_SWIZZLE_A, gl.GL_RED)
|
|
180
|
-
|
|
181
|
-
self.texture = tex
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
class _Text:
|
|
185
|
-
"""
|
|
186
|
-
Main class for managing and rendering text.
|
|
187
|
-
|
|
188
|
-
This class acts as a controller, loading fonts and providing methods
|
|
189
|
-
to render text strings to the screen. It is designed to be used as a
|
|
190
|
-
singleton instance.
|
|
191
|
-
"""
|
|
192
|
-
|
|
193
|
-
def __init__(self) -> None:
|
|
194
|
-
"""Initializes the Text renderer."""
|
|
195
|
-
self._fonts: Dict[str, FontAtlas] = {}
|
|
196
|
-
self._static_text: List[Any] = [] # Reserved for future use
|
|
197
|
-
|
|
198
|
-
def add_font(self, name: str, font_file: str, size: int) -> None:
|
|
199
|
-
"""
|
|
200
|
-
Loads a font and makes it available for rendering.
|
|
201
|
-
|
|
202
|
-
Args:
|
|
203
|
-
name: A unique name to identify this font (e.g., "main_font").
|
|
204
|
-
font_file: The path to the font file.
|
|
205
|
-
size: The font size in pixels.
|
|
206
|
-
"""
|
|
207
|
-
if not hasattr(self, "vao"):
|
|
208
|
-
self.vao = VAOFactory.create_vao(VAOType.SIMPLE, gl.GL_POINTS)
|
|
209
|
-
font = FontAtlas(font_file, size)
|
|
210
|
-
font.generate_texture()
|
|
211
|
-
print(f"Font '{name}' added with texture ID: {font.texture}")
|
|
212
|
-
self._fonts[name] = font
|
|
213
|
-
|
|
214
|
-
def set_screen_size(self, w: int, h: int) -> None:
|
|
215
|
-
"""
|
|
216
|
-
Sets the screen dimensions for the text shader.
|
|
217
|
-
|
|
218
|
-
This should be called whenever the window is resized.
|
|
219
|
-
|
|
220
|
-
Args:
|
|
221
|
-
w: The width of the screen in pixels.
|
|
222
|
-
h: The height of the screen in pixels.
|
|
223
|
-
"""
|
|
224
|
-
ShaderLib.use(DefaultShader.TEXT)
|
|
225
|
-
ShaderLib.set_uniform("textureID", 0)
|
|
226
|
-
ShaderLib.set_uniform("screenSize", w, h)
|
|
227
|
-
ShaderLib.set_uniform("fontSize", 1.0)
|
|
228
|
-
ShaderLib.set_uniform("textColor", 1.0, 1.0, 1.0, 1.0)
|
|
229
|
-
|
|
230
|
-
def render_text(
|
|
231
|
-
self, font: str, x: int, y: int, text: str, colour: Vec3 = Vec3(1, 1, 1)
|
|
232
|
-
) -> None:
|
|
233
|
-
"""
|
|
234
|
-
Renders a string of text to the screen.
|
|
235
|
-
|
|
236
|
-
Args:
|
|
237
|
-
font: The name of the font to use (previously added with add_font).
|
|
238
|
-
x: The x-coordinate of the starting position (baseline).
|
|
239
|
-
y: The y-coordinate of the starting position (baseline).
|
|
240
|
-
text: The string of text to render.
|
|
241
|
-
colour: The color of the text as a Vec4.
|
|
242
|
-
"""
|
|
243
|
-
render_data = self._build_instances(font, text, x, y)
|
|
244
|
-
if not render_data:
|
|
245
|
-
return
|
|
246
|
-
|
|
247
|
-
buffer_data = np.array(render_data, dtype=np.float32)
|
|
248
|
-
atlas = self._fonts[font]
|
|
249
|
-
|
|
250
|
-
# Enable blending for transparency
|
|
251
|
-
gl.glEnable(gl.GL_BLEND)
|
|
252
|
-
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
|
|
253
|
-
|
|
254
|
-
# Ensure text is rendered filled and restore state afterwards.
|
|
255
|
-
polygon_mode = gl.glGetIntegerv(gl.GL_POLYGON_MODE)[0]
|
|
256
|
-
if polygon_mode != gl.GL_FILL:
|
|
257
|
-
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
|
|
258
|
-
|
|
259
|
-
# Disable depth testing to ensure text is always drawn on top
|
|
260
|
-
depth_test_enabled = gl.glIsEnabled(gl.GL_DEPTH_TEST)
|
|
261
|
-
if depth_test_enabled:
|
|
262
|
-
gl.glDisable(gl.GL_DEPTH_TEST)
|
|
263
|
-
|
|
264
|
-
with self.vao as vao:
|
|
265
|
-
data = VertexData(data=buffer_data, size=buffer_data.nbytes)
|
|
266
|
-
stride = 32 # 8 floats * 4 bytes
|
|
267
|
-
vao.set_data(data)
|
|
268
|
-
# Vertex Attributes:
|
|
269
|
-
# 0: vec2 a_position (screen position of the glyph)
|
|
270
|
-
# 1: vec4 a_uvRect (u0, v0, u1, v1)
|
|
271
|
-
# 2: vec2 a_size (width, height of the glyph quad)
|
|
272
|
-
vao.set_vertex_attribute_pointer(0, 2, gl.GL_FLOAT, stride, 0)
|
|
273
|
-
vao.set_vertex_attribute_pointer(1, 4, gl.GL_FLOAT, stride, 8)
|
|
274
|
-
vao.set_vertex_attribute_pointer(2, 2, gl.GL_FLOAT, stride, 24)
|
|
275
|
-
|
|
276
|
-
gl.glActiveTexture(gl.GL_TEXTURE0)
|
|
277
|
-
gl.glBindTexture(gl.GL_TEXTURE_2D, atlas.texture)
|
|
278
|
-
ShaderLib.use(DefaultShader.TEXT)
|
|
279
|
-
ShaderLib.set_uniform("textColor", colour.x, colour.y, colour.z, 1.0)
|
|
280
|
-
# We are drawing one point per character
|
|
281
|
-
vao.set_num_indices(len(render_data) // 8)
|
|
282
|
-
vao.draw()
|
|
283
|
-
|
|
284
|
-
# Restore OpenGL state
|
|
285
|
-
gl.glDisable(gl.GL_BLEND)
|
|
286
|
-
if depth_test_enabled:
|
|
287
|
-
gl.glEnable(gl.GL_DEPTH_TEST)
|
|
288
|
-
if polygon_mode != gl.GL_FILL:
|
|
289
|
-
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, polygon_mode)
|
|
290
|
-
|
|
291
|
-
def _build_instances(
|
|
292
|
-
self, font: str, text: str, start_x: int, start_y: int
|
|
293
|
-
) -> List[float]:
|
|
294
|
-
"""
|
|
295
|
-
Generates vertex attribute data for each character in a string.
|
|
296
|
-
|
|
297
|
-
This data is sent to the GPU as a single buffer. The geometry shader
|
|
298
|
-
then uses this data to construct a quad for each character.
|
|
299
|
-
|
|
300
|
-
Args:
|
|
301
|
-
font: The name of the font to use.
|
|
302
|
-
text: The string to process.
|
|
303
|
-
start_x: The initial x-coordinate for the text baseline.
|
|
304
|
-
start_y: The initial y-coordinate for the text baseline.
|
|
305
|
-
|
|
306
|
-
Returns:
|
|
307
|
-
A list of floats representing the packed vertex data for all characters.
|
|
308
|
-
"""
|
|
309
|
-
inst = []
|
|
310
|
-
atlas = self._fonts.get(font)
|
|
311
|
-
if atlas:
|
|
312
|
-
x, y = float(start_x), float(start_y) # Use floats for positioning
|
|
313
|
-
|
|
314
|
-
for ch in text:
|
|
315
|
-
if ch not in atlas.glyphs:
|
|
316
|
-
continue
|
|
317
|
-
g = atlas.glyphs[ch]
|
|
318
|
-
w, h = g["size"]
|
|
319
|
-
adv = g["advance"]
|
|
320
|
-
bearing_x, bearing_y = g["bearing"]
|
|
321
|
-
|
|
322
|
-
# UV coordinates from atlas (in pixels)
|
|
323
|
-
u0_px, v0_px, u1_px, v1_px = g["uv"]
|
|
324
|
-
|
|
325
|
-
# Normalize UVs to the range [0, 1]
|
|
326
|
-
u0 = u0_px / atlas.atlas_w
|
|
327
|
-
v0 = v0_px / atlas.atlas_h
|
|
328
|
-
u1 = u1_px / atlas.atlas_w
|
|
329
|
-
v1 = v1_px / atlas.atlas_h
|
|
330
|
-
|
|
331
|
-
# Calculate the screen position for the top-left corner of the quad.
|
|
332
|
-
# FreeType's origin is at the baseline, with +y going up.
|
|
333
|
-
# Screen coordinates usually have +y going down, so we adjust.
|
|
334
|
-
pos_x = x + bearing_x
|
|
335
|
-
pos_y = y - bearing_y
|
|
336
|
-
|
|
337
|
-
# Each character is defined by 8 floats:
|
|
338
|
-
# pos_x, pos_y, u0, v0, u1, v1, w, h
|
|
339
|
-
inst.extend([pos_x, pos_y, u0, v0, u1, v1, float(w), float(h)])
|
|
340
|
-
# Advance the cursor for the next character
|
|
341
|
-
x += adv
|
|
342
|
-
return inst
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
# Create a singleton instance of the Text class for global use.
|
|
346
|
-
Text = _Text()
|
ncca/ngl/texture.py
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import OpenGL.GL as gl
|
|
4
|
-
|
|
5
|
-
from .image import Image
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Texture:
|
|
9
|
-
"""A texture class to load and create OpenGL textures."""
|
|
10
|
-
|
|
11
|
-
def __init__(self, filename: str = None) -> None:
|
|
12
|
-
self._image = Image(filename)
|
|
13
|
-
self._texture_id = 0
|
|
14
|
-
self._multi_texture_id = 0
|
|
15
|
-
|
|
16
|
-
@property
|
|
17
|
-
def width(self) -> int:
|
|
18
|
-
return self._image.width
|
|
19
|
-
|
|
20
|
-
@property
|
|
21
|
-
def height(self) -> int:
|
|
22
|
-
return self._image.height
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def format(self) -> int:
|
|
26
|
-
if self._image.mode:
|
|
27
|
-
if self._image.mode.value == "RGB":
|
|
28
|
-
return gl.GL_RGB
|
|
29
|
-
elif self._image.mode.value == "RGBA":
|
|
30
|
-
return gl.GL_RGBA
|
|
31
|
-
elif self._image.mode.value == "L":
|
|
32
|
-
return gl.GL_RED
|
|
33
|
-
return 0
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def internal_format(self) -> int:
|
|
37
|
-
if self._image.mode:
|
|
38
|
-
if self._image.mode.value == "RGB":
|
|
39
|
-
return gl.GL_RGB8
|
|
40
|
-
elif self._image.mode.value == "RGBA":
|
|
41
|
-
return gl.GL_RGBA8
|
|
42
|
-
elif self._image.mode.value == "L":
|
|
43
|
-
return gl.GL_R8
|
|
44
|
-
return 0
|
|
45
|
-
|
|
46
|
-
def load_image(self, filename: str) -> bool:
|
|
47
|
-
return self._image.load(filename)
|
|
48
|
-
|
|
49
|
-
def get_pixels(self) -> bytes:
|
|
50
|
-
return self._image.get_pixels().tobytes()
|
|
51
|
-
|
|
52
|
-
def set_texture_gl(self) -> int:
|
|
53
|
-
if self._image.width > 0 and self._image.height > 0:
|
|
54
|
-
self._texture_id = gl.glGenTextures(1)
|
|
55
|
-
gl.glActiveTexture(gl.GL_TEXTURE0 + self._multi_texture_id)
|
|
56
|
-
gl.glBindTexture(gl.GL_TEXTURE_2D, self._texture_id)
|
|
57
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
|
|
58
|
-
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
|
|
59
|
-
gl.glTexImage2D(
|
|
60
|
-
gl.GL_TEXTURE_2D,
|
|
61
|
-
0,
|
|
62
|
-
self.internal_format,
|
|
63
|
-
self.width,
|
|
64
|
-
self.height,
|
|
65
|
-
0,
|
|
66
|
-
self.format,
|
|
67
|
-
gl.GL_UNSIGNED_BYTE,
|
|
68
|
-
self.get_pixels(),
|
|
69
|
-
)
|
|
70
|
-
gl.glGenerateMipmap(gl.GL_TEXTURE_2D)
|
|
71
|
-
return self._texture_id
|
|
72
|
-
return 0
|
|
73
|
-
|
|
74
|
-
def set_multi_texture(self, id: int) -> None:
|
|
75
|
-
self._multi_texture_id = id
|
ncca/ngl/transform.py
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Class to represent a transform using translate, rotate and scale,
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from .mat4 import Mat4
|
|
6
|
-
from .vec3 import Vec3
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TransformRotationOrder(Exception):
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Transform:
|
|
14
|
-
rot_order = {
|
|
15
|
-
"xyz": "rz@ry@rx",
|
|
16
|
-
"yzx": "rx@rz@ry",
|
|
17
|
-
"zxy": "ry@rx@rz",
|
|
18
|
-
"xzy": "ry@rz@rx",
|
|
19
|
-
"yxz": "rz@rx@ry",
|
|
20
|
-
"zyx": "rx@ry@rz",
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
def __init__(self):
|
|
24
|
-
self.position = Vec3(0.0, 0.0, 0.0)
|
|
25
|
-
self.rotation = Vec3(0.0, 0.0, 0.0)
|
|
26
|
-
self.scale = Vec3(1.0, 1.0, 1.0)
|
|
27
|
-
self.matrix = Mat4()
|
|
28
|
-
self.need_recalc = True
|
|
29
|
-
self.order = "xyz"
|
|
30
|
-
|
|
31
|
-
def _set_value(self, args):
|
|
32
|
-
v = Vec3()
|
|
33
|
-
self.need_recalc = True
|
|
34
|
-
if len(args) == 1: # one argument
|
|
35
|
-
if isinstance(args[0], (list, tuple)):
|
|
36
|
-
v.x = args[0][0]
|
|
37
|
-
v.y = args[0][1]
|
|
38
|
-
v.z = args[0][2]
|
|
39
|
-
else: # try vec types
|
|
40
|
-
v.x = args[0].x
|
|
41
|
-
v.y = args[0].y
|
|
42
|
-
v.z = args[0].z
|
|
43
|
-
return v
|
|
44
|
-
elif len(args) == 3: # 3 as x,y,z
|
|
45
|
-
v.x = float(args[0])
|
|
46
|
-
v.y = float(args[1])
|
|
47
|
-
v.z = float(args[2])
|
|
48
|
-
return v
|
|
49
|
-
else:
|
|
50
|
-
raise ValueError
|
|
51
|
-
|
|
52
|
-
def reset(self):
|
|
53
|
-
self.position = Vec3()
|
|
54
|
-
self.rotation = Vec3()
|
|
55
|
-
self.scale = Vec3(1, 1, 1)
|
|
56
|
-
self.order = "xyz"
|
|
57
|
-
self.need_recalc = True
|
|
58
|
-
|
|
59
|
-
def set_position(self, *args):
|
|
60
|
-
"set position attrib using either x,y,z or vec types"
|
|
61
|
-
self.position = self._set_value(args)
|
|
62
|
-
|
|
63
|
-
def set_rotation(self, *args):
|
|
64
|
-
"set rotation attrib using either x,y,z or vec types"
|
|
65
|
-
self.rotation = self._set_value(args)
|
|
66
|
-
|
|
67
|
-
def set_scale(self, *args):
|
|
68
|
-
"set scale attrib using either x,y,z or vec types"
|
|
69
|
-
self.scale = self._set_value(args)
|
|
70
|
-
|
|
71
|
-
def set_order(self, order):
|
|
72
|
-
"set rotation order from string e.g xyz or zyx"
|
|
73
|
-
if order not in self.rot_order:
|
|
74
|
-
raise TransformRotationOrder
|
|
75
|
-
self.order = order
|
|
76
|
-
self.need_recalc = True
|
|
77
|
-
|
|
78
|
-
def get_matrix(self):
|
|
79
|
-
"return a transform matrix based on rotation order"
|
|
80
|
-
if self.need_recalc is True:
|
|
81
|
-
scale = Mat4.scale(self.scale.x, self.scale.y, self.scale.z)
|
|
82
|
-
rx = Mat4.rotate_x(self.rotation.x) # noqa: F841
|
|
83
|
-
ry = Mat4.rotate_y(self.rotation.y) # noqa: F841
|
|
84
|
-
rz = Mat4.rotate_z(self.rotation.z) # noqa: F841
|
|
85
|
-
rotation_scale = eval(self.rot_order.get(self.order)) @ scale
|
|
86
|
-
self.matrix = rotation_scale
|
|
87
|
-
self.matrix.m[3][0] = self.position.x
|
|
88
|
-
self.matrix.m[3][1] = self.position.y
|
|
89
|
-
self.matrix.m[3][2] = self.position.z
|
|
90
|
-
self.matrix.m[3][3] = 1.0
|
|
91
|
-
self.need_recalc = False
|
|
92
|
-
return self.matrix
|
|
93
|
-
|
|
94
|
-
def __str__(self):
|
|
95
|
-
return f"pos {self.position}\nrot {self.rotation}\nscale {self.scale}"
|