0
# Utility and Conversion Functions
1
2
Utility functions for type conversion, memory access, testing, packing/unpacking, and interfacing with external libraries. These functions provide essential tools for working with graphics APIs, file formats, and performance-critical applications.
3
4
## Capabilities
5
6
### Memory Access and Pointers
7
8
Functions for accessing the underlying memory of PyGLM types for interfacing with C libraries and graphics APIs.
9
10
```python { .api }
11
def value_ptr(x):
12
"""
13
Get a ctypes pointer to the underlying data of a PyGLM object.
14
15
Args:
16
x: Any PyGLM vector, matrix, or quaternion
17
18
Returns:
19
ctypes pointer to the data (c_float, c_double, c_int, etc.)
20
21
Example:
22
vec = glm.vec3(1, 2, 3)
23
ptr = glm.value_ptr(vec) # c_float pointer
24
# Pass ptr to OpenGL functions like glUniform3fv()
25
"""
26
27
def sizeof(glm_type):
28
"""
29
Get the size in bytes of a PyGLM type.
30
31
Args:
32
glm_type: PyGLM type (vec3, mat4, quat, etc.)
33
34
Returns:
35
Size in bytes
36
37
Example:
38
size = glm.sizeof(glm.vec3) # 12 bytes (3 * 4-byte floats)
39
size = glm.sizeof(glm.mat4) # 64 bytes (16 * 4-byte floats)
40
size = glm.sizeof(glm.dvec3) # 24 bytes (3 * 8-byte doubles)
41
"""
42
```
43
44
### Type Construction from Pointers
45
46
Functions for creating PyGLM objects from memory pointers, useful when interfacing with external libraries.
47
48
```python { .api }
49
def make_vec2(ptr):
50
"""
51
Create a vec2 from a ctypes pointer.
52
53
Args:
54
ptr: ctypes pointer to float data
55
56
Returns:
57
vec2 constructed from the pointer data
58
59
Example:
60
# Create vec2 from existing float array
61
import ctypes
62
data = (ctypes.c_float * 2)(1.0, 2.0)
63
vec = glm.make_vec2(data)
64
"""
65
66
def make_vec3(ptr):
67
"""
68
Create a vec3 from a ctypes pointer.
69
70
Args:
71
ptr: ctypes pointer to float data
72
73
Returns:
74
vec3 constructed from the pointer data
75
"""
76
77
def make_vec4(ptr):
78
"""
79
Create a vec4 from a ctypes pointer.
80
81
Args:
82
ptr: ctypes pointer to float data
83
84
Returns:
85
vec4 constructed from the pointer data
86
"""
87
88
def make_mat2(ptr):
89
"""
90
Create a mat2 from a ctypes pointer.
91
92
Args:
93
ptr: ctypes pointer to float data (4 elements)
94
95
Returns:
96
mat2x2 constructed from the pointer data
97
"""
98
99
def make_mat3(ptr):
100
"""
101
Create a mat3 from a ctypes pointer.
102
103
Args:
104
ptr: ctypes pointer to float data (9 elements)
105
106
Returns:
107
mat3x3 constructed from the pointer data
108
"""
109
110
def make_mat4(ptr):
111
"""
112
Create a mat4 from a ctypes pointer.
113
114
Args:
115
ptr: ctypes pointer to float data (16 elements)
116
117
Returns:
118
mat4x4 constructed from the pointer data
119
"""
120
121
def make_quat(ptr):
122
"""
123
Create a quat from a ctypes pointer.
124
125
Args:
126
ptr: ctypes pointer to float data (4 elements: w, x, y, z)
127
128
Returns:
129
quat constructed from the pointer data
130
"""
131
```
132
133
### Numerical Testing Functions
134
135
Functions for testing special floating-point values and performing epsilon-based comparisons.
136
137
```python { .api }
138
def isinf(x):
139
"""
140
Test if value(s) are infinite.
141
142
Args:
143
x: Scalar or vector input
144
145
Returns:
146
Boolean or boolean vector indicating infinity
147
148
Example:
149
isinf(float('inf')) # True
150
isinf(glm.vec3(1, float('inf'), 3)) # bvec3(False, True, False)
151
"""
152
153
def isnan(x):
154
"""
155
Test if value(s) are NaN (Not a Number).
156
157
Args:
158
x: Scalar or vector input
159
160
Returns:
161
Boolean or boolean vector indicating NaN
162
163
Example:
164
isnan(float('nan')) # True
165
isnan(glm.vec2(1.0, float('nan'))) # bvec2(False, True)
166
"""
167
168
def epsilonEqual(x, y, epsilon):
169
"""
170
Compare values using epsilon tolerance.
171
172
Args:
173
x: First value (scalar or vector)
174
y: Second value (same type as x)
175
epsilon: Tolerance value (scalar or vector)
176
177
Returns:
178
Boolean or boolean vector indicating equality within epsilon
179
180
Example:
181
epsilonEqual(1.0001, 1.0002, 0.001) # True
182
epsilonEqual(glm.vec3(1, 2, 3), glm.vec3(1.01, 2.01, 3.01), 0.02) # bvec3(True, True, True)
183
"""
184
185
def epsilonNotEqual(x, y, epsilon):
186
"""
187
Compare values for inequality using epsilon tolerance.
188
189
Args:
190
x: First value (scalar or vector)
191
y: Second value (same type as x)
192
epsilon: Tolerance value (scalar or vector)
193
194
Returns:
195
Boolean or boolean vector indicating inequality beyond epsilon
196
197
Example:
198
epsilonNotEqual(1.0, 1.1, 0.05) # True (difference > epsilon)
199
"""
200
```
201
202
### Bit Manipulation and Packing
203
204
Functions for packing and unpacking floating-point values and performing bit-level operations.
205
206
```python { .api }
207
def floatBitsToInt(value):
208
"""
209
Reinterpret float bits as signed integer.
210
211
Args:
212
value: Float value (scalar or vector)
213
214
Returns:
215
Integer with same bit pattern as input float
216
217
Example:
218
int_bits = glm.floatBitsToInt(1.0)
219
# Useful for bit manipulation or hashing
220
"""
221
222
def intBitsToFloat(value):
223
"""
224
Reinterpret integer bits as float.
225
226
Args:
227
value: Integer value (scalar or vector)
228
229
Returns:
230
Float with same bit pattern as input integer
231
232
Example:
233
float_val = glm.intBitsToFloat(1065353216) # Should be 1.0
234
"""
235
236
def floatBitsToUint(value):
237
"""
238
Reinterpret float bits as unsigned integer.
239
240
Args:
241
value: Float value (scalar or vector)
242
243
Returns:
244
Unsigned integer with same bit pattern as input float
245
"""
246
247
def uintBitsToFloat(value):
248
"""
249
Reinterpret unsigned integer bits as float.
250
251
Args:
252
value: Unsigned integer value (scalar or vector)
253
254
Returns:
255
Float with same bit pattern as input unsigned integer
256
"""
257
```
258
259
### Half-Precision Packing
260
261
Functions for packing and unpacking 16-bit half-precision floating-point values.
262
263
```python { .api }
264
def packHalf2x16(v):
265
"""
266
Pack two 32-bit floats into a single 32-bit unsigned integer as half-precision values.
267
268
Args:
269
v: vec2 containing two float values
270
271
Returns:
272
32-bit unsigned integer containing packed half-precision values
273
274
Example:
275
packed = glm.packHalf2x16(glm.vec2(1.0, -2.5))
276
# Useful for reducing memory usage in shaders
277
"""
278
279
def unpackHalf2x16(p):
280
"""
281
Unpack 32-bit unsigned integer into two half-precision floats.
282
283
Args:
284
p: 32-bit unsigned integer containing packed half-precision values
285
286
Returns:
287
vec2 containing the unpacked float values
288
289
Example:
290
unpacked = glm.unpackHalf2x16(packed_value) # Returns vec2
291
"""
292
293
def packHalf4x16(v):
294
"""
295
Pack four 32-bit floats into two 32-bit unsigned integers as half-precision values.
296
297
Args:
298
v: vec4 containing four float values
299
300
Returns:
301
uvec2 containing packed half-precision values
302
303
Example:
304
packed = glm.packHalf4x16(glm.vec4(1, 2, 3, 4))
305
"""
306
307
def unpackHalf4x16(p):
308
"""
309
Unpack two 32-bit unsigned integers into four half-precision floats.
310
311
Args:
312
p: uvec2 containing packed half-precision values
313
314
Returns:
315
vec4 containing the unpacked float values
316
"""
317
```
318
319
### Normalized Value Packing
320
321
Functions for packing normalized floating-point values into integer formats.
322
323
```python { .api }
324
def packUnorm2x16(v):
325
"""
326
Pack two normalized floats [0,1] into 16-bit unsigned integers.
327
328
Args:
329
v: vec2 with values in range [0, 1]
330
331
Returns:
332
32-bit unsigned integer containing packed values
333
334
Example:
335
packed = glm.packUnorm2x16(glm.vec2(0.5, 1.0))
336
"""
337
338
def unpackUnorm2x16(p):
339
"""
340
Unpack 32-bit unsigned integer into two normalized floats.
341
342
Args:
343
p: 32-bit unsigned integer containing packed values
344
345
Returns:
346
vec2 with values in range [0, 1]
347
"""
348
349
def packSnorm2x16(v):
350
"""
351
Pack two signed normalized floats [-1,1] into 16-bit signed integers.
352
353
Args:
354
v: vec2 with values in range [-1, 1]
355
356
Returns:
357
32-bit unsigned integer containing packed values
358
359
Example:
360
packed = glm.packSnorm2x16(glm.vec2(-0.5, 1.0))
361
"""
362
363
def unpackSnorm2x16(p):
364
"""
365
Unpack 32-bit unsigned integer into two signed normalized floats.
366
367
Args:
368
p: 32-bit unsigned integer containing packed values
369
370
Returns:
371
vec2 with values in range [-1, 1]
372
"""
373
374
def packUnorm4x8(v):
375
"""
376
Pack four normalized floats [0,1] into 8-bit unsigned integers.
377
378
Args:
379
v: vec4 with values in range [0, 1]
380
381
Returns:
382
32-bit unsigned integer containing packed values
383
384
Example:
385
# Pack RGBA color
386
color = glm.vec4(1.0, 0.5, 0.25, 1.0) # Red, half green, quarter blue, full alpha
387
packed_color = glm.packUnorm4x8(color)
388
"""
389
390
def unpackUnorm4x8(p):
391
"""
392
Unpack 32-bit unsigned integer into four normalized floats.
393
394
Args:
395
p: 32-bit unsigned integer containing packed values
396
397
Returns:
398
vec4 with values in range [0, 1]
399
"""
400
401
def packSnorm4x8(v):
402
"""
403
Pack four signed normalized floats [-1,1] into 8-bit signed integers.
404
405
Args:
406
v: vec4 with values in range [-1, 1]
407
408
Returns:
409
32-bit unsigned integer containing packed values
410
"""
411
412
def unpackSnorm4x8(p):
413
"""
414
Unpack 32-bit unsigned integer into four signed normalized floats.
415
416
Args:
417
p: 32-bit unsigned integer containing packed values
418
419
Returns:
420
vec4 with values in range [-1, 1]
421
"""
422
```
423
424
### Double-Precision Packing
425
426
Functions for packing and unpacking double-precision floating-point values.
427
428
```python { .api }
429
def packDouble2x32(v):
430
"""
431
Pack a double-precision float into two 32-bit unsigned integers.
432
433
Args:
434
v: Double-precision float value
435
436
Returns:
437
uvec2 containing the packed double
438
439
Example:
440
packed_double = glm.packDouble2x32(3.141592653589793)
441
"""
442
443
def unpackDouble2x32(p):
444
"""
445
Unpack two 32-bit unsigned integers into a double-precision float.
446
447
Args:
448
p: uvec2 containing packed double-precision value
449
450
Returns:
451
Double-precision float value
452
"""
453
```
454
455
### Bit Manipulation Functions
456
457
Advanced bit manipulation functions for integer operations.
458
459
```python { .api }
460
def bitfieldExtract(value, offset, bits):
461
"""
462
Extract a range of bits from an integer.
463
464
Args:
465
value: Integer value (scalar or vector)
466
offset: Bit offset (least significant bit = 0)
467
bits: Number of bits to extract
468
469
Returns:
470
Extracted bits as integer
471
"""
472
473
def bitfieldInsert(base, insert, offset, bits):
474
"""
475
Insert bits into a range of an integer.
476
477
Args:
478
base: Base integer value
479
insert: Integer containing bits to insert
480
offset: Bit offset for insertion
481
bits: Number of bits to insert
482
483
Returns:
484
Integer with bits inserted
485
"""
486
487
def bitfieldReverse(value):
488
"""
489
Reverse the bits in an integer.
490
491
Args:
492
value: Integer value (scalar or vector)
493
494
Returns:
495
Integer with bits reversed
496
"""
497
498
def bitCount(value):
499
"""
500
Count the number of 1 bits in an integer.
501
502
Args:
503
value: Integer value (scalar or vector)
504
505
Returns:
506
Number of set bits
507
"""
508
509
def findLSB(value):
510
"""
511
Find the index of the least significant set bit.
512
513
Args:
514
value: Integer value (scalar or vector)
515
516
Returns:
517
Index of LSB, or -1 if no bits set
518
"""
519
520
def findMSB(value):
521
"""
522
Find the index of the most significant set bit.
523
524
Args:
525
value: Integer value (scalar or vector)
526
527
Returns:
528
Index of MSB, or -1 if no bits set
529
"""
530
```
531
532
### Extended Integer Functions
533
534
Functions for extended precision integer arithmetic.
535
536
```python { .api }
537
def uaddCarry(x, y, carry):
538
"""
539
Add two unsigned integers with carry detection.
540
541
Args:
542
x: First unsigned integer
543
y: Second unsigned integer
544
carry: Output parameter for carry bit
545
546
Returns:
547
Sum of x and y
548
"""
549
550
def usubBorrow(x, y, borrow):
551
"""
552
Subtract two unsigned integers with borrow detection.
553
554
Args:
555
x: First unsigned integer (minuend)
556
y: Second unsigned integer (subtrahend)
557
borrow: Output parameter for borrow bit
558
559
Returns:
560
Difference of x and y
561
"""
562
563
def umulExtended(x, y, msb, lsb):
564
"""
565
Multiply two unsigned integers producing extended precision result.
566
567
Args:
568
x: First unsigned integer
569
y: Second unsigned integer
570
msb: Output parameter for most significant bits
571
lsb: Output parameter for least significant bits
572
573
Returns:
574
Extended precision product
575
"""
576
577
def imulExtended(x, y, msb, lsb):
578
"""
579
Multiply two signed integers producing extended precision result.
580
581
Args:
582
x: First signed integer
583
y: Second signed integer
584
msb: Output parameter for most significant bits
585
lsb: Output parameter for least significant bits
586
587
Returns:
588
Extended precision product
589
"""
590
591
def iround(x):
592
"""
593
Round float to nearest integer (signed).
594
595
Args:
596
x: Float value (scalar or vector)
597
598
Returns:
599
Rounded integer value
600
"""
601
602
def uround(x):
603
"""
604
Round float to nearest unsigned integer.
605
606
Args:
607
x: Float value (scalar or vector)
608
609
Returns:
610
Rounded unsigned integer value
611
"""
612
```
613
614
### Control Functions
615
616
Functions for controlling PyGLM behavior and warnings.
617
618
```python { .api }
619
def silence(id):
620
"""
621
Silence specific PyGLM warnings.
622
623
Args:
624
id: Warning ID to silence (0 = silence all warnings)
625
626
Example:
627
glm.silence(1) # Silence warning ID 1
628
glm.silence(0) # Silence all warnings
629
"""
630
```
631
632
### Usage Examples
633
634
```python
635
from pyglm import glm
636
import ctypes
637
638
# === Memory Access for Graphics APIs ===
639
640
# Pass matrix to OpenGL uniform
641
model_matrix = glm.mat4()
642
model_matrix = glm.rotate(model_matrix, glm.radians(45), glm.vec3(0, 1, 0))
643
644
# Get pointer for OpenGL
645
matrix_ptr = glm.value_ptr(model_matrix)
646
# In OpenGL: glUniformMatrix4fv(location, 1, GL_FALSE, matrix_ptr)
647
648
# Get size information
649
matrix_size = glm.sizeof(glm.mat4) # 64 bytes
650
vector_size = glm.sizeof(glm.vec3) # 12 bytes
651
652
# === Creating Types from External Data ===
653
654
# Create PyGLM vector from C array
655
float_array = (ctypes.c_float * 3)(1.0, 2.0, 3.0)
656
vec3_from_array = glm.make_vec3(float_array)
657
658
# Create matrix from external data
659
matrix_data = (ctypes.c_float * 16)(*[1 if i == j else 0 for i in range(4) for j in range(4)])
660
identity_matrix = glm.make_mat4(matrix_data)
661
662
# === Numerical Testing ===
663
664
# Test for special values
665
values = glm.vec4(1.0, float('inf'), float('nan'), -5.0)
666
inf_test = glm.isinf(values) # bvec4(False, True, False, False)
667
nan_test = glm.isnan(values) # bvec4(False, False, True, False)
668
669
# Epsilon comparisons for floating-point equality
670
a = glm.vec3(1.0, 2.0, 3.0)
671
b = glm.vec3(1.0001, 1.9999, 3.0001)
672
epsilon = 0.001
673
674
equal_test = glm.epsilonEqual(a, b, epsilon) # bvec3(True, True, True)
675
not_equal_test = glm.epsilonNotEqual(a, b, epsilon) # bvec3(False, False, False)
676
677
# === Bit Manipulation ===
678
679
# Convert between float and integer bit representations
680
float_val = 1.0
681
int_bits = glm.floatBitsToInt(float_val) # 1065353216
682
back_to_float = glm.intBitsToFloat(int_bits) # 1.0
683
684
# Useful for bit manipulation or hashing
685
def simple_hash(vec):
686
# Convert vector components to integer bits for hashing
687
x_bits = glm.floatBitsToInt(vec.x)
688
y_bits = glm.floatBitsToInt(vec.y)
689
z_bits = glm.floatBitsToInt(vec.z)
690
return hash((x_bits, y_bits, z_bits))
691
692
position = glm.vec3(1.5, 2.7, 3.1)
693
position_hash = simple_hash(position)
694
695
# === Half-Precision Packing for Memory Efficiency ===
696
697
# Pack two floats into half-precision for storage
698
original_vec2 = glm.vec2(1.5, -2.25)
699
packed_half = glm.packHalf2x16(original_vec2) # Stores in 32 bits instead of 64
700
unpacked_vec2 = glm.unpackHalf2x16(packed_half) # Slight precision loss
701
702
# Pack four floats into half-precision
703
original_vec4 = glm.vec4(1.0, 2.0, 3.0, 4.0)
704
packed_half4 = glm.packHalf4x16(original_vec4) # Returns uvec2
705
unpacked_vec4 = glm.unpackHalf4x16(packed_half4)
706
707
# === Normalized Value Packing ===
708
709
# Pack RGBA color values efficiently
710
color = glm.vec4(1.0, 0.5, 0.25, 1.0) # Red, half green, quarter blue, full alpha
711
packed_color = glm.packUnorm4x8(color) # Pack into 32-bit integer
712
unpacked_color = glm.unpackUnorm4x8(packed_color) # Unpack back to vec4
713
714
# Pack normal vectors (commonly in range [-1, 1])
715
normal = glm.vec3(-0.5, 0.707, 0.5) # Normalized normal vector
716
# Extend to vec4 for packing
717
normal_vec4 = glm.vec4(normal.x, normal.y, normal.z, 0.0)
718
packed_normal = glm.packSnorm4x8(normal_vec4)
719
unpacked_normal = glm.unpackSnorm4x8(packed_normal)
720
721
# === Double-Precision Packing ===
722
723
# Pack double-precision value for storage or transmission
724
double_val = 3.141592653589793
725
packed_double = glm.packDouble2x32(double_val) # Returns uvec2
726
unpacked_double = glm.unpackDouble2x32(packed_double)
727
728
# === Practical Example: Vertex Data Compression ===
729
730
class CompressedVertex:
731
def __init__(self, position, normal, uv, color):
732
# Store position as-is (high precision needed)
733
self.position = position
734
735
# Pack normal into 32-bit integer (normals are in [-1,1] range)
736
normal_vec4 = glm.vec4(normal.x, normal.y, normal.z, 0.0)
737
self.packed_normal = glm.packSnorm4x8(normal_vec4)
738
739
# Pack UV coordinates into 32-bit integer (UVs are in [0,1] range)
740
uv_vec4 = glm.vec4(uv.x, uv.y, 0.0, 0.0) # Pad to vec4
741
self.packed_uv = glm.packUnorm4x8(uv_vec4)
742
743
# Pack color into 32-bit integer
744
self.packed_color = glm.packUnorm4x8(color)
745
746
def get_normal(self):
747
unpacked = glm.unpackSnorm4x8(self.packed_normal)
748
return glm.vec3(unpacked.x, unpacked.y, unpacked.z)
749
750
def get_uv(self):
751
unpacked = glm.unpackUnorm4x8(self.packed_uv)
752
return glm.vec2(unpacked.x, unpacked.y)
753
754
def get_color(self):
755
return glm.unpackUnorm4x8(self.packed_color)
756
757
# Create compressed vertex
758
original_position = glm.vec3(1.0, 2.0, 3.0)
759
original_normal = glm.vec3(0.0, 1.0, 0.0)
760
original_uv = glm.vec2(0.5, 0.75)
761
original_color = glm.vec4(1.0, 0.5, 0.25, 1.0)
762
763
compressed = CompressedVertex(original_position, original_normal, original_uv, original_color)
764
765
# Retrieve data (with some precision loss from compression)
766
retrieved_normal = compressed.get_normal()
767
retrieved_uv = compressed.get_uv()
768
retrieved_color = compressed.get_color()
769
770
# === OpenGL Integration Example ===
771
772
def upload_matrix_to_gpu(matrix, uniform_location):
773
"""Upload matrix to OpenGL uniform."""
774
# Get pointer to matrix data
775
matrix_ptr = glm.value_ptr(matrix)
776
matrix_size = glm.sizeof(type(matrix))
777
778
# Determine matrix dimensions and upload
779
if matrix_size == 64: # mat4
780
# glUniformMatrix4fv(uniform_location, 1, GL_FALSE, matrix_ptr)
781
pass
782
elif matrix_size == 36: # mat3
783
# glUniformMatrix3fv(uniform_location, 1, GL_FALSE, matrix_ptr)
784
pass
785
# etc.
786
787
# === Performance Monitoring ===
788
789
def compare_storage_sizes():
790
"""Compare storage sizes of different representations."""
791
792
# Original vec4
793
original = glm.vec4(1.0, 0.5, 0.25, 1.0)
794
original_size = glm.sizeof(glm.vec4) # 16 bytes
795
796
# Half-precision packed
797
packed_half = glm.packHalf4x16(original)
798
packed_half_size = glm.sizeof(glm.uvec2) # 8 bytes
799
800
# Normalized packed
801
packed_norm = glm.packUnorm4x8(original)
802
packed_norm_size = 4 # 4 bytes (single uint32)
803
804
print(f"Original: {original_size} bytes")
805
print(f"Half-precision: {packed_half_size} bytes ({packed_half_size/original_size:.1%} of original)")
806
print(f"Normalized: {packed_norm_size} bytes ({packed_norm_size/original_size:.1%} of original)")
807
808
compare_storage_sizes()
809
810
# === Error Handling and Warnings ===
811
812
# Silence specific warnings
813
glm.silence(1) # Silence warning with ID 1
814
815
# Silence all warnings
816
glm.silence(0)
817
818
# Example function that might generate warnings
819
def risky_operation():
820
# Some operation that might trigger frexp warning
821
result = glm.frexp(glm.vec3(1.5, 2.0, 3.0))
822
return result
823
824
# Call without warnings (if silenced)
825
result = risky_operation()
826
```
827
828
### Integration Best Practices
829
830
1. **Memory Management**: Always ensure that ctypes arrays remain in scope when using `make_*` functions
831
2. **Precision Trade-offs**: Understand precision loss when using packed formats - test with your specific data ranges
832
3. **Performance**: Use packed formats for large datasets (vertex buffers, texture data) but full precision for calculations
833
4. **Graphics API Integration**: Use `value_ptr()` to pass data directly to OpenGL, Vulkan, and other graphics APIs
834
5. **Error Handling**: Use numerical testing functions (`isinf`, `isnan`) when working with computed values
835
6. **Bit Manipulation**: Be careful with bit manipulation functions - they're primarily for specialized use cases