ncca-ngl 0.1.0__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/PrimData/pack_arrays.py +20 -0
- ncca/ngl/__init__.py +100 -0
- ncca/ngl/abstract_vao.py +89 -0
- ncca/ngl/base_mesh.py +170 -0
- ncca/ngl/base_mesh.pyi +11 -0
- ncca/ngl/bbox.py +224 -0
- ncca/ngl/bezier_curve.py +75 -0
- ncca/ngl/first_person_camera.py +174 -0
- ncca/ngl/image.py +94 -0
- ncca/ngl/log.py +44 -0
- ncca/ngl/mat2.py +128 -0
- ncca/ngl/mat3.py +466 -0
- ncca/ngl/mat4.py +456 -0
- ncca/ngl/multi_buffer_vao.py +49 -0
- ncca/ngl/obj.py +416 -0
- ncca/ngl/plane.py +47 -0
- ncca/ngl/primitives.py +706 -0
- ncca/ngl/pyside_event_handling_mixin.py +318 -0
- ncca/ngl/quaternion.py +112 -0
- ncca/ngl/random.py +167 -0
- ncca/ngl/shader.py +229 -0
- ncca/ngl/shader_lib.py +536 -0
- ncca/ngl/shader_program.py +816 -0
- ncca/ngl/simple_index_vao.py +65 -0
- ncca/ngl/simple_vao.py +42 -0
- ncca/ngl/text.py +346 -0
- ncca/ngl/texture.py +75 -0
- ncca/ngl/transform.py +95 -0
- ncca/ngl/util.py +128 -0
- ncca/ngl/vao_factory.py +34 -0
- ncca/ngl/vec2.py +350 -0
- ncca/ngl/vec2_array.py +106 -0
- ncca/ngl/vec3.py +401 -0
- ncca/ngl/vec3_array.py +110 -0
- ncca/ngl/vec4.py +229 -0
- ncca/ngl/vec4_array.py +106 -0
- ncca_ngl-0.1.0.dist-info/METADATA +23 -0
- ncca_ngl-0.1.0.dist-info/RECORD +41 -0
- ncca_ngl-0.1.0.dist-info/WHEEL +5 -0
- ncca_ngl-0.1.0.dist-info/licenses/LICENSE.txt +7 -0
- ncca_ngl-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Event Handling Mixin for PyNGL Applications
|
|
4
|
+
|
|
5
|
+
This module provides a reusable mixin class that implements common event handling
|
|
6
|
+
patterns used across PyNGL applications, including mouse-based camera control,
|
|
7
|
+
keyboard shortcuts, and wheel-based zooming.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
class MyWindow(EventHandlingMixin, QOpenGLWindow):
|
|
11
|
+
def __init__(self):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.setup_event_handling() # Initialize mixin attributes
|
|
14
|
+
# ... rest of your initialization
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Protocol
|
|
18
|
+
|
|
19
|
+
import OpenGL.GL as gl
|
|
20
|
+
from PySide6.QtCore import Qt
|
|
21
|
+
|
|
22
|
+
from .vec3 import Vec3
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class EventHandlingTarget(Protocol):
|
|
26
|
+
"""
|
|
27
|
+
Protocol defining the interface that classes using EventHandlingMixin must implement.
|
|
28
|
+
|
|
29
|
+
This ensures that the mixin has access to the necessary methods and attributes.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def update(self) -> None:
|
|
33
|
+
"""Trigger a redraw of the window."""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
def close(self) -> None:
|
|
37
|
+
"""Close the window."""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PySideEventHandlingMixin:
|
|
42
|
+
"""
|
|
43
|
+
Mixin class providing standard event handling for PyNGL applications.
|
|
44
|
+
|
|
45
|
+
This mixin provides common functionality for:
|
|
46
|
+
- Mouse-based camera control (rotation with left button, translation with right button)
|
|
47
|
+
- Keyboard shortcuts (wireframe/solid mode, reset, escape)
|
|
48
|
+
- Mouse wheel zooming
|
|
49
|
+
|
|
50
|
+
Classes using this mixin should call setup_event_handling() in their __init__ method.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# Default sensitivity values
|
|
54
|
+
DEFAULT_ROTATION_SENSITIVITY = 0.5
|
|
55
|
+
DEFAULT_TRANSLATION_SENSITIVITY = 0.01
|
|
56
|
+
DEFAULT_ZOOM_SENSITIVITY = 0.1
|
|
57
|
+
|
|
58
|
+
def setup_event_handling(
|
|
59
|
+
self,
|
|
60
|
+
rotation_sensitivity: float = DEFAULT_ROTATION_SENSITIVITY,
|
|
61
|
+
translation_sensitivity: float = DEFAULT_TRANSLATION_SENSITIVITY,
|
|
62
|
+
zoom_sensitivity: float = DEFAULT_ZOOM_SENSITIVITY,
|
|
63
|
+
initial_position: Vec3 = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Initialize event handling attributes.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
rotation_sensitivity: Mouse sensitivity for rotation (default: 0.5)
|
|
70
|
+
translation_sensitivity: Mouse sensitivity for translation (default: 0.01)
|
|
71
|
+
zoom_sensitivity: Mouse wheel sensitivity for zooming (default: 0.1)
|
|
72
|
+
initial_position: Initial model position (default: Vec3(0,0,0))
|
|
73
|
+
"""
|
|
74
|
+
# Mouse control state
|
|
75
|
+
self.rotate: bool = False
|
|
76
|
+
self.translate: bool = False
|
|
77
|
+
|
|
78
|
+
# Rotation state
|
|
79
|
+
self.spin_x_face: int = 0
|
|
80
|
+
self.spin_y_face: int = 0
|
|
81
|
+
|
|
82
|
+
# Mouse position tracking for rotation
|
|
83
|
+
self.original_x_rotation: float = 0.0
|
|
84
|
+
self.original_y_rotation: float = 0.0
|
|
85
|
+
|
|
86
|
+
# Mouse position tracking for translation
|
|
87
|
+
self.original_x_pos: float = 0.0
|
|
88
|
+
self.original_y_pos: float = 0.0
|
|
89
|
+
|
|
90
|
+
# Model position and sensitivity settings
|
|
91
|
+
self.model_position: Vec3 = initial_position or Vec3(0, 0, 0)
|
|
92
|
+
self.rotation_sensitivity: float = rotation_sensitivity
|
|
93
|
+
self.translation_sensitivity: float = translation_sensitivity
|
|
94
|
+
self.zoom_sensitivity: float = zoom_sensitivity
|
|
95
|
+
|
|
96
|
+
self.INCREMENT = self.translation_sensitivity
|
|
97
|
+
self.ZOOM = self.zoom_sensitivity
|
|
98
|
+
|
|
99
|
+
# def sync_legacy_attributes(self) -> None:
|
|
100
|
+
# """
|
|
101
|
+
# Synchronize legacy attribute names with new ones.
|
|
102
|
+
# Call this if you modify the legacy attributes directly.
|
|
103
|
+
# """
|
|
104
|
+
# self.spin_x_face = self.spinXFace
|
|
105
|
+
# self.spin_y_face = self.spinYFace
|
|
106
|
+
# self.model_position = self.modelPos
|
|
107
|
+
# self.original_x_rotation = self.origX
|
|
108
|
+
# self.original_y_rotation = self.origY
|
|
109
|
+
# self.original_x_pos = self.origXPos
|
|
110
|
+
# self.original_y_pos = self.origYPos
|
|
111
|
+
# self.translation_sensitivity = self.INCREMENT
|
|
112
|
+
# self.zoom_sensitivity = self.ZOOM
|
|
113
|
+
|
|
114
|
+
def reset_camera(self) -> None:
|
|
115
|
+
"""Reset camera rotation and model position to defaults."""
|
|
116
|
+
self.spin_x_face = 0
|
|
117
|
+
self.spin_y_face = 0
|
|
118
|
+
self.model_position.set(0, 0, 0)
|
|
119
|
+
|
|
120
|
+
# # Sync legacy attributes
|
|
121
|
+
# self.spinXFace = 0
|
|
122
|
+
# self.spinYFace = 0
|
|
123
|
+
# self.modelPos.set(0, 0, 0)
|
|
124
|
+
|
|
125
|
+
def keyPressEvent(self, event) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Handle keyboard press events with common shortcuts.
|
|
128
|
+
|
|
129
|
+
Shortcuts:
|
|
130
|
+
- Escape: Close application
|
|
131
|
+
- W: Switch to wireframe mode
|
|
132
|
+
- S: Switch to solid fill mode
|
|
133
|
+
- Space: Reset camera rotation and position
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
event: The QKeyEvent object
|
|
137
|
+
"""
|
|
138
|
+
key = event.key()
|
|
139
|
+
|
|
140
|
+
if key == Qt.Key_Escape:
|
|
141
|
+
self.close()
|
|
142
|
+
elif key == Qt.Key_W:
|
|
143
|
+
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)
|
|
144
|
+
elif key == Qt.Key_S:
|
|
145
|
+
gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
|
|
146
|
+
elif key == Qt.Key_Space:
|
|
147
|
+
self.reset_camera()
|
|
148
|
+
else:
|
|
149
|
+
# Let subclasses handle other keys
|
|
150
|
+
super().keyPressEvent(event)
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
self.update()
|
|
154
|
+
|
|
155
|
+
def mouseMoveEvent(self, event) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Handle mouse movement for camera control.
|
|
158
|
+
|
|
159
|
+
- Left button: Rotate the scene
|
|
160
|
+
- Right button: Translate (pan) the scene
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
event: The QMouseEvent object
|
|
164
|
+
"""
|
|
165
|
+
position = event.position()
|
|
166
|
+
|
|
167
|
+
# Handle rotation with left mouse button
|
|
168
|
+
if self.rotate and event.buttons() == Qt.LeftButton:
|
|
169
|
+
diff_x = position.x() - self.original_x_rotation
|
|
170
|
+
diff_y = position.y() - self.original_y_rotation
|
|
171
|
+
|
|
172
|
+
self.spin_x_face += int(self.rotation_sensitivity * diff_y)
|
|
173
|
+
self.spin_y_face += int(self.rotation_sensitivity * diff_x)
|
|
174
|
+
|
|
175
|
+
self.original_x_rotation = position.x()
|
|
176
|
+
self.original_y_rotation = position.y()
|
|
177
|
+
|
|
178
|
+
# # Sync legacy attributes
|
|
179
|
+
# self.spinXFace = self.spin_x_face
|
|
180
|
+
# self.spinYFace = self.spin_y_face
|
|
181
|
+
# self.origX = self.original_x_rotation
|
|
182
|
+
# self.origY = self.original_y_rotation
|
|
183
|
+
|
|
184
|
+
self.update()
|
|
185
|
+
|
|
186
|
+
# Handle translation with right mouse button
|
|
187
|
+
elif self.translate and event.buttons() == Qt.RightButton:
|
|
188
|
+
diff_x = int(position.x() - self.original_x_pos)
|
|
189
|
+
diff_y = int(position.y() - self.original_y_pos)
|
|
190
|
+
|
|
191
|
+
self.original_x_pos = position.x()
|
|
192
|
+
self.original_y_pos = position.y()
|
|
193
|
+
|
|
194
|
+
self.model_position.x += self.translation_sensitivity * diff_x
|
|
195
|
+
self.model_position.y -= self.translation_sensitivity * diff_y
|
|
196
|
+
|
|
197
|
+
# # Sync legacy attributes
|
|
198
|
+
# self.origXPos = self.original_x_pos
|
|
199
|
+
# self.origYPos = self.original_y_pos
|
|
200
|
+
# self.modelPos = self.model_position
|
|
201
|
+
|
|
202
|
+
self.update()
|
|
203
|
+
|
|
204
|
+
def mousePressEvent(self, event) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Handle mouse button press events to initiate rotation or translation.
|
|
207
|
+
|
|
208
|
+
- Left button: Start rotation mode
|
|
209
|
+
- Right button: Start translation mode
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
event: The QMouseEvent object
|
|
213
|
+
"""
|
|
214
|
+
position = event.position()
|
|
215
|
+
|
|
216
|
+
if event.button() == Qt.LeftButton:
|
|
217
|
+
self.original_x_rotation = position.x()
|
|
218
|
+
self.original_y_rotation = position.y()
|
|
219
|
+
self.rotate = True
|
|
220
|
+
|
|
221
|
+
# # Sync legacy attributes
|
|
222
|
+
# self.origX = self.original_x_rotation
|
|
223
|
+
# self.origY = self.original_y_rotation
|
|
224
|
+
|
|
225
|
+
elif event.button() == Qt.RightButton:
|
|
226
|
+
self.original_x_pos = position.x()
|
|
227
|
+
self.original_y_pos = position.y()
|
|
228
|
+
self.translate = True
|
|
229
|
+
|
|
230
|
+
# # Sync legacy attributes
|
|
231
|
+
# self.origXPos = self.original_x_pos
|
|
232
|
+
# self.origYPos = self.original_y_pos
|
|
233
|
+
|
|
234
|
+
def mouseReleaseEvent(self, event) -> None:
|
|
235
|
+
"""
|
|
236
|
+
Handle mouse button release events to stop rotation or translation.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
event: The QMouseEvent object
|
|
240
|
+
"""
|
|
241
|
+
if event.button() == Qt.LeftButton:
|
|
242
|
+
self.rotate = False
|
|
243
|
+
elif event.button() == Qt.RightButton:
|
|
244
|
+
self.translate = False
|
|
245
|
+
|
|
246
|
+
def wheelEvent(self, event) -> None:
|
|
247
|
+
"""
|
|
248
|
+
Handle mouse wheel events for zooming.
|
|
249
|
+
|
|
250
|
+
Zooming is performed by adjusting the Z coordinate of the model position.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
event: The QWheelEvent object
|
|
254
|
+
"""
|
|
255
|
+
angle_delta = event.angleDelta()
|
|
256
|
+
|
|
257
|
+
# Handle both x and y wheel movement (some mice/trackpads use different axes)
|
|
258
|
+
delta = angle_delta.y() if angle_delta.y() != 0 else angle_delta.x()
|
|
259
|
+
|
|
260
|
+
if delta > 0:
|
|
261
|
+
self.model_position.z += self.zoom_sensitivity
|
|
262
|
+
elif delta < 0:
|
|
263
|
+
self.model_position.z -= self.zoom_sensitivity
|
|
264
|
+
|
|
265
|
+
# # Sync legacy attributes
|
|
266
|
+
# self.modelPos = self.model_position
|
|
267
|
+
|
|
268
|
+
self.update()
|
|
269
|
+
|
|
270
|
+
def get_camera_state(self) -> dict:
|
|
271
|
+
"""
|
|
272
|
+
Get the current camera state for serialization or debugging.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Dictionary containing current camera state
|
|
276
|
+
"""
|
|
277
|
+
return {
|
|
278
|
+
"spin_x_face": self.spin_x_face,
|
|
279
|
+
"spin_y_face": self.spin_y_face,
|
|
280
|
+
"model_position": [
|
|
281
|
+
self.model_position.x,
|
|
282
|
+
self.model_position.y,
|
|
283
|
+
self.model_position.z,
|
|
284
|
+
],
|
|
285
|
+
"rotation_sensitivity": self.rotation_sensitivity,
|
|
286
|
+
"translation_sensitivity": self.translation_sensitivity,
|
|
287
|
+
"zoom_sensitivity": self.zoom_sensitivity,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
def set_camera_state(self, state: dict) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Restore camera state from a dictionary.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
state: Dictionary containing camera state (from get_camera_state())
|
|
296
|
+
"""
|
|
297
|
+
self.spin_x_face = state.get("spin_x_face", 0)
|
|
298
|
+
self.spin_y_face = state.get("spin_y_face", 0)
|
|
299
|
+
|
|
300
|
+
pos = state.get("model_position", [0, 0, 0])
|
|
301
|
+
# Handle cases where pos might have fewer than 3 elements
|
|
302
|
+
x = pos[0] if len(pos) > 0 else 0
|
|
303
|
+
y = pos[1] if len(pos) > 1 else 0
|
|
304
|
+
z = pos[2] if len(pos) > 2 else 0
|
|
305
|
+
self.model_position.set(x, y, z)
|
|
306
|
+
|
|
307
|
+
self.rotation_sensitivity = state.get(
|
|
308
|
+
"rotation_sensitivity", self.DEFAULT_ROTATION_SENSITIVITY
|
|
309
|
+
)
|
|
310
|
+
self.translation_sensitivity = state.get(
|
|
311
|
+
"translation_sensitivity", self.DEFAULT_TRANSLATION_SENSITIVITY
|
|
312
|
+
)
|
|
313
|
+
self.zoom_sensitivity = state.get(
|
|
314
|
+
"zoom_sensitivity", self.DEFAULT_ZOOM_SENSITIVITY
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# # Sync legacy attributes
|
|
318
|
+
# self.sync_legacy_attributes()
|
ncca/ngl/quaternion.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A simple Quaternion class for use in NCCA Python
|
|
3
|
+
Attributes:
|
|
4
|
+
s (float): The scalar part of the quaternion.
|
|
5
|
+
x (float): The x-coordinate of the vector part of the quaternion.
|
|
6
|
+
y (float): The y-coordinate of the vector part of the quaternion.
|
|
7
|
+
z (float): The z-coordinate of the vector part of the quaternion.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import math
|
|
11
|
+
|
|
12
|
+
from .mat4 import Mat4
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Quaternion:
|
|
16
|
+
__slots__ = ["s", "x", "y", "z"] # fix the attributes to s x,y,z
|
|
17
|
+
|
|
18
|
+
def __init__(self, s: float = 1.0, x: float = 0, y: float = 0, z: float = 0):
|
|
19
|
+
"""
|
|
20
|
+
Initializes a new instance of the Quaternion class.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
s (float): The scalar part of the quaternion.
|
|
24
|
+
x (float): The x-coordinate of the vector part of the quaternion.
|
|
25
|
+
y (float): The y-coordinate of the vector part of the quaternion.
|
|
26
|
+
z (float): The z-coordinate of the vector part of the quaternion.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
self.s = float(s)
|
|
30
|
+
self.x = float(x)
|
|
31
|
+
self.y = float(y)
|
|
32
|
+
self.z = float(z)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def from_mat4(mat: "Mat4") -> "Quaternion":
|
|
36
|
+
"""
|
|
37
|
+
Creates a new Quaternion from a Mat4 rotation matrix.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
mat (Mat4): The rotation matrix to convert.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Quaternion: A new Quaternion representing the rotation matrix.
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
matrix = mat.get_matrix()
|
|
47
|
+
T = 1.0 + matrix[0] + matrix[5] + matrix[10]
|
|
48
|
+
if T > 0.00000001: # to avoid large distortions!
|
|
49
|
+
scale = math.sqrt(T) * 2.0
|
|
50
|
+
x = (matrix[6] - matrix[9]) / scale
|
|
51
|
+
y = (matrix[8] - matrix[2]) / scale
|
|
52
|
+
z = (matrix[1] - matrix[4]) / scale
|
|
53
|
+
s = 0.25 * scale
|
|
54
|
+
elif matrix[0] > matrix[5] and matrix[0] > matrix[10]:
|
|
55
|
+
scale = math.sqrt(1.0 + matrix[0] - matrix[5] - matrix[10]) * 2.0
|
|
56
|
+
x = 0.25 * scale
|
|
57
|
+
y = (matrix[4] + matrix[1]) / scale
|
|
58
|
+
z = (matrix[2] + matrix[8]) / scale
|
|
59
|
+
s = (matrix[6] - matrix[9]) / scale
|
|
60
|
+
|
|
61
|
+
elif matrix[5] > matrix[10]:
|
|
62
|
+
scale = math.sqrt(1.0 + matrix[5] - matrix[0] - matrix[10]) * 2.0
|
|
63
|
+
x = (matrix[4] + matrix[1]) / scale
|
|
64
|
+
y = 0.25 * scale
|
|
65
|
+
z = (matrix[9] + matrix[6]) / scale
|
|
66
|
+
s = (matrix[8] - matrix[2]) / scale
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
scale = math.sqrt(1.0 + matrix[10] - matrix[0] - matrix[5]) * 2.0
|
|
70
|
+
x = (matrix[8] + matrix[2]) / scale
|
|
71
|
+
y = (matrix[9] + matrix[6]) / scale
|
|
72
|
+
z = 0.25 * scale
|
|
73
|
+
s = (matrix[1] - matrix[4]) / scale
|
|
74
|
+
|
|
75
|
+
return Quaternion(s, x, y, z)
|
|
76
|
+
|
|
77
|
+
def __add__(self, rhs):
|
|
78
|
+
return Quaternion(self.s + rhs.s, self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
|
|
79
|
+
|
|
80
|
+
def __iadd__(self, rhs):
|
|
81
|
+
self.s += rhs.s
|
|
82
|
+
self.x += rhs.x
|
|
83
|
+
self.y += rhs.y
|
|
84
|
+
self.z += rhs.z
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def __sub__(self, rhs):
|
|
88
|
+
return Quaternion(self.s - rhs.s, self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
|
|
89
|
+
|
|
90
|
+
def __isub__(self, rhs):
|
|
91
|
+
return self.__sub__(rhs)
|
|
92
|
+
|
|
93
|
+
def __mul__(self, rhs):
|
|
94
|
+
return Quaternion(
|
|
95
|
+
self.s * rhs.s - self.x * rhs.x - self.y * rhs.y - self.z * rhs.z,
|
|
96
|
+
self.s * rhs.x + self.x * rhs.s + self.y * rhs.z - self.z * rhs.y,
|
|
97
|
+
self.s * rhs.y - self.x * rhs.z + self.y * rhs.s + self.z * rhs.x,
|
|
98
|
+
self.s * rhs.z + self.x * rhs.y - self.y * rhs.x + self.z * rhs.s,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def __str__(self) -> str:
|
|
102
|
+
"""
|
|
103
|
+
Returns a string representation of the Quaternion.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
str: A string representation of the Quaternion.
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
return f"Quaternion({self.s}, [{self.x}, {self.y}, {self.z}])"
|
|
110
|
+
|
|
111
|
+
def __repr__(self) -> str:
|
|
112
|
+
return f"Quaternion({self.s}, [{self.x}, {self.y}, {self.z}])"
|
ncca/ngl/random.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module to replicate the functionality of the NGL C++ Random class.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from .vec2 import Vec2
|
|
9
|
+
from .vec3 import Vec3
|
|
10
|
+
from .vec4 import Vec4
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Random:
|
|
14
|
+
_float_generators = {
|
|
15
|
+
"RandomFloat": lambda: random.uniform(-1.0, 1.0),
|
|
16
|
+
"RandomPositiveFloat": lambda: random.uniform(0.0, 1.0),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_int_generators = {}
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def set_seed():
|
|
23
|
+
"""set the seed using std::time(NULL)"""
|
|
24
|
+
random.seed(int(time.time()))
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def set_seed_value(value: int):
|
|
28
|
+
"""set the seed using a param value
|
|
29
|
+
Args:
|
|
30
|
+
value (int): the seed value
|
|
31
|
+
"""
|
|
32
|
+
random.seed(value)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def get_float_from_generator_name(name: str) -> float:
|
|
36
|
+
"""gets a pre-generated float value for a genetator
|
|
37
|
+
Args:
|
|
38
|
+
name (str): the name of the generator to use for the number
|
|
39
|
+
Returns:
|
|
40
|
+
a random number created by the generator or 0 if the generator is not found
|
|
41
|
+
"""
|
|
42
|
+
if name in Random._float_generators:
|
|
43
|
+
return Random._float_generators[name]()
|
|
44
|
+
return 0.0
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def get_int_from_generator_name(name: str) -> int:
|
|
48
|
+
"""gets a pre-generated int value for a genetator
|
|
49
|
+
Args:
|
|
50
|
+
name (str): the name of the generator to use for the number
|
|
51
|
+
Returns:
|
|
52
|
+
a random number created by the generator or 0 if the generator is not found
|
|
53
|
+
"""
|
|
54
|
+
if name in Random._int_generators:
|
|
55
|
+
return Random._int_generators[name]()
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def add_int_generator(name: str, generator):
|
|
60
|
+
"""add a generator to the int generators
|
|
61
|
+
Args:
|
|
62
|
+
name (str): the name of the generator to use for the number
|
|
63
|
+
generator : the generator to add should be a callable function
|
|
64
|
+
"""
|
|
65
|
+
Random._int_generators[name] = generator
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def add_float_generator(name: str, generator):
|
|
69
|
+
"""add a generator to the float generators
|
|
70
|
+
Args:
|
|
71
|
+
name (str): the name of the generator to use for the number
|
|
72
|
+
generator : the generator to add should be a callable function
|
|
73
|
+
"""
|
|
74
|
+
Random._float_generators[name] = generator
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def get_random_vec4() -> Vec4:
|
|
78
|
+
"""get a random vector with componets ranged from +/- 1"""
|
|
79
|
+
gen = Random._float_generators["RandomFloat"]
|
|
80
|
+
return Vec4(gen(), gen(), gen(), 0.0)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def get_random_colour4() -> Vec4:
|
|
84
|
+
"""get a random colour with components ranged from 0-1"""
|
|
85
|
+
gen = Random._float_generators["RandomPositiveFloat"]
|
|
86
|
+
return Vec4(gen(), gen(), gen(), 1.0)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def get_random_colour3() -> Vec3:
|
|
90
|
+
"""get a random colour with components ranged from 0-1"""
|
|
91
|
+
gen = Random._float_generators["RandomPositiveFloat"]
|
|
92
|
+
return Vec3(gen(), gen(), gen())
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def get_random_normalized_vec4() -> Vec4:
|
|
96
|
+
"""get a random vector with componets ranged from +/- 1 and Normalized"""
|
|
97
|
+
gen = Random._float_generators["RandomFloat"]
|
|
98
|
+
v = Vec4(gen(), gen(), gen(), 0.0)
|
|
99
|
+
v.normalize()
|
|
100
|
+
return v
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def get_random_vec3() -> Vec3:
|
|
104
|
+
"""get a random vector with componets ranged from +/- 1"""
|
|
105
|
+
gen = Random._float_generators["RandomFloat"]
|
|
106
|
+
return Vec3(gen(), gen(), gen())
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def get_random_normalized_vec3() -> Vec3:
|
|
110
|
+
"""get a random vector with componets ranged from +/- 1 and Normalized"""
|
|
111
|
+
gen = Random._float_generators["RandomFloat"]
|
|
112
|
+
v = Vec3(gen(), gen(), gen())
|
|
113
|
+
v.normalize()
|
|
114
|
+
return v
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def get_random_vec2() -> Vec2:
|
|
118
|
+
"""get a random vector with componets ranged from +/- 1"""
|
|
119
|
+
gen = Random._float_generators["RandomFloat"]
|
|
120
|
+
return Vec2(gen(), gen())
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def get_random_normalized_vec2() -> Vec2:
|
|
124
|
+
"""get a random vector with componets ranged from +/- 1 and Normalized"""
|
|
125
|
+
gen = Random._float_generators["RandomFloat"]
|
|
126
|
+
v = Vec2(gen(), gen())
|
|
127
|
+
v.normalize()
|
|
128
|
+
return v
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def get_random_point(
|
|
132
|
+
x_range: float = 1.0, y_range: float = 1.0, z_range: float = 1.0
|
|
133
|
+
) -> Vec3:
|
|
134
|
+
"""get a random point in 3D space defaults to +/- 1 else user defined range
|
|
135
|
+
Args:
|
|
136
|
+
x_range (float): the +/-x range
|
|
137
|
+
y_range (float): the +/-y range
|
|
138
|
+
z_range (float): the +/-z range
|
|
139
|
+
Returns:
|
|
140
|
+
a random point
|
|
141
|
+
"""
|
|
142
|
+
gen = Random._float_generators["RandomFloat"]
|
|
143
|
+
return Vec3(gen() * x_range, gen() * y_range, gen() * z_range)
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def random_number(mult: float = 1.0) -> float:
|
|
147
|
+
"""a replacement for the old RandomNumber func
|
|
148
|
+
this is basically a convinience function
|
|
149
|
+
Args:
|
|
150
|
+
mult (float): an optional multiplyer for the output
|
|
151
|
+
Returns:
|
|
152
|
+
(uniform_random(-1-0-+1) * mult)
|
|
153
|
+
"""
|
|
154
|
+
gen = Random._float_generators["RandomFloat"]
|
|
155
|
+
return gen() * mult
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def random_positive_number(mult: float = 1.0) -> float:
|
|
159
|
+
"""a replacement for the old ReandomPosNum
|
|
160
|
+
this is basically a convinience function
|
|
161
|
+
Args:
|
|
162
|
+
mult (float): an optional multiplyer for the output
|
|
163
|
+
Returns:
|
|
164
|
+
(uniform_random(0-1) * mult)
|
|
165
|
+
"""
|
|
166
|
+
gen = Random._float_generators["RandomPositiveFloat"]
|
|
167
|
+
return gen() * mult
|