--- a/geometry.py Tue Dec 12 13:46:17 2017 +0200 +++ b/geometry.py Wed Dec 20 17:25:09 2017 +0200 @@ -19,19 +19,19 @@ def coordinates(self): return self.x, self.y, self.z def __add__(self, other): - return Vertex(self.x + other.x, self.y + other.y, self.z + other.z) + return type(self)(self.x + other.x, self.y + other.y, self.z + other.z) def __neg__(self): - return Vertex(-self.x, -self.y, -self.z) + return type(self)(-self.x, -self.y, -self.z) def __sub__(self, other): return self + (-other) def __mul__(self, scalar): - return Vertex(self.x * scalar, self.y * scalar, self.z * scalar) + return type(self)(self.x * scalar, self.y * scalar, self.z * scalar) def __rmul__(self, other): return self * other def __truediv__(self, scalar): - return Vertex(self.x / scalar, self.y / scalar, self.z / scalar) + return type(self)(self.x / scalar, self.y / scalar, self.z / scalar) def __floordiv__(self, scalar): - return Vertex(self.x // scalar, self.y // scalar, self.z // scalar) + return type(self)(self.x // scalar, self.y // scalar, self.z // scalar) def __matmul__(self, transformation_matrix): return transform(self, transformation_matrix) def __eq__(self, other): @@ -55,17 +55,104 @@ @property def vertices(self): return self.v1, self.v2 + def __len__(self): + return 2 + def __getitem__(self, index): + return self.vertices[index] + +class IndexRing: + def __init__(self, container): + self.container = container + def __getitem__(self, index): + return self.container[index % len(self.container)] + +class Vector(Vertex): + 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) + def length(self): + from math import sqrt + return sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) + def normalized(self): + length = self.length() + if length > 0: + return Vector(self.x / length, self.y / length, self.z / length) + else: + return Vector(0, 0, 0) + +def dot_product(v1, v2): + ''' Returns the dot product v1 · v2. ''' + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z + +def cross_product(v1, v2): + ''' Returns the cross product v1 × v2. ''' + return Vector( + x = v1.y * v2.z - v1.z * v2.y, + y = v1.z * v2.x - v1.x * v2.z, + z = v1.x * v2.y - v1.y * v2.x, + ) + +def unit_normal(a, b, c): + x = Matrix3x3([ + 1, a.y, a.z, + 1, b.y, b.z, + 1, c.y, c.z, + ]).determinant() + y = Matrix3x3([ + a.x, 1, a.z, + b.x, 1, b.z, + c.x, 1, c.z, + ]).determinant() + z = Matrix3x3([ + a.x, a.y, 1, + b.x, b.y, 1, + c.x, c.y, 1, + ]).determinant() + return Vector(x, y, z).normalized() + +def pairs(iterable, *, count = 2): + ''' + Iterates the given iterable and returns tuples containing `count` + sequential values in the iterable. + + Formally, for a vector A with indices 0…n and pair count k, this + function yields: + (A₀, A₁, …, Aₖ), + (A₁, A₂, …, Aₖ₊₁), + …, + (Aₙ₋₂, Aₙ₋₁, …, Aₖ₋₂), + (Aₙ₋₁, A₀, …, Aₖ₋₁). + ''' + iterable_count = len(iterable) + for index in range(iterable_count): + yield tuple( + iterable[(index + offset) % iterable_count] + for offset in range(count) + ) class Polygon: def __init__(self, vertices): self.vertices = vertices def __repr__(self): return str.format('Polygon({!r})', self.vertices) + def area(self): + total = Vector(0, 0, 0) + for v1, v2 in pairs(self.vertices): + total += cross_product(v1, v2) + return dot_product(total, unit_normal(self.vertices[0], self.vertices[1], self.vertices[2])) + def centroid(self): + ... + raise NotImplementedError + def __getitem__(self, index): + return self.vertices[index % len(self.vertices)] + def __len__(self): + return len(self.vertices) def is_real(number): return isinstance(number, int) or isinstance(number, float) -class TransformationMatrix: +class Matrix3x3: ''' A 3×3 matrix forming the top-left portion of a full 4×4 transformation matrix. @@ -75,9 +162,19 @@ assert len(values) == 9 self.values = values def __repr__(self): - return str.format('TransformationMatrix({!r})', self.values) + return str.format('Matrix3x3({!r})', self.values) def __getitem__(self, index): return self.values[index] + def determinant(self): + v = self.values + return sum([ + +(v[0] * v[4] * v[8]), + +(v[1] * v[5] * v[6]), + +(v[2] * v[3] * v[7]), + -(v[2] * v[4] * v[6]), + -(v[1] * v[3] * v[8]), + -(v[0] * v[5] * v[7]), + ]) def complete_matrix(matrix, anchor): '''