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. |