Skip to content

Commit 56027ba

Browse files
committed
Vectors now emulate container types; vector swizzling comming next.
1 parent 09f5c07 commit 56027ba

File tree

1 file changed

+220
-15
lines changed

1 file changed

+220
-15
lines changed

raylibpy/__init__.py

Lines changed: 220 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
from math import modf
44
from enum import IntEnum, IntFlag
5-
from typing import Tuple, Union, Sequence, AnyStr
5+
from typing import Tuple, Union, Sequence, AnyStr, Optional
66
from ctypes import (
77
c_bool,
88
c_char_p,
@@ -810,7 +810,7 @@
810810

811811
Number = Union[int, float]
812812
Seq = Sequence[Number]
813-
813+
VectorN = Union[Seq, 'Vector4', 'Vector3', 'Vector2']
814814

815815
def _float(value) -> float:
816816
return float(value) if isinstance(value, int) else value
@@ -863,6 +863,81 @@ def _color(seq: Sequence[Number]) -> 'Color':
863863
return Color(_float(r), _float(r), _float(b), _float(q))
864864

865865

866+
def _attr_swizzle(attr: str, size: int, write: bool=False) -> Tuple[bool, str]:
867+
if len(attr) not in (1, 2, 3, 4):
868+
return False, "Wrong number of components to swizzle (must be 1, 2, 3 or 4; not {}).".format(len(attr))
869+
if size not in (2, 3, 4):
870+
return False, "Wrong vector size (must be 2, 3 or 4; not {}).".format(size)
871+
872+
groups = ['xyzw', 'uv', 'rgba']
873+
if size == 3:
874+
groups = ['xyz', 'uv', 'rgb']
875+
elif size == 2:
876+
groups = ['xy', 'uv', '']
877+
878+
if attr[0] in groups[0]:
879+
group = 0
880+
elif attr[0] in groups[1]:
881+
group = 1
882+
elif attr[0] in groups[2]:
883+
group = 2
884+
else:
885+
return False, "Invalid component '{}' in swizzled Vector{} attribute.".format(attr[0], size)
886+
887+
already_set = []
888+
result = ''
889+
for i, c in enumerate(attr):
890+
if c not in groups[group]:
891+
return False, "Invalid component '{}' in swizzled attribute.".format(c)
892+
if write and c in already_set:
893+
return False, "Component '{}' in swizzled attribute is set more than once.".format(c)
894+
if write:
895+
already_set.append(c)
896+
result += groups[0][groups[group].index(c)]
897+
return True, result
898+
899+
900+
def _values(size, *args) -> Tuple[bool, tuple]:
901+
if size not in (2, 3, 4):
902+
return False, ("Wrong vector size (must be 2, 3 or 4; not {}).".format(size),)
903+
# if len(values) > size:
904+
# return False, ("Too many values ({} instead of {}).".format(size + abs(n_args), size),)
905+
906+
n_args = 0
907+
result = []
908+
for arg in args:
909+
if isinstance(arg, (int, float)):
910+
n_args += 1
911+
result.append(arg)
912+
elif isinstance(arg, Vector2):
913+
n_args += 2
914+
result.append(arg.x)
915+
result.append(arg.y)
916+
elif isinstance(arg, Vector3):
917+
n_args += 3
918+
result.append(arg.x)
919+
result.append(arg.y)
920+
result.append(arg.z)
921+
elif isinstance(arg, Vector4):
922+
n_args += 4
923+
result.append(arg.x)
924+
result.append(arg.y)
925+
result.append(arg.z)
926+
result.append(arg.w)
927+
elif arg is None:
928+
break
929+
else:
930+
try:
931+
for v in arg:
932+
result.append(v)
933+
n_args += 1
934+
except (TypeError, ValueError):
935+
return False, ("{} is not iterable".format(arg.__class__.__name__),)
936+
if n_args != size:
937+
return False, ("Too many or too few values ({} instead of {}).".format(n_args, size),)
938+
939+
return True, tuple(result)
940+
866941

867942
_NOARGS = []
868943

@@ -1047,7 +1122,7 @@ def _color(seq: Sequence[Number]) -> 'Color':
10471122
# STRUCTURES DEFINITIONS
10481123
# -------------------------------------------------------------------
10491124

1050-
class Vector2(Structure):
1125+
class _Vector2(Structure):
10511126
"""
10521127
Wrapper for raylib Vector2 struct:
10531128
@@ -1061,6 +1136,9 @@ class Vector2(Structure):
10611136
('y', c_float)
10621137
]
10631138

1139+
1140+
class Vector2(_Vector2):
1141+
10641142
@classmethod
10651143
def zero(cls) -> 'Vector2':
10661144
return Vector2(0., 0.)
@@ -1069,17 +1147,54 @@ def zero(cls) -> 'Vector2':
10691147
def one(cls) -> 'Vector2':
10701148
return Vector2(1., 1.)
10711149

1150+
def __init__(self, *args) -> None:
1151+
is_ok, result = _values(2, *args)
1152+
if not is_ok:
1153+
raise ValueError(result[0])
1154+
super(Vector2, self).__init__(*result)
1155+
10721156
def __str__(self) -> str:
10731157
return "({}, {})".format(self.x, self.y)
10741158

10751159
def __repr__(self) -> str:
10761160
return "{}({}, {})".format(self.__class__.__qualname__, self.x, self.y)
10771161

1162+
def __getattr__(self, name: str) -> Union[float, 'Vector2', 'Vector3', 'Vector4']:
1163+
is_valid, result = _attr_swizzle(name, 2)
1164+
if is_valid:
1165+
comps = {'x': self.x, 'y': self.y, 'z': 0.0, 'w': 0.0}
1166+
n = len(result)
1167+
v = [comps[comp] for comp in result]
1168+
if n == 2:
1169+
return Vector2(*v)
1170+
if n == 3:
1171+
return Vector3(*v)
1172+
if n == 4:
1173+
return Vector4(*v)
1174+
1175+
raise AttributeError(result)
1176+
1177+
def __setattr__(self, name: str, value: Union[float, 'Vector2', 'Vector3', 'Vector4']) -> None:
1178+
is_valid, result = _attr_swizzle(name, 2, True) # True for setattr, so components can't be set more than once.
1179+
if is_valid:
1180+
if len(name) == 1:
1181+
v = float(value)
1182+
super(Vector2, self).__setattr__(result, v)
1183+
else:
1184+
v = _vec2(value)
1185+
for c in result:
1186+
super(Vector2, self).__setattr__(c, getattr(v, c))
1187+
else:
1188+
raise AttributeError(result)
1189+
10781190
def __getitem__(self, key: str) -> Union[float, 'Vector2', 'Vector3', 'Vector4']:
1079-
assert isinstance(key, str), "key must be a str of 1 to 4 characters ('x' or 'y')."
1080-
assert 0 < len(key) < 5, "key must have between 1 and 4 characters ('x' or 'y')."
1081-
comps = [self.x, self.y, 0., 0.]
1082-
values = []
1191+
assert isinstance(key, str, int), "key must be a str or int."
1192+
comps = [self.x, self.y, 0.0, 0.0]
1193+
if isinstance(key, int):
1194+
assert 0 <= key < 2, "key must be an int in range [0..1]"
1195+
return comps[key]
1196+
else:
1197+
assert 0 < len(key) < 5, "key must have between 1 and 4 characters ('x', 'y', 'z' or 'w')."
10831198
for i, axis in enumerate(key):
10841199
values.append({
10851200
'x': self.x,
@@ -1204,7 +1319,7 @@ def __imul__(self, other: Union['Vector2', Seq]) -> 'Vector2':
12041319
Vector2Ptr = POINTER(Vector2)
12051320

12061321

1207-
class Vector3(Structure):
1322+
class _Vector3(Structure):
12081323
"""
12091324
Wrapper for raylib Vector3 struct:
12101325
@@ -1220,6 +1335,9 @@ class Vector3(Structure):
12201335
('z', c_float),
12211336
]
12221337

