Source code for Geometry3D.geometry.polygon

# -*- coding: utf-8 -*-
"""Polygon Module"""
from .body import GeoBody
from .point import Point
from ..utils.vector import Vector
from .line import Line
from .segment import Segment
from .plane import Plane
from ..utils.constant import *
from ..utils.vector import x_unit_vector,y_unit_vector

import copy
import math

[docs]def get_circle_point_list(center,normal,radius,n=10): if n <= 2: raise ValueError("n must be at least 3 to construct an inscribed polygon for a circle") import math,copy if normal.angle(x_unit_vector()) < SMALL_ANGLE: base_vector = y_unit_vector() if normal.angle(y_unit_vector()) < SMALL_ANGLE: raise ValueError("Bug detected! please contact the author") else: base_vector = x_unit_vector() v1 = normal.normalized().cross(base_vector).normalized() v2 = normal.normalized().cross(v1) v1 = v1 * radius v2 = v2 * radius point_list = [] for i in range(n): angle_i = math.pi * 2 / n * i point_list.append(copy.deepcopy(center).move(v1 * math.cos(angle_i) + v2 * math.sin(angle_i))) return point_list
def get_triangle_area(pa,pb,pc): """ **Input:** - pa: a Point - pb: a Point - pc: a Point **Output:** The area of the triangle composed by pa, pb and pc. Heron's formula is used """ a = pa.distance(pb) b = pb.distance(pc) c = pc.distance(pa) p = (a + b + c) / 2 return math.sqrt(p * (p - a) * (p - b) * (p - c))
[docs]class ConvexPolygon(GeoBody): """ - ConvexPolygons(points) points: a tuple of points. The points needn't to be in order. The convexity should be guaranteed. This function **will not** check the convexity. If the Polygon is not convex, there might be errors. """ class_level = 4 # the class level of ConvexPolygon
[docs] @classmethod def Circle(cls,center,normal,radius,n=10): """ A special function for creating an inscribed convex polygon of a circle **Input:** - Center: The center point of the circle - normal: The normal vector of the circle - radius: The radius of the circle - n=10: The number of Points of the ConvexPolygon **Output:** - An inscribed convex polygon of a circle. """ return cls(get_circle_point_list(center,normal,radius,n))
[docs] @classmethod def Parallelogram(cls,base_point,v1,v2): """ A special function for creating Parallelogram **Input:** - base_point: a Point - v1, v2: two Vectors **Output:** - A parallelogram which is a ConvexPolygon instance. """ if isinstance(base_point,Point) and isinstance(v1,Vector) and isinstance(v2,Vector): if v1.length() == 0 or v2.length == 0: raise ValueError("The length for the two vector shouldn't be zero") elif v1.parallel(v2): raise ValueError("The two vectors shouldn't be parallel to each other") else: return cls(( base_point, copy.deepcopy(base_point).move(v1), copy.deepcopy(base_point).move(v2), copy.deepcopy(base_point).move(v1).move(v2) )) else: raise TypeError("Parallelogram should be initialized with Point, Vector and Vector, but the given types are %s, %s and %s" %(type(base_point),type(v1),type(v2)))
def __init__(self,pts,reverse = False, check_convex=False): # merge same points points = copy.deepcopy(pts) self.points = sorted(set(points),key=points.index) if len(points) < 3: raise ValueError('Cannot build a polygon with number of points smaller than 3') if reverse: self.plane = -Plane(self.points[0],self.points[1],self.points[2]) else: self.plane = Plane(self.points[0],self.points[1],self.points[2]) self.center_point = self._get_center_point() self._check_and_sort_points()
[docs] def segments(self): """ **Input:** - self **Output:** - iterator of segments """ for i in range(len(self.points)): index_0 = i if i == len(self.points) - 1: index_1 = 0 else: index_1 = i + 1 yield Segment(self.points[index_0],self.points[index_1])
def _get_center_point(self): """ **Input:** - points: tuple of points **Output:** - The center point of given points """ x,y,z =(0,0,0) num_points = len(self.points) for point in self.points: x += point.x y += point.y z += point.z return Point(float(x) / num_points,float(y) / num_points,float(z) / num_points)
[docs] def area(self): """ **Input:** - self **Output:** - The area of the convex polygon """ area = 0 for i in range(len(self.points)): index_0 = i if i == len(self.points) - 1: index_1 = 0 else: index_1 = i + 1 area += get_triangle_area(self.center_point,self.points[index_0],self.points[index_1]) return area
def _check_and_sort_points(self): """ **Input:** -self **Output:** - True for check passed - False for check not passed This is only a **weak** check, passing the check doesn't guarantee it is a convex polygon """ the_normal = (self.plane.n).normalized() v0 = Vector(self.center_point,self.points[0]).normalized() v1 = the_normal.cross(v0) angle_point_dict = dict() # dict[angle] = Point for point in self.points: if not point in self.plane: raise ValueError('Convex Check Fails Because {} Is Not On {}'.format(point,self.plane)) pv = point.pv() - self.center_point.pv() y_coordinate = pv * v0 z_coordinate = pv * v1 # the range of vector_angle is [0,2*pi) vector_angle = math.atan2(z_coordinate,y_coordinate) if vector_angle < 0: vector_angle += 2 * math.pi angle_point_dict[vector_angle] = point point_list = [angle_point_dict[angle] for angle in sorted(angle_point_dict)] self.points = tuple(point_list) return True def __repr__(self): return "ConvexPolygon({})".format(self.points) def __contains__(self, other): """Checks if a point or segment lies in a ConvexPolygon""" if isinstance(other,Point): r1 = other in self.plane # requirement 1: the point is on the plane the_normal = self.plane.n.normalized() r2 = True # requirement 2: the point is inside the polygon for i in range(len(self.points)): # check if the point lies in the inside direction of every segment index_0 = i if i == len(self.points) - 1: index_1 = 0 else: index_1 = i + 1 # self.points[index_0] and self.points[index_1] is the i^th segment v0 = Vector(self.points[index_0],self.points[index_1]) v1 = the_normal.cross(v0) vec = Vector(self.points[index_0],other) if vec * v1 < - get_eps(): r2 = False break return r1 and r2 elif isinstance(other,Segment): return (other.start_point in self) and (other.end_point in self) else: return NotImplementedError("")
[docs] def in_(self,other): """ **Input:** - self: ConvexPolygon - other: Plane **Output:** - whether self in other """ if isinstance(other,Plane): return self.plane == other else: raise NotImplementedError("")
def __eq__(self,other): if isinstance(other,ConvexPolygon): return (hash(self) == hash(other)) else: return False def _get_point_hash_sum(self): """return the sum of hash of all points""" hash_sum = 0 for point in self.points: hash_sum += hash(point) return hash_sum # the hash function is not accurate # in some extreme case, this function may fail # which means it's vulnerable to attacks. def __hash__(self): """return the has of the convexpolygon""" return hash(("ConvexPolygon", round(self._get_point_hash_sum(),SIG_FIGURES), hash(self.plane) + hash(-self.plane), hash(self.plane) * hash(-self.plane) ))
[docs] def eq_with_normal(self,other): """return whether self equals with other considering the normal""" if isinstance(other,ConvexPolygon): return (self.hash_with_normal() == other.hash_with_normal()) else: return False
[docs] def hash_with_normal(self): """return the hash value considering the normal""" return hash(("ConvexPolygon", round(self._get_point_hash_sum(),SIG_FIGURES-5), hash(self.plane) ))
def __neg__(self): """return the negative ConvexPolygon by reverting the normal""" return ConvexPolygon(self.points,reverse=True)
[docs] def length(self): """return the total length of ConvexPolygon""" length = 0 for segment in self.segments(): length += segment.length() return length
[docs] def move(self,v): """Return the ConvexPolygon that you get when you move self by vector v, self is also moved""" if isinstance(v,Vector): point_list = [] for point in self.points: point_list.append(point.move(v)) self.points = tuple(point_list) self.plane = Plane(self.points[0],self.points[1],self.points[2]) self.center_point = self._get_center_point() return ConvexPolygon(self.points) else: raise NotImplementedError("The second parameter for move function must be Vector")
Parallelogram = ConvexPolygon.Parallelogram Circle = ConvexPolygon.Circle __all__ = ("ConvexPolygon","Parallelogram","get_circle_point_list","Circle")