Skip to content

Commit 8fb7df9

Browse files
committed
Move to 3D
1 parent 6356e35 commit 8fb7df9

File tree

6 files changed

+377
-215
lines changed

6 files changed

+377
-215
lines changed

pycg/app.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,13 @@ def new_object():
184184
a, b = parsed
185185
obj = Line(Point(*a), Point(*b))
186186
elif typename == 'Wireframe' and len(parsed) > 2:
187-
obj = Wireframe([Point(x, y) for x, y in parsed])
187+
obj = Wireframe([Point(*p) for p in parsed])
188188
elif typename == 'Polygon' and len(parsed) > 2:
189-
obj = Polygon([Point(x, y) for x, y in parsed])
189+
obj = Polygon([Point(*p) for p in parsed])
190190
elif typename == "Bezier" and len(parsed) > 2:
191-
obj = Bezier([Point(x, y) for x, y in parsed])
191+
obj = Bezier([Point(*p) for p in parsed])
192192
elif typename == "BSpline" and len(parsed) > 2:
193-
obj = BSpline([Point(x, y) for x, y in parsed])
193+
obj = BSpline([Point(*p) for p in parsed])
194194
else:
195195
return
196196

@@ -360,9 +360,9 @@ def pan_camera(self, dx, dy, _normalized=True):
360360
dx *= self._pan
361361
dy *= self._pan
362362
align = Transformation().rotate(self.camera.angle).matrix()
363-
dx, dy, _ = align @ Vector(dx, dy, 1)
364-
self.camera.x += dx
365-
self.camera.y += dy
363+
delta = align @ Point(dx, dy)
364+
self.camera.x += delta.x
365+
self.camera.y += delta.y
366366
self.update()
367367

368368
def update_zoom(self, value, minimum, maximum):
@@ -454,7 +454,7 @@ def mouseReleaseEvent(self, event):
454454
def mouseMoveEvent(self, event):
455455
if self._drag_begin:
456456
# compute motion vector and update drag initial position
457-
delta = (event.x(), event.y()) - self._drag_begin
457+
delta = Point(event.x(), event.y()) - self._drag_begin
458458
self._drag_begin += delta
459459
# adjust delta based on window zoom
460460
dx = delta.x / self.camera.zoom

pycg/blas.py

+109-64
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,61 @@
11
"""A custom implementation of some Basic Linear Algebra Subprograms (BLAS)."""
22

33
from typing import Sequence, Callable
4+
from math import sqrt, acos
45

5-
import numpy as np
66

7+
class Vector:
8+
"""Structure which can be used as a column vector."""
79

8-
class Matrix:
9-
"""Matrix structured for row-major access."""
10-
11-
def __init__(self, first: Sequence, *rest: Sequence):
12-
if isinstance(first, np.ndarray):
13-
self._matrix = first
14-
else:
15-
self._matrix = np.array([first] + list(rest), np.float32)
10+
def __init__(self, first, *rest):
11+
self._coordinates = [first] + list(rest)
1612

1713
@staticmethod
18-
def from_lists(rows: Sequence):
19-
"""Creates a Matrix with the given sequence of sequences."""
20-
return Matrix(*rows)
21-
22-
@staticmethod
23-
def from_function(m: int, n: int, builder: Callable):
24-
"""
25-
Builds an MxN Matrix by calling a given builder function on each index
26-
pair (row, column) and filling its cell with the function's return.
27-
"""
28-
return Matrix.from_lists([[builder(i, j) for j in range(n)] for i in range(m)])
29-
30-
@staticmethod
31-
def identity(n: int):
32-
"""Create an NxN identity Matrix."""
33-
return Matrix.from_function(n, n, lambda i, j: 1 if j == i else 0)
34-
35-
@staticmethod
36-
def zeros(m: int, n: int):
37-
"""Create an MxN Matrix filled with zeros."""
38-
return Matrix.from_function(m, n, lambda i, j: 0)
39-
40-
@property
41-
def rows(self):
42-
return len(self)
43-
44-
@property
45-
def columns(self):
46-
return len(self[0])
14+
def angle(a, b):
15+
"""Finds the angle between two vectors."""
16+
return acos((a @ b) / (a.length * b.length))
4717

4818
def __getitem__(self, key):
49-
return self._matrix[key]
19+
return self._coordinates[key]
5020

5121
def __setitem__(self, key, item):
52-
self._matrix[key] = item
22+
self._coordinates[key] = item
5323

5424
def __len__(self):
55-
return len(self._matrix)
25+
return len(self._coordinates)
5626

5727
def __repr__(self):
58-
return str(self._matrix)
28+
return str(self._coordinates)
5929

60-
def __add__(self, other):
61-
if not isinstance(other, Matrix):
62-
other = Matrix(*other)
63-
return Matrix(self._matrix + other._matrix)
30+
def __add__(self, other: Sequence):
31+
try:
32+
return Vector(*tuple(v + other[i] for i, v in enumerate(self)))
33+
except Exception as exc:
34+
raise NotImplementedError from exc
6435

