| 17 ) |
17 ) |
| 18 @property |
18 @property |
| 19 def coordinates(self): |
19 def coordinates(self): |
| 20 return self.x, self.y, self.z |
20 return self.x, self.y, self.z |
| 21 def __add__(self, other): |
21 def __add__(self, other): |
| 22 return Vertex(self.x + other.x, self.y + other.y, self.z + other.z) |
22 return type(self)(self.x + other.x, self.y + other.y, self.z + other.z) |
| 23 def __neg__(self): |
23 def __neg__(self): |
| 24 return Vertex(-self.x, -self.y, -self.z) |
24 return type(self)(-self.x, -self.y, -self.z) |
| 25 def __sub__(self, other): |
25 def __sub__(self, other): |
| 26 return self + (-other) |
26 return self + (-other) |
| 27 def __mul__(self, scalar): |
27 def __mul__(self, scalar): |
| 28 return Vertex(self.x * scalar, self.y * scalar, self.z * scalar) |
28 return type(self)(self.x * scalar, self.y * scalar, self.z * scalar) |
| 29 def __rmul__(self, other): |
29 def __rmul__(self, other): |
| 30 return self * other |
30 return self * other |
| 31 def __truediv__(self, scalar): |
31 def __truediv__(self, scalar): |
| 32 return Vertex(self.x / scalar, self.y / scalar, self.z / scalar) |
32 return type(self)(self.x / scalar, self.y / scalar, self.z / scalar) |
| 33 def __floordiv__(self, scalar): |
33 def __floordiv__(self, scalar): |
| 34 return Vertex(self.x // scalar, self.y // scalar, self.z // scalar) |
34 return type(self)(self.x // scalar, self.y // scalar, self.z // scalar) |
| 35 def __matmul__(self, transformation_matrix): |
35 def __matmul__(self, transformation_matrix): |
| 36 return transform(self, transformation_matrix) |
36 return transform(self, transformation_matrix) |
| 37 def __eq__(self, other): |
37 def __eq__(self, other): |
| 38 return self.coordinates == other.coordinates |
38 return self.coordinates == other.coordinates |
| 39 def __lt__(self, other): |
39 def __lt__(self, other): |
| 53 def __repr__(self): |
53 def __repr__(self): |
| 54 return str.format('LineSegment({!r}, {!r})', self.v1, self.v2) |
54 return str.format('LineSegment({!r}, {!r})', self.v1, self.v2) |
| 55 @property |
55 @property |
| 56 def vertices(self): |
56 def vertices(self): |
| 57 return self.v1, self.v2 |
57 return self.v1, self.v2 |
| |
58 def __len__(self): |
| |
59 return 2 |
| |
60 def __getitem__(self, index): |
| |
61 return self.vertices[index] |
| |
62 |
| |
63 class IndexRing: |
| |
64 def __init__(self, container): |
| |
65 self.container = container |
| |
66 def __getitem__(self, index): |
| |
67 return self.container[index % len(self.container)] |
| |
68 |
| |
69 class Vector(Vertex): |
| |
70 def __init__(self, x, y, z): |
| |
71 super().__init__(x, y, z) |
| |
72 def __repr__(self): |
| |
73 return str.format('Vector({!r}, {!r}. {!r})', self.x, self.y, self.z) |
| |
74 def length(self): |
| |
75 from math import sqrt |
| |
76 return sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) |
| |
77 def normalized(self): |
| |
78 length = self.length() |
| |
79 if length > 0: |
| |
80 return Vector(self.x / length, self.y / length, self.z / length) |
| |
81 else: |
| |
82 return Vector(0, 0, 0) |
| |
83 |
| |
84 def dot_product(v1, v2): |
| |
85 ''' Returns the dot product v1 · v2. ''' |
| |
86 return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z |
| |
87 |
| |
88 def cross_product(v1, v2): |
| |
89 ''' Returns the cross product v1 × v2. ''' |
| |
90 return Vector( |
| |
91 x = v1.y * v2.z - v1.z * v2.y, |
| |
92 y = v1.z * v2.x - v1.x * v2.z, |
| |
93 z = v1.x * v2.y - v1.y * v2.x, |
| |
94 ) |
| |
95 |
| |
96 def unit_normal(a, b, c): |
| |
97 x = Matrix3x3([ |
| |
98 1, a.y, a.z, |
| |
99 1, b.y, b.z, |
| |
100 1, c.y, c.z, |
| |
101 ]).determinant() |
| |
102 y = Matrix3x3([ |
| |
103 a.x, 1, a.z, |
| |
104 b.x, 1, b.z, |
| |
105 c.x, 1, c.z, |
| |
106 ]).determinant() |
| |
107 z = Matrix3x3([ |
| |
108 a.x, a.y, 1, |
| |
109 b.x, b.y, 1, |
| |
110 c.x, c.y, 1, |
| |
111 ]).determinant() |
| |
112 return Vector(x, y, z).normalized() |
| |
113 |
| |
114 def pairs(iterable, *, count = 2): |
| |
115 ''' |
| |
116 Iterates the given iterable and returns tuples containing `count` |
| |
117 sequential values in the iterable. |
| |
118 |
| |
119 Formally, for a vector A with indices 0…n and pair count k, this |
| |
120 function yields: |
| |
121 (A₀, A₁, …, Aₖ), |
| |
122 (A₁, A₂, …, Aₖ₊₁), |
| |
123 …, |
| |
124 (Aₙ₋₂, Aₙ₋₁, …, Aₖ₋₂), |
| |
125 (Aₙ₋₁, A₀, …, Aₖ₋₁). |
| |
126 ''' |
| |
127 iterable_count = len(iterable) |
| |
128 for index in range(iterable_count): |
| |
129 yield tuple( |
| |
130 iterable[(index + offset) % iterable_count] |
| |
131 for offset in range(count) |
| |
132 ) |
| 58 |
133 |
| 59 class Polygon: |
134 class Polygon: |
| 60 def __init__(self, vertices): |
135 def __init__(self, vertices): |
| 61 self.vertices = vertices |
136 self.vertices = vertices |
| 62 def __repr__(self): |
137 def __repr__(self): |
| 63 return str.format('Polygon({!r})', self.vertices) |
138 return str.format('Polygon({!r})', self.vertices) |
| |
139 def area(self): |
| |
140 total = Vector(0, 0, 0) |
| |
141 for v1, v2 in pairs(self.vertices): |
| |
142 total += cross_product(v1, v2) |
| |
143 return dot_product(total, unit_normal(self.vertices[0], self.vertices[1], self.vertices[2])) |
| |
144 def centroid(self): |
| |
145 ... |
| |
146 raise NotImplementedError |
| |
147 def __getitem__(self, index): |
| |
148 return self.vertices[index % len(self.vertices)] |
| |
149 def __len__(self): |
| |
150 return len(self.vertices) |
| 64 |
151 |
| 65 def is_real(number): |
152 def is_real(number): |
| 66 return isinstance(number, int) or isinstance(number, float) |
153 return isinstance(number, int) or isinstance(number, float) |
| 67 |
154 |
| 68 class TransformationMatrix: |
155 class Matrix3x3: |
| 69 ''' |
156 ''' |
| 70 A 3×3 matrix forming the top-left portion of a full 4×4 transformation |
157 A 3×3 matrix forming the top-left portion of a full 4×4 transformation |
| 71 matrix. |
158 matrix. |
| 72 ''' |
159 ''' |
| 73 def __init__(self, values): |
160 def __init__(self, values): |
| 74 assert(all(is_real(x) for x in values)) |
161 assert(all(is_real(x) for x in values)) |
| 75 assert len(values) == 9 |
162 assert len(values) == 9 |
| 76 self.values = values |
163 self.values = values |
| 77 def __repr__(self): |
164 def __repr__(self): |
| 78 return str.format('TransformationMatrix({!r})', self.values) |
165 return str.format('Matrix3x3({!r})', self.values) |
| 79 def __getitem__(self, index): |
166 def __getitem__(self, index): |
| 80 return self.values[index] |
167 return self.values[index] |
| |
168 def determinant(self): |
| |
169 v = self.values |
| |
170 return sum([ |
| |
171 +(v[0] * v[4] * v[8]), |
| |
172 +(v[1] * v[5] * v[6]), |
| |
173 +(v[2] * v[3] * v[7]), |
| |
174 -(v[2] * v[4] * v[6]), |
| |
175 -(v[1] * v[3] * v[8]), |
| |
176 -(v[0] * v[5] * v[7]), |
| |
177 ]) |
| 81 |
178 |
| 82 def complete_matrix(matrix, anchor): |
179 def complete_matrix(matrix, anchor): |
| 83 ''' |
180 ''' |
| 84 Combines a 3×3 matrix and an anchor vertex into a full 4×4 |
181 Combines a 3×3 matrix and an anchor vertex into a full 4×4 |
| 85 transformation matrix. |
182 transformation matrix. |