class LinearTransform(object): def __init__(self, x_axis=None, y_axis=None): self.x_axis = x_axis if x_axis is not None else Vector(1.0, 0.0) self.y_axis = y_axis if y_axis is not None else Vector(0.0, 1.0) def Copy(self): return LinearTransform(self.x_axis.Copy(), self.y_axis.Copy()) def Serialize(self): json_data = { 'x_axis': self.x_axis.Serialize(), 'y_axis': self.y_axis.Serialize() } return json_data def Deserialize(self, json_data): self.x_axis = Vector().Deserialize(json_data['x_axis']) self.y_axis = Vector().Deserialize(json_data['y_axis']) return self def IsTransform(self, transform, epsilon=1e-7): return self.x_axis.IsPoint(transform.x_axis, epsilon) and self.y_axis.IsPoint( transform.y_axis, epsilon) def Identity(self): self.x_axis = Vector(1.0, 0.0) self.y_axis = Vector(0.0, 1.0) def IsIdentity(self, epsilon=1e-7): if not self.x_axis.IsPoint(Vector(1.0, 0.0), epsilon): return False if not self.y_axis.IsPoint(Vector(0.0, 1.0), epsilon): return False return True def Transform(self, object): if isinstance(object, Vector): return self.x_axis * object.x + self.y_axis * object.y elif isinstance(object, LinearTransform): transform = LinearTransform() transform.x_axis = self.Transform(object.x_axis) transform.y_axis = self.Transform(object.y_axis) return transform def __call__(self, object): return self.Transform(object) def __mul__(self, other): return self.Transform(other) def Determinant(self): return self.x_axis.Cross(self.y_axis) def Invert(self): inverse = self.Inverted() if inverse is None: return False else: self.x_axis = inverse.x_axis self.y_axis = inverse.y_axis return True def Inverted(self): try: det = self.Determinant() inverse = LinearTransform() scale = 1.0 / det inverse.x_axis = Vector(self.y_axis.y, -self.x_axis.y) * scale inverse.y_axis = Vector(-self.y_axis.x, self.x_axis.x) * scale return inverse except ZeroDivisionError: return None def Rotation(self, angle): self.Identity() self.x_axis = self.x_axis.Rotated(angle) self.y_axis = self.y_axis.Rotated(angle) def Reflection(self, vector): self.Identity() self.x_axis = self.x_axis.Reflected(vector) self.y_axis = self.y_axis.Reflected(vector) def Scale(self, x_scale, y_scale): self.x_axis = Vector(x_scale, 0.0) self.y_axis = Vector(0.0, y_scale) def Decompose(self): pass
class AffineTransform(object): def __init__(self, x_axis=None, y_axis=None, translation=None): self.linear_transform = LinearTransform(x_axis, y_axis) self.translation = translation if translation is not None else Vector( 0.0, 0.0) def Copy(self): return AffineTransform(self.linear_transform.x_axis.Copy(), self.linear_transform.y_axis.Copy(), self.translation.Copy()) def Serialize(self): json_data = { 'linear_transform': self.linear_transform.Serialize(), 'translation': self.translation.Serialize() } return json_data def Deserialize(self, json_data): self.linear_transform = LinearTransform().Deserialize( json_data['linear_transform']) self.translation = Vector().Deserialize(json_data['translation']) return self def IsTransform(self, transform, epsilon=1e-7): if not self.linear_transform.IsTransform(transform.linear_transform, epsilon): return False if not self.translation.IsPoint(transform.translation, epsilon): return False return True def Identity(self): self.linear_transform.Identity() self.translation = Vector(0.0, 0.0) def IsIdentity(self, epsilon=1e-7): if not self.linear_transform.IsIdentity(epsilon): return False return True if self.translation.IsZero(epsilon) else False def Transform(self, object): from math2d_polygon import Polygon from math2d_region import Region, SubRegion from math2d_aa_rect import AxisAlignedRectangle if isinstance(object, Vector): return self.linear_transform.Transform(object) + self.translation elif isinstance(object, AffineTransform): transform = AffineTransform() transform.linear_transform.x_axis = self.linear_transform.Transform( object.linear_transform.x_axis) transform.linear_transform.y_axis = self.linear_transform.Transform( object.linear_transform.y_axis) transform.translation = self.Transform(object.translation) return transform elif isinstance(object, LineSegment): return LineSegment(self.Transform(object.point_a), self.Transform(object.point_b)) elif isinstance(object, Polygon): polygon = Polygon() for vertex in object.vertex_list: polygon.vertex_list.append(self.Transform(vertex)) return polygon elif isinstance(object, Region): region = Region() for sub_region in object.sub_region_list: region.sub_region_list.append(self.Transform(sub_region)) return region elif isinstance(object, SubRegion): sub_region = SubRegion() sub_region.polygon = self.Transform(object.polygon) for hole in object.hole_list: sub_region.hole_list.append(self.Transform(hole)) return sub_region elif isinstance(object, AxisAlignedRectangle): return AxisAlignedRectangle( min_point=self.Transform(object.min_point), max_point=self.Transform(object.max_point)) def __call__(self, object): return self.Transform(object) def __mul__(self, other): return self.Transform(other) def Determinant(self): return self.linear_transform.Determinant() def Invert(self): self.linear_transform.Invert() self.translation = -self.linear_transform.Transform(self.translation) def Inverted(self): inverse = self.Copy() inverse.Invert() return inverse def Rotation(self, center, angle): translation_a = AffineTransform(None, None, -center) translation_b = AffineTransform(None, None, center) rotation = AffineTransform() rotation.linear_transform.Rotation(angle) transform = translation_b * rotation * translation_a self.linear_transform = transform.linear_transform self.translation = transform.translation def Reflection(self, center, vector): translation_a = AffineTransform(None, None, -center) translation_b = AffineTransform(None, None, center) reflection = AffineTransform() reflection.linear_transform.Reflection(vector) transform = translation_b * reflection * translation_a self.linear_transform = transform.linear_transform self.translation = transform.translation def Translation(self, translation): self.linear_transform.Identity() self.translation = translation def RigidBodyMotion(self, rotation_angle, translation): self.linear_transform.Rotation(rotation_angle) self.translation = translation def Decompose(self): pass
class LineSegment(object): def __init__(self, point_a=None, point_b=None): self.point_a = point_a if point_a is not None else Vector(0.0, 0.0) self.point_b = point_b if point_b is not None else Vector(0.0, 0.0) def Serialize(self): json_data = { 'point_a': self.point_a.Serialize(), 'point_b': self.point_b.Serialize() } return json_data def Deserialize(self, json_data): self.point_a = Vector().Deserialize(json_data['point_a']) self.point_b = Vector().Deserialize(json_data['point_b']) return self def Copy(self): return LineSegment(self.point_a.Copy(), self.point_b.Copy()) def Direction(self): return self.point_b - self.point_a def Length(self): return self.Direction().Length() def Lerp(self, lerp_value): return self.point_a + (self.point_b - self.point_a) * lerp_value def LerpValue(self, point, epsilon=1e-7): try: vector_a = self.point_b - self.point_a vector_b = point - self.point_a cross = vector_a.Cross(vector_b) if math.fabs(cross) >= epsilon: return None lerp_value = vector_a.Dot(vector_b) / vector_a.Dot(vector_a) return lerp_value except ZeroDivisionError: return None def ContainsPoint(self, point, epsilon=1e-7): lerp_value = self.LerpValue(point, epsilon) if lerp_value is None: return False return True if -epsilon < lerp_value < 1.0 + epsilon else False def IsEndPoint(self, point, epsilon=1e-7): return self.point_a.IsPoint(point, epsilon) or self.point_b.IsPoint( point, epsilon) def IntersectWith(self, other, epsilon=1e-7): if isinstance(other, LineSegment): numer_a = (other.point_b - other.point_a).Cross(self.point_a - other.point_a) numer_b = (self.point_b - self.point_a).Cross(other.point_a - self.point_a) denom = (self.point_b - self.point_a).Cross(other.point_b - other.point_a) try: lerp_value_a = numer_a / denom lerp_value_b = numer_b / -denom except ZeroDivisionError: return None if -epsilon <= lerp_value_a <= 1.0 + epsilon and -epsilon <= lerp_value_b <= 1.0 + epsilon: point = self.Lerp(lerp_value_a) # Let's scrutinize further. Two disjoint and parallel line segments sometimes warrant this. if self.ContainsPoint(point, epsilon) and other.ContainsPoint( point, epsilon): return point else: return None def Distance(self, point): return (point - self.ClosestPoint(point)).Length() def ClosestPoint(self, point): vector = (self.point_b - self.point_a).Normalized() length = (point - self.point_a).Dot(vector) if length < 0.0: return self.point_a elif length > self.Length(): return self.point_b else: return self.point_a + vector * length @staticmethod def ReduceLineList(given_line_list, epsilon): if len(given_line_list) < 2: return given_line_list else: i = int( len(given_line_list) / 2 if len(given_line_list) % 2 == 0 else (len(given_line_list) + 1) / 2) line_list_a = given_line_list[0:i] line_list_b = given_line_list[i:] line_list_a = LineSegment.ReduceLineList(line_list_a, epsilon) line_list_b = LineSegment.ReduceLineList(line_list_b, epsilon) pair_queue = [] for i in range(len(line_list_a)): for j in range(len(line_list_b)): pair_queue.append((i, j)) while (len(pair_queue) > 0): pair = pair_queue.pop() line_seg_a = line_list_a[pair[0]] line_seg_b = line_list_b[pair[1]] line_seg = LineSegment.CompressLineSegments( line_seg_a, line_seg_b, epsilon) if line_seg is not None: line_list_a[pair[0]] = None line_list_b[pair[1]] = None pair_queue = [ queued_pair for queued_pair in pair_queue if queued_pair[0] != pair[0] and queued_pair[1] != pair[1] ] line_list_b.append(line_seg) # Arbitrarily choose list B. for i in range(len(line_list_a)): if line_list_a[i] is not None: pair_queue.append((i, len(line_list_b) - 1)) line_list_a = [line for line in line_list_a if line is not None] line_list_b = [line for line in line_list_b if line is not None] return line_list_a + line_list_b @staticmethod def CompressLineSegments(line_seg_a, line_seg_b, epsilon): if line_seg_a.point_a.IsPoint(line_seg_b.point_a, epsilon) and line_seg_a.point_b.IsPoint( line_seg_b.point_b, epsilon): return line_seg_a elif line_seg_a.point_a.IsPoint( line_seg_b.point_b, epsilon) and line_seg_a.point_b.IsPoint( line_seg_b.point_a, epsilon): return line_seg_a mid_point = None if line_seg_a.IsEndPoint(line_seg_b.point_a): mid_point = line_seg_b.point_a elif line_seg_a.IsEndPoint(line_seg_b.point_b): mid_point = line_seg_b.point_b if mid_point is not None: point_a = line_seg_a.point_a if not line_seg_a.point_a.IsPoint( mid_point, epsilon) else line_seg_a.point_b point_b = line_seg_b.point_a if not line_seg_b.point_a.IsPoint( mid_point, epsilon) else line_seg_b.point_b new_line_seg = LineSegment(point_a=point_a, point_b=point_b) distance = new_line_seg.Distance(mid_point) if distance < epsilon: return new_line_seg