6536
def __radd__(self, other):
6637
return self + other
6738

68-
def __sub__(self, other):
69-
if not isinstance(other, Matrix):
70-
other = Matrix(*other)
71-
return Matrix(self._matrix - other._matrix)
39+
def __sub__(self, other: Sequence):
40+
return self + tuple(-x for x in other)
7241

7342
def __rsub__(self, other):
7443
return other + (-self)
7544

7645
def __mul__(self, scalar): # scalar product
77-
return Matrix(self._matrix * scalar)
46+
try:
47+
return Vector(*tuple(scalar * x for x in self))
48+
except Exception as exc:
49+
raise NotImplementedError from exc
7850

7951
def __rmul__(self, other):
8052
return self * other
8153

82-
def __matmul__(self, other):
83-
if not isinstance(other, Matrix):
84-
other = Matrix(*other)
85-
product = self._matrix @ other._matrix
86-
return product if isinstance(product, np.float32) else Matrix(product)
54+
def __matmul__(self, other: Sequence): # dot product
55+
try:
56+
return sum(tuple(v * other[i] for i, v in enumerate(self)))
57+
except Exception as exc:
58+
raise NotImplementedError from exc
8759

8860
def __neg__(self):
8961
return self * -1
@@ -92,12 +64,19 @@ def __truediv__(self, scalar):
9264
return self * (1 / scalar)
9365

9466
def __mod__(self, z):
95-
return Matrix(self._matrix % z)
67+
return Vector(*tuple(x % z for x in self))
9668

9769
def __eq__(self, other):
98-
if not isinstance(other, Matrix):
99-
other = Matrix(*other)
100-
return np.array_equal(self, other)
70+
if len(self) != len(other):
71+
return False
72+
for i, v in enumerate(self):
73+
if v != other[i]:
74+
return False
75+
return True
76+
77+
def normalized(self):
78+
"""Returns a normalized version of this vector."""
79+
return self / self.length
10180

10281
@property
10382
def x(self):
@@ -123,9 +102,75 @@ def z(self):
123102
def z(self, z):
124103
self[2] = z
125104

105+
@property
106+
def length(self):
107+
squared_sum = 0
108+
for component in self:
109+
squared_sum += component * component
110+
return sqrt(squared_sum)
126111

127-
class Vector(Matrix):
128-
"""Structure which can be used as a column vector."""
129112

130-
def __init__(self, x, y, *others):
131-
super().__init__(x, y, *others)
113+
class Matrix(Vector):
114+
"""Matrix structured for row-major access."""
115+
116+
def __init__(self, first: Sequence, *rest: Sequence):
117+
convert = lambda seq: seq if isinstance(seq, Vector) else Vector(*seq)
118+
rows = [convert(first)]
119+
for row in rest:
120+
if len(row) != len(first):
121+
raise ValueError("Matrix row length mismatch.")
122+
else:
123+
rows.append(convert(row))
124+
super().__init__(*rows)
125+
126+
@staticmethod
127+
def from_lists(rows: Sequence):
128+
"""Creates a Matrix with the given sequence of sequences."""
129+
return Matrix(*rows)
130+
131+
@staticmethod
132+
def from_function(m: int, n: int, builder: Callable):
133+
"""
134+
Builds an MxN Matrix by calling a given builder function on each index
135+
pair (row, column) and filling its cell with the function's return.
136+
"""
137+
rows = [[builder(i, j) for j in range(n)] for i in range(m)]
138+
return Matrix.from_lists(rows)
139+
140+
@staticmethod
141+
def identity(n: int):
142+
"""Create an NxN identity Matrix."""
143+
return Matrix.from_function(n, n, lambda i, j: 1 if j == i else 0)
144+
145+
@staticmethod
146+
def zeros(m: int, n: int):
147+
"""Create an MxN Matrix filled with zeros."""
148+
return Matrix.from_function(m, n, lambda i, j: 0)
149+
150+
def __matmul__(self, other):
151+
if isinstance(other, Matrix):
152+
m, n, p = self.rows, self.columns, other.columns
153+
if other.rows != n:
154+
raise ValueError("Matrix has incompatible dimensions.")
155+
result = Matrix.zeros(m, p)
156+
for i in range(m):
157+
for j in range(p):
158+
for k in range(n):
159+
result[i][j] += self[i][k] * other[k][j]
160+
return result
161+
elif isinstance(other, Vector):
162+
n = len(other)
163+
if n != self.columns:
164+
raise ValueError("Vector has incompatible dimensions.")
165+
return Vector(*[line @ other for line in self])
166+
else:
167+
raise NotImplementedError("Can't multiply Matrix by %s." %
168+
type(other).__name__)
169+
170+
@property
171+
def rows(self):
172+
return len(self)
173+
174+
@property
175+
def columns(self):
176+
return len(self[0])

0 commit comments

Comments
 (0)