|
1 def degree_rep(angle): |
|
2 from math import degrees |
|
3 return '∠ %.2f°' % degrees(angle) |
|
4 |
|
5 def position_vector(vertex): |
|
6 assert isinstance(vertex, Vertex) |
|
7 return Vector(*vertex.coordinates) |
|
8 |
|
9 def angle_magnitude_key(angle): |
|
10 ''' |
|
11 Returns how great an angle is. |
|
12 ''' |
|
13 from math import pi as π |
|
14 normalized_angle = abs(angle) % (2 * π) |
|
15 if normalized_angle > π: |
|
16 normalized_angle = (2 * π) - normalized_angle |
|
17 return normalized_angle |
|
18 |
1 class Vertex: |
19 class Vertex: |
2 def __init__(self, x, y, z): |
20 def __init__(self, x, y, z): |
3 if not all(is_real(coordinate) for coordinate in (x, y, z)): |
21 if not all(is_real(coordinate) for coordinate in (x, y, z)): |
4 raise ValueError(str.format('Bad vertex coordinates: {!r}', |
22 raise ValueError(str.format('Bad vertex coordinates: {!r}', |
5 (x, y, z), |
23 (x, y, z), |
17 ) |
35 ) |
18 @property |
36 @property |
19 def coordinates(self): |
37 def coordinates(self): |
20 return self.x, self.y, self.z |
38 return self.x, self.y, self.z |
21 def __add__(self, other): |
39 def __add__(self, other): |
22 return type(self)(self.x + other.x, self.y + other.y, self.z + other.z) |
40 assert not (type(self) == type(other) == Vertex) |
|
41 if type(self) == Vector and type(other) == Vector: |
|
42 result_type = Vector |
|
43 else: |
|
44 result_type = Vertex |
|
45 return result_type(self.x + other.x, self.y + other.y, self.z + other.z) |
|
46 def __radd__(self, other): |
|
47 return self + other |
23 def __neg__(self): |
48 def __neg__(self): |
24 return type(self)(-self.x, -self.y, -self.z) |
49 return type(self)(-self.x, -self.y, -self.z) |
25 def __sub__(self, other): |
50 def __sub__(self, other): |
26 return self + (-other) |
51 result = self + (-position_vector(other)) |
|
52 if isinstance(other, Vertex): |
|
53 return Vector(*result.coordinates) |
|
54 else: |
|
55 return result |
27 def __mul__(self, scalar): |
56 def __mul__(self, scalar): |
|
57 assert is_real(scalar) |
28 return type(self)(self.x * scalar, self.y * scalar, self.z * scalar) |
58 return type(self)(self.x * scalar, self.y * scalar, self.z * scalar) |
29 def __rmul__(self, other): |
59 def __rmul__(self, other): |
30 return self * other |
60 return self * other |
31 def __truediv__(self, scalar): |
61 def __truediv__(self, scalar): |
|
62 assert is_real(scalar) |
32 return type(self)(self.x / scalar, self.y / scalar, self.z / scalar) |
63 return type(self)(self.x / scalar, self.y / scalar, self.z / scalar) |
33 def __floordiv__(self, scalar): |
64 def __floordiv__(self, scalar): |
|
65 assert is_real(scalar) |
34 return type(self)(self.x // scalar, self.y // scalar, self.z // scalar) |
66 return type(self)(self.x // scalar, self.y // scalar, self.z // scalar) |
35 def __matmul__(self, transformation_matrix): |
67 def __matmul__(self, transformation_matrix): |
36 return transform(self, transformation_matrix) |
68 return transform(self, transformation_matrix) |
37 def __eq__(self, other): |
69 def __eq__(self, other): |
38 return self.coordinates == other.coordinates |
70 return self.coordinates == other.coordinates |
51 def __init__(self, v1, v2): |
83 def __init__(self, v1, v2): |
52 self.v1, self.v2 = v1, v2 |
84 self.v1, self.v2 = v1, v2 |
53 def __repr__(self): |
85 def __repr__(self): |
54 return str.format('LineSegment({!r}, {!r})', self.v1, self.v2) |
86 return str.format('LineSegment({!r}, {!r})', self.v1, self.v2) |
55 @property |
87 @property |
|
88 def length(self): |
|
89 return self.v1.distance_to(self.v2) |
|
90 @property |
56 def vertices(self): |
91 def vertices(self): |
57 return self.v1, self.v2 |
92 return self.v1, self.v2 |
58 def __len__(self): |
93 def __len__(self): |
59 return 2 |
94 return 2 |
60 def __getitem__(self, index): |
95 def __getitem__(self, index): |
68 |
103 |
69 class Vector(Vertex): |
104 class Vector(Vertex): |
70 def __init__(self, x, y, z): |
105 def __init__(self, x, y, z): |
71 super().__init__(x, y, z) |
106 super().__init__(x, y, z) |
72 def __repr__(self): |
107 def __repr__(self): |
73 return str.format('Vector({!r}, {!r}. {!r})', self.x, self.y, self.z) |
108 return str.format('Vector({!r}, {!r}, {!r})', self.x, self.y, self.z) |
74 def length(self): |
109 def length(self): |
75 from math import sqrt |
110 from math import sqrt |
76 return sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) |
111 return sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) |
77 def normalized(self): |
112 def normalized(self): |
78 length = self.length() |
113 length = self.length() |
146 raise NotImplementedError |
181 raise NotImplementedError |
147 def __getitem__(self, index): |
182 def __getitem__(self, index): |
148 return self.vertices[index % len(self.vertices)] |
183 return self.vertices[index % len(self.vertices)] |
149 def __len__(self): |
184 def __len__(self): |
150 return len(self.vertices) |
185 return len(self.vertices) |
|
186 @property |
|
187 def perimeter_lines(self): |
|
188 for v1, v2 in pairs(self.vertices): |
|
189 yield LineSegment(v1, v2) |
|
190 @property |
|
191 def smallest_angle(self): |
|
192 from math import acos, inf |
|
193 min_angle = inf |
|
194 for v1, v2, v3 in pairs(self.vertices, count = 3): |
|
195 vec1 = (position_vector(v3) - position_vector(v2)).normalized() |
|
196 vec2 = (position_vector(v1) - position_vector(v2)).normalized() |
|
197 cosine = dot_product(vec1, vec2) / vec1.length() / vec2.length() |
|
198 min_angle = min(min_angle, angle_magnitude_key(acos(cosine))) |
|
199 return min_angle |
|
200 @property |
|
201 def hairline_ratio(self): |
|
202 lengths = [line.length for line in self.perimeter_lines] |
|
203 return max(lengths) / min(lengths) |
151 |
204 |
152 def is_real(number): |
205 def is_real(number): |
153 return isinstance(number, int) or isinstance(number, float) |
206 return isinstance(number, int) or isinstance(number, float) |
154 |
207 |
155 class Matrix3x3: |
208 class Matrix3x3: |