1338+
1339+
class Vector3(_Vector3):
1340+
12231341
@classmethod
12241342
def zero(cls) -> 'Vector3':
12251343
return Vector3(0., 0., 0.)
@@ -1228,16 +1346,54 @@ def zero(cls) -> 'Vector3':
12281346
def one(cls) -> 'Vector3':
12291347
return Vector3(1., 1., 1.)
12301348

1349+
def __init__(self, *args) -> None:
1350+
is_ok, result = _values(3, *args)
1351+
if not is_ok:
1352+
raise ValueError(result[0])
1353+
super(Vector3, self).__init__(*result)
1354+
12311355
def __str__(self) -> str:
12321356
return "({}, {}, {})".format(self.x, self.y, self.z)
12331357

12341358
def __repr__(self) -> str:
12351359
return "{}({}, {}, {})".format(self.__class__.__qualname__, self.x, self.y, self.z)
12361360

1361+
def __getattr__(self, name: str) -> Union[float, 'Vector2', 'Vector3', 'Vector4']:
1362+
is_valid, result = _attr_swizzle(name, 3)
1363+
if is_valid:
1364+
comps = {'x': self.x, 'y': self.y, 'z': self.z, 'w': 0.0}
1365+
n = len(result)
1366+
v = [comps[comp] for comp in result]
1367+
if n == 2:
1368+
return Vector2(*v)
1369+
if n == 3:
1370+
return Vector3(*v)
1371+
if n == 4:
1372+
return Vector4(*v)
1373+
1374+
raise AttributeError(result)
1375+
1376+
def __setattr__(self, name: str, value: Union[float, 'Vector2', 'Vector3', 'Vector4']) -> None:
1377+
is_valid, result = _attr_swizzle(name, 3, True) # True for setattr, so components can't be set more than once.
1378+
if is_valid:
1379+
if len(name) == 1:
1380+
v = float(value)
1381+
super(Vector3, self).__setattr__(result, v)
1382+
else:
1383+
v = _vec3(value)
1384+
for c in result:
1385+
super(Vector3, self).__setattr__(c, getattr(v, c))
1386+
else:
1387+
raise AttributeError(result)
1388+
12371389
def __getitem__(self, key: str) -> Union[float, 'Vector2', 'Vector3', 'Vector4']:
1238-
assert isinstance(key, str), "key must be a str of 1 to 4 characters ('x', 'y', or 'z')."
1239-
assert 0 < len(key) < 5, "key must have between 1 and 4 characters ('x', 'y', or 'z')."
1240-
comps = [self.x, self.y, self.z, 0.]
1390+
assert isinstance(key, str, int), "key must be a str or int."
1391+
comps = [self.x, self.y, self.z, 0.0]
1392+
if isinstance(key, int):
1393+
assert 0 <= key < 3, "key must be an int in range [0..2]"
1394+
return comps[key]
1395+
else:
1396+
assert 0 < len(key) < 5, "key must have between 1 and 3 characters ('x', 'y' or 'z')."
12411397
values = []
12421398
for i, axis in enumerate(key):
12431399
values.append({
@@ -1377,7 +1533,7 @@ def __imul__(self, other: Union['Vector3', Seq]) -> 'Vector3':
13771533
Vector3Ptr = POINTER(Vector3)
13781534

13791535

1380-
class Vector4(Structure):
1536+
class _Vector4(Structure):
13811537
"""
13821538
Wrapper for raylib Vector4 struct:
13831539
@@ -1395,24 +1551,68 @@ class Vector4(Structure):
13951551
('w', c_float),
13961552
]
13971553

1554+
class Vector4(_Vector4):
1555+
13981556
@classmethod
13991557
def zero(cls) -> 'Vector4':
1400-
return Vector2(0., 0., 0., 1.)
1558+
return Vector4(0., 0., 0., 1.)
14011559

14021560
@classmethod
14031561
def one(cls) -> 'Vector4':
14041562
return Vector2(1., 1., 1., 1.)
14051563

1564+
def __init__(self, *args) -> None:
1565+
is_ok, result = _values(4, *args)
1566+
if not is_ok:
1567+
raise ValueError(result[0])
1568+
super(Vector4, self).__init__(*result)
1569+
14061570
def __str__(self) -> str:
14071571
return "({}, {}, {}, {})".format(self.x, self.y, self.z, self.w)
14081572

14091573
def __repr__(self) -> str:
14101574
return "{}({}, {}, {}, {})".format(self.__class__.__qualname__, self.x, self.y, self.z, self.w)
14111575

1576+
def __getattr__(self, name: str) -> Union[float, 'Vector2', 'Vector3', 'Vector4']:
1577+
is_valid, result = _attr_swizzle(name, 4)
1578+
if is_valid:
1579+
comps = {'x': self.x, 'y': self.y, 'z': self.z, 'w': self.w}
1580+
n = len(result)
1581+
v = [comps[comp] for comp in result]
1582+
if n == 2:
1583+
return Vector2(*v)
1584+
if n == 3:
1585+
return Vector3(*v)
1586+
if n == 4:
1587+
return Vector4(*v)
1588+
1589+
raise AttributeError(result)
1590+
1591+
def __setattr__(self, name: str, value: Union[float, 'Vector2', 'Vector3', 'Vector4']) -> None:
1592+
is_valid, result = _attr_swizzle(name, 4, True) # True for setattr, so components can't be set more than once.
1593+
if is_valid:
1594+
if len(name) == 1:
1595+
v = float(value)
1596+
super(Vector4, self).__setattr__(result, v)
1597+
else:
1598+
is_valid, v = _values(len(name), *value)
1599+
print('::', value)
1600+
if is_valid:
1601+
for i, c in enumerate(result):
1602+
super(Vector4, self).__setattr__(c, v[i])
1603+
else:
1604+
raise ValueError(v[0]) # thisline
1605+
else:
1606+
raise AttributeError(result)
1607+
14121608
def __getitem__(self, key: str) -> Union[float, 'Vector2', 'Vector3', 'Vector4']:
1413-
assert isinstance(key, str), "key must be a str of 1 to 4 characters ('x', 'y', 'z' or 'w')."
1414-
assert 0 < len(key) < 5, "key must have between 1 and 4 characters ('x', 'y', 'z' or 'w')."
1609+
assert isinstance(key, str, int), "key must be a str or int."
14151610
comps = [self.x, self.y, self.z, self.w]
1611+
if isinstance(key, int):
1612+
assert 0 <= key < 4, "key must be an int in range [0..3]"
1613+
return comps[key]
1614+
else:
1615+
assert 0 < len(key) < 5, "key must have between 1 and 4 characters ('x', 'y', 'z' or 'w')."
14161616
values = []
14171617
for i, axis in enumerate(key):
14181618
values.append({
@@ -4758,3 +4958,8 @@ def set_audio_stream_volume(stream: AudioStream, volume: float) -> None:
47584958
def set_audio_stream_pitch(stream: AudioStream, pitch: float) -> None:
47594959
"""Set pitch for audio stream (1.0 is base level)"""
47604960
return _rl.SetAudioStreamPitch(stream, _float(pitch))
4961+
4962+
a = Vector4(Vector2(10, 0), 100, 20)
4963+
b = Vector4.zero()
4964+
b.rgba = 0.0, a.rg, 1.0
4965+
a.xyzw = (10, (20, 40)), 1.0

0 commit comments

Comments
 (0)