--- a/geometry.py Wed Dec 20 17:25:09 2017 +0200 +++ b/geometry.py Thu Dec 21 10:46:41 2017 +0200 @@ -1,3 +1,21 @@ +def degree_rep(angle): + from math import degrees + return '∠ %.2f°' % degrees(angle) + +def position_vector(vertex): + assert isinstance(vertex, Vertex) + return Vector(*vertex.coordinates) + +def angle_magnitude_key(angle): + ''' + Returns how great an angle is. + ''' + from math import pi as π + normalized_angle = abs(angle) % (2 * π) + if normalized_angle > π: + normalized_angle = (2 * π) - normalized_angle + return normalized_angle + class Vertex: def __init__(self, x, y, z): if not all(is_real(coordinate) for coordinate in (x, y, z)): @@ -19,18 +37,32 @@ def coordinates(self): return self.x, self.y, self.z def __add__(self, other): - return type(self)(self.x + other.x, self.y + other.y, self.z + other.z) + assert not (type(self) == type(other) == Vertex) + if type(self) == Vector and type(other) == Vector: + result_type = Vector + else: + result_type = Vertex + return result_type(self.x + other.x, self.y + other.y, self.z + other.z) + def __radd__(self, other): + return self + other def __neg__(self): return type(self)(-self.x, -self.y, -self.z) def __sub__(self, other): - return self + (-other) + result = self + (-position_vector(other)) + if isinstance(other, Vertex): + return Vector(*result.coordinates) + else: + return result def __mul__(self, scalar): + assert is_real(scalar) return type(self)(self.x * scalar, self.y * scalar, self.z * scalar) def __rmul__(self, other): return self * other def __truediv__(self, scalar): + assert is_real(scalar) return type(self)(self.x / scalar, self.y / scalar, self.z / scalar) def __floordiv__(self, scalar): + assert is_real(scalar) return type(self)(self.x // scalar, self.y // scalar, self.z // scalar) def __matmul__(self, transformation_matrix): return transform(self, transformation_matrix) @@ -53,6 +85,9 @@ def __repr__(self): return str.format('LineSegment({!r}, {!r})', self.v1, self.v2) @property + def length(self): + return self.v1.distance_to(self.v2) + @property def vertices(self): return self.v1, self.v2 def __len__(self): @@ -70,7 +105,7 @@ def __init__(self, x, y, z): super().__init__(x, y, z) def __repr__(self): - return str.format('Vector({!r}, {!r}. {!r})', self.x, self.y, self.z) + return str.format('Vector({!r}, {!r}, {!r})', self.x, self.y, self.z) def length(self): from math import sqrt return sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) @@ -148,6 +183,24 @@ return self.vertices[index % len(self.vertices)] def __len__(self): return len(self.vertices) + @property + def perimeter_lines(self): + for v1, v2 in pairs(self.vertices): + yield LineSegment(v1, v2) + @property + def smallest_angle(self): + from math import acos, inf + min_angle = inf + for v1, v2, v3 in pairs(self.vertices, count = 3): + vec1 = (position_vector(v3) - position_vector(v2)).normalized() + vec2 = (position_vector(v1) - position_vector(v2)).normalized() + cosine = dot_product(vec1, vec2) / vec1.length() / vec2.length() + min_angle = min(min_angle, angle_magnitude_key(acos(cosine))) + return min_angle + @property + def hairline_ratio(self): + lengths = [line.length for line in self.perimeter_lines] + return max(lengths) / min(lengths) def is_real(number): return isinstance(number, int) or isinstance(number, float)