diff --git a/main.py b/main.py index 6ef1751..190bd5d 100644 --- a/main.py +++ b/main.py @@ -323,7 +323,7 @@ for i in range(len(p.coordinates)-1): if p.coordinates[i] != None: s = Segment2D(Point2D(p.coordinates[i].x, p.coordinates[i].y), Point2D( p.coordinates[i+1].x, p.coordinates[i+1].y)) - s.compute_thick_segment(ww, LINE_THICKNESS_MODE.MIDDLE) + s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) print(s.coordinates) for j in range(len(s.coordinates)-1): # editor.placeBlock( diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index dcd2cfe..1c17a7a 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -4,28 +4,52 @@ from typing import List class Circle: - def __init__(self, center: Point2D, inner: int, outer: int): + def __init__(self, center: Point2D): self.center = center - self.inner = inner - self.outer = outer + self.radius = None self.coordinates = [] - self.radius = None # Used with circle_points() + self.inner = None + self.outer = None + self.coordinates_thick = [] + + self.spaced_radius = None self.spaced_coordinates = [] - self.circle(self.center, self.inner, self.outer) - def __repr__(self): - return f"Circle(center: {self.center}, inner: {self.inner}, outer: {self.outer})" + return f"Circle(center: {self.center}, radius: {self.radius}, spaced_radius: {self.spaced_radius}, inner: {self.inner}, outer: {self.outer})" - def circle(self, center: Point2D, inner: int, outer: int) -> List[Point2D]: + def cirlce(self, radius: int) -> List[Point2D]: + self.radius = radius + center = self.center.copy() + + x = -radius + y = 0 + error = 2-2*radius + while (True): + self.coordinates.append(Point2D(center.x-x, center.y+y)) + self.coordinates.append(Point2D(center.x-y, center.y-x)) + self.coordinates.append(Point2D(center.x+x, center.y-y)) + self.coordinates.append(Point2D(center.x+y, center.y+x)) + r = error + if (r <= y): + y += 1 + error += y*2+1 + if (r > x or error > y): + x += 1 + error += x*2+1 + if (x < 0): + continue + else: + break + + def circle_thick(self, inner: int, outer: int) -> List[Point2D]: """Compute discrete value of a 2d-circle with thickness. - https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm + From: https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm Args: - center (Type[Point2D]): Center of the circle. Circles always have an odd diameter due to the central coordinate. inner (int): The minimum radius at which the disc is filled (included). outer (int): The maximum radius where disc filling stops (included). @@ -34,8 +58,14 @@ class Circle: >>> Circle(Point2D(0, 0), 5, 10) """ + + self.inner = inner + self.outer = outer + center = self.center.copy() + xo = outer xi = inner + y = 0 erro = 1 - xo erri = 1 - xi @@ -66,21 +96,23 @@ class Circle: else: xi -= 1 erri += 2 * (y - xi + 1) - return self.coordinates + return self.coordinates_thick - def circle_points(self, number: int, radius: int) -> List[Point2D]: + def circle_spaced(self, number: int, radius: int) -> List[Point2D]: """Get evenly spaced coordinates of the circle. - https://stackoverflow.com/questions/8487893/generate-all-the-points-on-the-circumference-of-a-circle + From: https://stackoverflow.com/questions/8487893/generate-all-the-points-on-the-circumference-of-a-circle Args: number (int): Number of coordinates to be returned. - radius (int, optional): Radius of the circle. Defaults to self.inner. + radius (int): Radius of the circle. Returns: list(Point2D): List of evenly spaced 2d-coordinates forming the circle. """ - print(self.center.x) + self.spaced_radius = radius + center = self.center + self.spaced_coordinates = [ Point2D(cos(2 * pi / number * i) * radius, sin(2 * pi / number * i) * radius) @@ -89,17 +121,17 @@ class Circle: for i in range(len(self.spaced_coordinates)): self.spaced_coordinates[i] = Point2D( - self.spaced_coordinates[i].x + self.center.x, - self.spaced_coordinates[i].y + self.center.y + self.spaced_coordinates[i].x + center.x, + self.spaced_coordinates[i].y + center.y ).round() return self.spaced_coordinates def _x_line(self, x1, x2, y): while x1 <= x2: - self.coordinates.append(Point2D(x1, y)) + self.coordinates_thick.append(Point2D(x1, y)) x1 += 1 def _y_line(self, x, y1, y2): while y1 <= y2: - self.coordinates.append(Point2D(x, y1)) + self.coordinates_thick.append(Point2D(x, y1)) y1 += 1 diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index a03ad31..4d2b49a 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -54,9 +54,6 @@ class Point2D: else: return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= d - def distance(self, point: "Point2D") -> int: - return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2) - def nearest(self, points: List["Point2D"]) -> "Point2D": """Return the nearest point. If multiple nearest point, returns the first in the list. @@ -182,6 +179,9 @@ class Point2D: self.coordinate = (self.x, self.y) return self + def distance(self, point: "Point2D") -> int: + return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2) + @staticmethod def to_vectors(points: List["Point3D"]) -> List[np.array]: vectors = [] diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index b4988de..03474de 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -13,14 +13,12 @@ class Point3D: def copy(self): return Point3D(self.x, self.y, self.z) - def coordinates(self): - return (self.x, self.y, self.z) - def __repr__(self): return f"Point3D(x: {self.x}, y: {self.y}, z: {self.z})" - def distance(self, point: "Point3D"): - return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2 + (point.z - self.z) ** 2) + def __eq__(self, other): + if isinstance(other, Point3D): + return self.x == other.x and self.y == other.y and self.z == other.z def nearest(self, points: List["Point3D"]) -> "Point3D": """Return the nearest point. If multiple nearest point, returns the first in the list. @@ -69,6 +67,9 @@ class Point3D: self.coordinate = (self.x, self.y, self.z) return self + def distance(self, point: "Point3D"): + return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2 + (point.z - self.z) ** 2) + @staticmethod def to_vectors(points: List["Point3D"]): vectors = [] diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index a1d9c48..15a1067 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -5,20 +5,20 @@ from math import sqrt class Segment2D: - def __init__(self, start: Point2D, end: Point2D, thickness_mode: LINE_THICKNESS_MODE = LINE_THICKNESS_MODE.MIDDLE): + def __init__(self, start: Point2D, end: Point2D): self.start = start self.end = end self.coordinates = [] + self.coordinates_thick = [] self.thickness = None - self.thickness_mode = thickness_mode def __repr__(self): return str(self.coordinates) - def compute_segment_overlap(self, start: Point2D, end: Point2D, overlap: LINE_OVERLAP): + def segment(self, overlap: LINE_OVERLAP = LINE_OVERLAP.NONE, is_computing_thickness: bool = False) -> List[Point2D]: """Modified Bresenham draw (line) with optional overlap. - From https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp + From: https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp Args: start (Point2D): Start point of the segment. @@ -28,8 +28,8 @@ class Segment2D: >>> Segment2D(Point2D(0, 0), Point2D(10, 15)) """ - start = start.copy() - end = end.copy() + start = self.start.copy() + end = self.end.copy() # Direction delta_x = end.x - start.x @@ -50,7 +50,7 @@ class Segment2D: delta_2x = 2*delta_x delta_2y = 2*delta_y - self.coordinates.append(start.copy()) + self._add_coordinates(start, is_computing_thickness) if (delta_x > delta_y): error = delta_2y - delta_x @@ -58,36 +58,39 @@ class Segment2D: start.x += step_x if (error >= 0): if (overlap == LINE_OVERLAP.MAJOR): - self.coordinates.append(start.copy()) + self._add_coordinates(start, is_computing_thickness) start.y += step_y if (overlap == LINE_OVERLAP.MINOR): - self.coordinates.append( - Point2D(start.copy().x - step_x, start.copy().y)) + self._add_coordinates( + Point2D(start.copy().x - step_x, start.copy().y), is_computing_thickness) error -= delta_2x error += delta_2y - self.coordinates.append(start.copy()) + self._add_coordinates(start, is_computing_thickness) else: error = delta_2x - delta_y while (start.y != end.y): start.y += step_y if (error >= 0): if (overlap == LINE_OVERLAP.MAJOR): - self.coordinates.append(start.copy()) + self._add_coordinates(start, is_computing_thickness) start.x += step_x if (overlap == LINE_OVERLAP.MINOR): - self.coordinates.append( - Point2D(start.copy().x, start.copy().y - step_y)) + self._add_coordinates( + Point2D(start.copy().x, start.copy().y - step_y), is_computing_thickness) error -= delta_2y error += delta_2x - self.coordinates.append(start.copy()) + self._add_coordinates(start, is_computing_thickness) - def compute_thick_segment(self, thickness: int, thickness_mode: LINE_THICKNESS_MODE): + if not is_computing_thickness: + return self.coordinates + + def segment_thick(self, thickness: int, thickness_mode: LINE_THICKNESS_MODE) -> List[Point2D]: """Bresenham with thickness. - From https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp - Probably inspired from Murphy's Modified Bresenham algorithm : http://zoo.co.uk/murphy/thickline/index.html + From: https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp + Murphy's Modified Bresenham algorithm : http://zoo.co.uk/murphy/thickline/index.html Args: start (Point2D): Start point of the segment. @@ -146,7 +149,8 @@ class Segment2D: error -= delta_2x error += delta_2x - self.compute_segment_overlap(start, end, LINE_OVERLAP.NONE) + self.segment( + start, end, LINE_OVERLAP.NONE, is_computing_thickness=True) error = delta_2x - delta_x for i in range(thickness, 1, -1): @@ -160,7 +164,8 @@ class Segment2D: overlap = LINE_OVERLAP.MAJOR error += delta_2y - self.compute_segment_overlap(start, end, overlap) + self.segment( + start, end, overlap, is_computing_thickness=True) else: if swap: @@ -180,7 +185,8 @@ class Segment2D: error -= delta_2y error += delta_2x - self.compute_segment_overlap(start, end, LINE_OVERLAP.NONE) + self.segment( + start, end, LINE_OVERLAP.NONE, is_computing_thickness=True) error = delta_2x - delta_y for i in range(thickness, 1, -1): @@ -194,7 +200,10 @@ class Segment2D: overlap = LINE_OVERLAP.MAJOR error += delta_2x - self.compute_segment_overlap(start, end, overlap) + self.segment( + start, end, overlap, is_computing_thickness=True) + + return self.coordinates def perpendicular(self, distance: int) -> List[Point2D]: """Compute perpendicular points from both side of the segment placed at start level. @@ -217,3 +226,14 @@ class Segment2D: x4 = self.start.x - (distance / 2) * dy y4 = self.start.y + (distance / 2) * dx return Point2D(x3, y3).round(), Point2D(x4, y4).round() + + def middle_point(self): + return (np.round((self.start.x + self.end.x) / 2.0).astype(int), + np.round((self.start.y + self.end.y) / 2.0).astype(int), + ) + + def _add_coordinates(self, coordinates, is_computing_thickness): + if is_computing_thickness: + self.coordinates_thick.append(coordinates.copy()) + else: + self.coordinates.append(coordinates.copy()) diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index 9322146..89fbe96 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -4,33 +4,28 @@ from networks.geometry.Point3D import Point3D class Segment3D: - def __init__(self, start: Point3D, end: Point3D, overlap: bool = True): + def __init__(self, start: Point3D, end: Point3D): self.start = start self.end = end self.coordinates = [] - self.overlap = overlap - - self._compute_segment(self.start, self.end, self.overlap) def __repr__(self): return str(self.coordinates) - def _compute_segment(self, start: Point3D, end: Point3D, overlap: bool = False): + def discrete_coordinates(self, overlap: bool = False): """Calculate a segment between two points in 3D space. 3d Bresenham algorithm. From: https://www.geeksforgeeks.org/bresenhams-algorithm-for-3-d-line-drawing/ Args: - start (Point3D): First coordinates. - end (Point3D): Second coordinates. overlap (bool, optional): If False, remove unnecessary coordinates connecting to other coordinates side by side, leaving only a diagonal connection. Defaults to False. >>> Segment3D(Point3D(0, 0, 0), Point3D(10, 10, 15)) """ self.coordinates.append(start.copy()) - dx = abs(end.x - start.x) - dy = abs(end.y - start.y) - dz = abs(end.z - start.z) + dx = abs(self.end.x - self.start.x) + dy = abs(self.end.y - self.start.y) + dz = abs(self.end.z - self.start.z) if end.x > start.x: xs = 1 else: @@ -109,3 +104,10 @@ class Segment3D: p2 -= 2 * dz p1 += 2 * dy p2 += 2 * dx + return self.coordinates + + def middle_point(self): + return (np.round((self.start.x + self.end.x) / 2.0).astype(int), + np.round((self.start.y + self.end.y) / 2.0).astype(int), + np.round((self.start.z + self.end.z) / 2.0).astype(int), + ) diff --git a/networks/geometry/segment_tools.py b/networks/geometry/segment_tools.py index e2c5b0a..b50a17a 100644 --- a/networks/geometry/segment_tools.py +++ b/networks/geometry/segment_tools.py @@ -54,121 +54,3 @@ def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): orthogonal = np.add(np.multiply(orthogonal, distance), origin).astype(int) return orthogonal - - -def discrete_segment(start_point, end_point, pixel_perfect=True): - """ - Calculate a line between two points in 3D space. - - https://www.geeksforgeeks.org/bresenhams-algorithm-for-3-d-line-drawing/ - - Args: - start_point (tuple): (x, y, z) First coordinates. - end_point (tuple): (x, y, z) Second coordinates. - pixel_perfect (bool, optional): If true, remove unnecessary coordinates connecting to other coordinates side by side, leaving only a diagonal connection. Defaults to True. - - Returns: - list: List of coordinates. - """ - (x1, y1, z1) = start_point - (x2, y2, z2) = end_point - x1, y1, z1, x2, y2, z2 = ( - round(x1), - round(y1), - round(z1), - round(x2), - round(y2), - round(z2), - ) - - points = [] - points.append((x1, y1, z1)) - dx = abs(x2 - x1) - dy = abs(y2 - y1) - dz = abs(z2 - z1) - if x2 > x1: - xs = 1 - else: - xs = -1 - if y2 > y1: - ys = 1 - else: - ys = -1 - if z2 > z1: - zs = 1 - else: - zs = -1 - - # Driving axis is X-axis - if dx >= dy and dx >= dz: - p1 = 2 * dy - dx - p2 = 2 * dz - dx - while x1 != x2: - x1 += xs - points.append((x1, y1, z1)) - if p1 >= 0: - y1 += ys - if not pixel_perfect: - if points[-1][1] != y1: - points.append((x1, y1, z1)) - p1 -= 2 * dx - if p2 >= 0: - z1 += zs - if not pixel_perfect: - if points[-1][2] != z1: - points.append((x1, y1, z1)) - p2 -= 2 * dx - p1 += 2 * dy - p2 += 2 * dz - - # Driving axis is Y-axis - elif dy >= dx and dy >= dz: - p1 = 2 * dx - dy - p2 = 2 * dz - dy - while y1 != y2: - y1 += ys - points.append((x1, y1, z1)) - if p1 >= 0: - x1 += xs - if not pixel_perfect: - if points[-1][0] != x1: - points.append((x1, y1, z1)) - p1 -= 2 * dy - if p2 >= 0: - z1 += zs - if not pixel_perfect: - if points[-1][2] != z1: - points.append((x1, y1, z1)) - p2 -= 2 * dy - p1 += 2 * dx - p2 += 2 * dz - - # Driving axis is Z-axis - else: - p1 = 2 * dy - dz - p2 = 2 * dx - dz - while z1 != z2: - z1 += zs - points.append((x1, y1, z1)) - if p1 >= 0: - y1 += ys - if not pixel_perfect: - if points[-1][1] != y1: - points.append((x1, y1, z1)) - p1 -= 2 * dz - if p2 >= 0: - x1 += xs - if not pixel_perfect: - if points[-1][0] != x1: - points.append((x1, y1, z1)) - p2 -= 2 * dz - p1 += 2 * dy - p2 += 2 * dx - return points - - -def middle_point(start_point, end_point): - return (np.round((start_point[0] + end_point[0]) / 2.0).astype(int), - np.round((start_point[1] + end_point[1]) / 2.0).astype(int), - np.round((start_point[2] + end_point[2]) / 2.0).astype(int), - )