From d218a67f4250362ab9bdd9383113d477874a3cc9 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sat, 20 Apr 2024 20:34:53 +0200 Subject: [PATCH 01/69] Add orthogonal and parallel --- main.py | 18 +++++++----- networks/{curve.py => Curve.py} | 18 ++++++------ networks/Segment.py | 44 +++++++++++++++++++++++++++++ networks/roads/{road.py => Road.py} | 0 4 files changed, 64 insertions(+), 16 deletions(-) rename networks/{curve.py => Curve.py} (73%) create mode 100644 networks/Segment.py rename networks/roads/{road.py => Road.py} (100%) diff --git a/main.py b/main.py index 1f43c45..f21ea28 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ from gdpc import Editor, Block, geometry -import networks.curve as curve +import networks.Curve as curve +import networks.Segment as segment import numpy as np editor = Editor(buffering=True) @@ -13,10 +14,13 @@ editor = Editor(buffering=True) # # Build a cube # geometry.placeCuboid(editor, (458, 92, 488), (468, 99, 471), Block("oak_planks")) -curve = curve.Curve([(396, 132, 740), (435, 138, 730), - (443, 161, 758), (417, 73, 729)]) -curve.compute_curve() +# curve = curve.Curve([(396, 132, 740), (435, 138, 730), +# (443, 161, 758), (417, 73, 729)]) +# curve.compute_curve() -for point in curve.computed_points: - print(point) - editor.placeBlock(point, Block("stone")) +# for point in curve.computed_points: +# print(point) +# editor.placeBlock(point, Block("stone")) + +print(segment.parrallel(((0, 0, 0), (0, 0, 10)), 10)) +print(segment.orthogonal((0, 0, 0), (1, 0, 0), 10)) diff --git a/networks/curve.py b/networks/Curve.py similarity index 73% rename from networks/curve.py rename to networks/Curve.py index 01f0c89..74bdbcc 100644 --- a/networks/curve.py +++ b/networks/Curve.py @@ -5,21 +5,17 @@ from scipy import interpolate class Curve: def __init__(self, target_points): # list of points to [(x1, y1, z1), (...), ...] - self.target_points = target_points - self.computed_points = [] + self.computed_points = compute_curve(target_points) - def compute_curve(self, resolution=40): + @staticmethod + def compute_curve(self, target_points, resolution=40): """ Fill self.computed_points with a list of points that approximate a smooth curve following self.target_points. https://stackoverflow.com/questions/18962175/spline-interpolation-coefficients-of-a-line-curve-in-3d-space - - Args: - points (np.array): Points where the curve should pass in order. - resolution (int, optional): Total number of points to compute. Defaults to 40. """ # Remove duplicates. Curve can't intersect itself - points = tuple(map(tuple, np.array(self.target_points))) + points = tuple(map(tuple, np.array(target_points))) points = sorted(set(points), key=points.index) # Change coordinates structure to (x1, x2, x3, ...), (y1, y2, y3, ...) (z1, z2, z3, ...) @@ -38,5 +34,9 @@ class Curve: y_rounded = np.round(y_fine).astype(int) z_rounded = np.round(z_fine).astype(int) - self.computed_points = [(x, y, z) for x, y, z in zip( + return [(x, y, z) for x, y, z in zip( x_rounded, y_rounded, z_rounded)] + + @staticmethod + def offset(self): + pass diff --git a/networks/Segment.py b/networks/Segment.py new file mode 100644 index 0000000..a0cd8e6 --- /dev/null +++ b/networks/Segment.py @@ -0,0 +1,44 @@ +import numpy as np + + +def parallel(segment, distance, normal=np.array([0, 1, 0])): + """Get parallel segment in 3D space at a distance. + + Args: + segment (np.array, np.array): start and end points of the segement. + distance (int): distance between both segment. Thickness in the context of a line. Positive direction means left. + + Returns: + (np.array(), np.array()): parallel segment. + """ + return (orthogonal(segment[0], segment[1], distance, normal), orthogonal(segment[1], segment[0], -distance, normal)) + + +def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): + """Get orthogonal point from a given one at the specified distance in 3D space with normal direction. + + Args: + origin (tuple or np.array): origin + point (tuple or np.array): (point-origin) makes the first vector. Only the direction is used. + distance (int): distance from the origin. Thickness in the context of a line. Positive direction means left. + normal (list or np.array, optional): second vector. Defaults to the vertical [0, 1, 0]. + + Raises: + ValueError: if vectors are not linearly independent. + + Returns: + np.array: (x y z) + + >>>orthogonal((5, 5, 5), (150, 5, 5), 10) + [ 5. 5. 15.] + """ + vector = np.subtract(point, origin) + magnitude = np.linalg.norm(vector) + normalized_vector = vector / magnitude + orthogonal = np.cross(normalized_vector, normal) + + if np.array_equal(orthogonal, np.zeros((3,))): + raise ValueError("The input vectors are not linearly independent.") + + orthogonal = np.add(np.multiply(orthogonal, distance), origin) + return orthogonal diff --git a/networks/roads/road.py b/networks/roads/Road.py similarity index 100% rename from networks/roads/road.py rename to networks/roads/Road.py From 28403f83bf59f9b2912896bb0f6333f5efc9371d Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sat, 20 Apr 2024 22:51:09 +0200 Subject: [PATCH 02/69] Add curvature --- main.py | 24 ++++++++- networks/Curve.py | 109 ++++++++++++++++++++++++++----------- networks/Segment.py | 128 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 224 insertions(+), 37 deletions(-) diff --git a/main.py b/main.py index f21ea28..8bd89af 100644 --- a/main.py +++ b/main.py @@ -22,5 +22,25 @@ editor = Editor(buffering=True) # print(point) # editor.placeBlock(point, Block("stone")) -print(segment.parrallel(((0, 0, 0), (0, 0, 10)), 10)) -print(segment.orthogonal((0, 0, 0), (1, 0, 0), 10)) + +# print(segment.parallel(((0, 0, 0), (0, 0, 10)), 10)) +# print(segment.orthogonal((0, 0, 0), (1, 0, 0), 10)) +# print(curve.curvature(np.array(([0, 0, 0], [0, 1, 1], [1, 0, 1])))) + + +curve_points = curve.curve( + [(390, 150, 788), (368, 155, 803), (377, 160, 836)], resolution=5) +offset = curve.offset(curve_points, 10) + +for coordinate in offset: + editor.placeBlock(coordinate, Block("blue_concrete")) + +curve_points = curve.curve( + [(390, 150, 788), (368, 155, 803), (377, 160, 836)], resolution=5) +offset = curve.offset(curve_points, -10) + +for coordinate in offset: + editor.placeBlock(coordinate, Block("red_concrete")) + +for coordinate in curve_points: + editor.placeBlock(coordinate, Block("white_concrete")) diff --git a/networks/Curve.py b/networks/Curve.py index 74bdbcc..99d698a 100644 --- a/networks/Curve.py +++ b/networks/Curve.py @@ -1,42 +1,89 @@ import numpy as np +import networks.Segment as segment from scipy import interpolate -class Curve: - def __init__(self, target_points): - # list of points to [(x1, y1, z1), (...), ...] - self.computed_points = compute_curve(target_points) +def curve(target_points, resolution=40): + """ + Returns a list of spaced points that approximate a smooth curve following target_points. - @staticmethod - def compute_curve(self, target_points, resolution=40): - """ - Fill self.computed_points with a list of points that approximate a smooth curve following self.target_points. + https://stackoverflow.com/questions/18962175/spline-interpolation-coefficients-of-a-line-curve-in-3d-space + """ + # Remove duplicates. Curve can't intersect itself + points = tuple(map(tuple, np.array(target_points))) + points = sorted(set(points), key=points.index) - https://stackoverflow.com/questions/18962175/spline-interpolation-coefficients-of-a-line-curve-in-3d-space - """ - # Remove duplicates. Curve can't intersect itself - points = tuple(map(tuple, np.array(target_points))) - points = sorted(set(points), key=points.index) + # Change coordinates structure to (x1, x2, x3, ...), (y1, y2, y3, ...) (z1, z2, z3, ...) + coords = np.array(points, dtype=np.float32) + x = coords[:, 0] + y = coords[:, 1] + z = coords[:, 2] - # Change coordinates structure to (x1, x2, x3, ...), (y1, y2, y3, ...) (z1, z2, z3, ...) - coords = np.array(points, dtype=np.float32) - x = coords[:, 0] - y = coords[:, 1] - z = coords[:, 2] + # Compute + tck, u = interpolate.splprep([x, y, z], s=2, k=2) + x_knots, y_knots, z_knots = interpolate.splev(tck[0], tck) + u_fine = np.linspace(0, 1, resolution) + x_fine, y_fine, z_fine = interpolate.splev(u_fine, tck) - # Compute - tck, u = interpolate.splprep([x, y, z], s=2, k=2) - x_knots, y_knots, z_knots = interpolate.splev(tck[0], tck) - u_fine = np.linspace(0, 1, resolution) - x_fine, y_fine, z_fine = interpolate.splev(u_fine, tck) + x_rounded = np.round(x_fine).astype(int) + y_rounded = np.round(y_fine).astype(int) + z_rounded = np.round(z_fine).astype(int) - x_rounded = np.round(x_fine).astype(int) - y_rounded = np.round(y_fine).astype(int) - z_rounded = np.round(z_fine).astype(int) + return [(x, y, z) for x, y, z in zip( + x_rounded, y_rounded, z_rounded)] - return [(x, y, z) for x, y, z in zip( - x_rounded, y_rounded, z_rounded)] - @staticmethod - def offset(self): - pass +def curvature(curve): + """Get the normal vector at each point of the given points representing the direction in wich the curve is turning. + + https://stackoverflow.com/questions/28269379/curve-curvature-in-numpy + + Args: + curve (np.array): array of points representing the curve + + Returns: + np.array: array of points representing the normal vector at each point in curve array + + >>> curvature(np.array(([0, 0, 0], [0, 0, 1], [1, 0, 1]))) + [[ 0.92387953 0. -0.38268343] + [ 0.70710678 0. -0.70710678] + [ 0.38268343 0. -0.92387953]] + """ + curve_points = np.array(curve) + dx_dt = np.gradient(curve_points[:, 0]) + dy_dt = np.gradient(curve_points[:, 1]) + dz_dt = np.gradient(curve_points[:, 2]) + velocity = np.array([[dx_dt[i], dy_dt[i], dz_dt[i]] + for i in range(dx_dt.size)]) + + ds_dt = np.sqrt(dx_dt * dx_dt + dy_dt * dy_dt + dz_dt * dz_dt) + + tangent = np.array([1/ds_dt]).transpose() * velocity + tangent_x = tangent[:, 0] + tangent_y = tangent[:, 1] + tangent_z = tangent[:, 2] + + deriv_tangent_x = np.gradient(tangent_x) + deriv_tangent_y = np.gradient(tangent_y) + deriv_tangent_z = np.gradient(tangent_z) + + dT_dt = np.array([[deriv_tangent_x[i], deriv_tangent_y[i], deriv_tangent_z[i]] + for i in range(deriv_tangent_x.size)]) + length_dT_dt = np.sqrt( + deriv_tangent_x * deriv_tangent_x + deriv_tangent_y * deriv_tangent_y + deriv_tangent_z * deriv_tangent_z) + + normal = np.array([1/length_dT_dt]).transpose() * dT_dt + return normal + + +def offset(curve, distance): + curvature_values = curvature(curve) + + # Offsetting + offset_curve = [segment.parallel( + (curve[i], curve[i+1]), distance) for i in range(len(curve) - 1)] + + return offset_curve + + # for i in range(1, len(offset_curve)-1): + # pass diff --git a/networks/Segment.py b/networks/Segment.py index a0cd8e6..6da98f5 100644 --- a/networks/Segment.py +++ b/networks/Segment.py @@ -10,10 +10,19 @@ def parallel(segment, distance, normal=np.array([0, 1, 0])): Returns: (np.array(), np.array()): parallel segment. + + >>> parrallel(((0, 0, 0), (0, 0, 10)), 10)) + (array([-10., 0., 0.]), array([-10., 0., 10.])) """ return (orthogonal(segment[0], segment[1], distance, normal), orthogonal(segment[1], segment[0], -distance, normal)) +def normalized(vector): + magnitude = np.linalg.norm(vector) + normalized_vector = vector / magnitude + return normalized_vector + + def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): """Get orthogonal point from a given one at the specified distance in 3D space with normal direction. @@ -29,16 +38,127 @@ def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): Returns: np.array: (x y z) - >>>orthogonal((5, 5, 5), (150, 5, 5), 10) + >>> orthogonal((5, 5, 5), (150, 5, 5), 10) [ 5. 5. 15.] """ vector = np.subtract(point, origin) - magnitude = np.linalg.norm(vector) - normalized_vector = vector / magnitude - orthogonal = np.cross(normalized_vector, normal) + normalized_vector = normalized(vector) + normalized_normal = normalized(normal) + orthogonal = np.cross(normalized_vector, normalized_normal) if np.array_equal(orthogonal, np.zeros((3,))): raise ValueError("The input vectors are not linearly independent.") orthogonal = np.add(np.multiply(orthogonal, distance), origin) return orthogonal + + +def discrete_segment(xyz1, xyz2, pixel_perfect=True): + """ + Calculate a line between two points in 3D space. + + https://www.geeksforgeeks.org/bresenhams-algorithm-for-3-d-line-drawing/ + + Args: + xyz1 (tuple): First coordinates. + xyz2 (tuple): 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) = xyz1 + (x2, y2, z2) = xyz2 + 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 From c82c6880daa3e96b46328931a8f015093a0e8720 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sun, 21 Apr 2024 13:42:08 +0200 Subject: [PATCH 03/69] Better offset --- main.py | 24 +++++++++++------------- networks/Curve.py | 16 +++++++++++++--- networks/Segment.py | 20 ++++++++++++++------ 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/main.py b/main.py index 8bd89af..4510b56 100644 --- a/main.py +++ b/main.py @@ -27,20 +27,18 @@ editor = Editor(buffering=True) # print(segment.orthogonal((0, 0, 0), (1, 0, 0), 10)) # print(curve.curvature(np.array(([0, 0, 0], [0, 1, 1], [1, 0, 1])))) +for i in range(10): + curve_points = curve.curve( + [(317, 90, 686), (291, 95, 686), (271, 100, 705), (250, 95, 715), (234, 90, 692), (220, 146, 607), (185, 158, 598), (146, 90, 596), (142, 70, 674)], resolution=160) + offset = curve.offset(curve_points, i) -curve_points = curve.curve( - [(390, 150, 788), (368, 155, 803), (377, 160, 836)], resolution=5) -offset = curve.offset(curve_points, 10) + for coordinate in offset: + editor.placeBlock(coordinate, Block("blue_concrete")) -for coordinate in offset: - editor.placeBlock(coordinate, Block("blue_concrete")) + offset = curve.offset(curve_points, -i) -curve_points = curve.curve( - [(390, 150, 788), (368, 155, 803), (377, 160, 836)], resolution=5) -offset = curve.offset(curve_points, -10) + for coordinate in offset: + editor.placeBlock(coordinate, Block("red_concrete")) -for coordinate in offset: - editor.placeBlock(coordinate, Block("red_concrete")) - -for coordinate in curve_points: - editor.placeBlock(coordinate, Block("white_concrete")) + for coordinate in curve_points: + editor.placeBlock(coordinate, Block("white_concrete")) diff --git a/networks/Curve.py b/networks/Curve.py index 99d698a..23ecec5 100644 --- a/networks/Curve.py +++ b/networks/Curve.py @@ -80,10 +80,20 @@ def offset(curve, distance): curvature_values = curvature(curve) # Offsetting - offset_curve = [segment.parallel( - (curve[i], curve[i+1]), distance) for i in range(len(curve) - 1)] + offset_segments = [segment.parallel( + (curve[i], curve[i+1]), distance, curvature_values[i]) for i in range(len(curve) - 1)] - return offset_curve + # Combining segments + combined_curve = [] + combined_curve.append(np.round(offset_segments[0][0]).tolist()) + for i in range(0, len(offset_segments)-1): + combined_curve.append(segment.middle_point( + offset_segments[i][1], offset_segments[i+1][0])) + combined_curve.append(np.round(offset_segments[-1][1]).tolist()) + + return combined_curve # for i in range(1, len(offset_curve)-1): # pass + +# TODO : Curve Offset diff --git a/networks/Segment.py b/networks/Segment.py index 6da98f5..3434f11 100644 --- a/networks/Segment.py +++ b/networks/Segment.py @@ -49,26 +49,27 @@ def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): if np.array_equal(orthogonal, np.zeros((3,))): raise ValueError("The input vectors are not linearly independent.") - orthogonal = np.add(np.multiply(orthogonal, distance), origin) + orthogonal = np.round( + np.add(np.multiply(orthogonal, distance), origin)).astype(int) return orthogonal -def discrete_segment(xyz1, xyz2, pixel_perfect=True): +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: - xyz1 (tuple): First coordinates. - xyz2 (tuple): Second coordinates. + 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) = xyz1 - (x2, y2, z2) = xyz2 + (x1, y1, z1) = start_point + (x2, y2, z2) = end_point x1, y1, z1, x2, y2, z2 = ( round(x1), round(y1), @@ -162,3 +163,10 @@ def discrete_segment(xyz1, xyz2, pixel_perfect=True): 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), + ) From 25dc6665b06887f7dd9a19d5d920520028eabd83 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sun, 21 Apr 2024 18:37:11 +0200 Subject: [PATCH 04/69] Test --- main.py | 22 +++++++++++----------- networks/Curve.py | 5 ----- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/main.py b/main.py index 4510b56..c67b6d9 100644 --- a/main.py +++ b/main.py @@ -27,18 +27,18 @@ editor = Editor(buffering=True) # print(segment.orthogonal((0, 0, 0), (1, 0, 0), 10)) # print(curve.curvature(np.array(([0, 0, 0], [0, 1, 1], [1, 0, 1])))) -for i in range(10): - curve_points = curve.curve( - [(317, 90, 686), (291, 95, 686), (271, 100, 705), (250, 95, 715), (234, 90, 692), (220, 146, 607), (185, 158, 598), (146, 90, 596), (142, 70, 674)], resolution=160) - offset = curve.offset(curve_points, i) +i = 10 +curve_points = curve.curve( + [(317, 90, 686), (291, 95, 686), (271, 100, 705), (250, 95, 715), (234, 90, 692), (220, 146, 607), (185, 158, 598), (146, 90, 596), (142, 70, 674)], resolution=40) +offset = curve.offset(curve_points, i) - for coordinate in offset: - editor.placeBlock(coordinate, Block("blue_concrete")) +for coordinate in offset: + editor.placeBlock(coordinate, Block("blue_concrete")) - offset = curve.offset(curve_points, -i) +offset = curve.offset(curve_points, -i) - for coordinate in offset: - editor.placeBlock(coordinate, Block("red_concrete")) +for coordinate in offset: + editor.placeBlock(coordinate, Block("red_concrete")) - for coordinate in curve_points: - editor.placeBlock(coordinate, Block("white_concrete")) +for coordinate in curve_points: + editor.placeBlock(coordinate, Block("white_concrete")) diff --git a/networks/Curve.py b/networks/Curve.py index 23ecec5..fd04968 100644 --- a/networks/Curve.py +++ b/networks/Curve.py @@ -92,8 +92,3 @@ def offset(curve, distance): combined_curve.append(np.round(offset_segments[-1][1]).tolist()) return combined_curve - - # for i in range(1, len(offset_curve)-1): - # pass - -# TODO : Curve Offset From 1c454947dcf2abe313e68fa3bcdbd831c69413bb Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 25 Apr 2024 19:09:51 +0200 Subject: [PATCH 05/69] Add curve resolution from spacing distance --- main.py | 23 +++++++++++++++-------- networks/Curve.py | 12 ++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index c67b6d9..11a9d75 100644 --- a/main.py +++ b/main.py @@ -27,18 +27,25 @@ editor = Editor(buffering=True) # print(segment.orthogonal((0, 0, 0), (1, 0, 0), 10)) # print(curve.curvature(np.array(([0, 0, 0], [0, 1, 1], [1, 0, 1])))) + +coordinates = [(-854, 77, -210), (-770, 89, -207), (-736, 75, -184)] + +resolution = curve.resolution_from_spacing(coordinates, 10) + i = 10 -curve_points = curve.curve( - [(317, 90, 686), (291, 95, 686), (271, 100, 705), (250, 95, 715), (234, 90, 692), (220, 146, 607), (185, 158, 598), (146, 90, 596), (142, 70, 674)], resolution=40) -offset = curve.offset(curve_points, i) +curve_points = curve.curve(coordinates, resolution) -for coordinate in offset: - editor.placeBlock(coordinate, Block("blue_concrete")) +# offset = curve.offset(curve_points, i) -offset = curve.offset(curve_points, -i) +# for coordinate in offset: +# editor.placeBlock(coordinate, Block("blue_concrete")) -for coordinate in offset: - editor.placeBlock(coordinate, Block("red_concrete")) +# offset = curve.offset(curve_points, -i) + +# for coordinate in offset: +# editor.placeBlock(coordinate, Block("red_concrete")) for coordinate in curve_points: editor.placeBlock(coordinate, Block("white_concrete")) + +### diff --git a/networks/Curve.py b/networks/Curve.py index fd04968..d37002e 100644 --- a/networks/Curve.py +++ b/networks/Curve.py @@ -1,6 +1,7 @@ import numpy as np import networks.Segment as segment from scipy import interpolate +from math import sqrt def curve(target_points, resolution=40): @@ -92,3 +93,14 @@ def offset(curve, distance): combined_curve.append(np.round(offset_segments[-1][1]).tolist()) return combined_curve + + +def resolution_from_spacing(target_points, spacing_distance): + length = 0 + for i in range(len(target_points) - 1): + length += sqrt( + ((target_points[i][0] - target_points[i + 1][0]) ** 2) + + ((target_points[i][1] - target_points[i + 1][1]) ** 2) + + ((target_points[i][2] - target_points[i + 1][2]) ** 2) + ) + return round(length / spacing_distance) From bdde8f54b1016468b26a6bc80a6070635924be3c Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 25 Apr 2024 19:20:06 +0200 Subject: [PATCH 06/69] Add curve simplification --- networks/Curve.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/networks/Curve.py b/networks/Curve.py index d37002e..93d78fa 100644 --- a/networks/Curve.py +++ b/networks/Curve.py @@ -104,3 +104,36 @@ def resolution_from_spacing(target_points, spacing_distance): + ((target_points[i][2] - target_points[i + 1][2]) ** 2) ) return round(length / spacing_distance) + + +def simplify_segments(points, epsilon): + if len(points) < 3: + return points + + # Find the point with the maximum distance + max_distance = 0 + max_index = 0 + end_index = len(points) - 1 + + for i in range(1, end_index): + distance = get_distance(points[i], points[0]) + if distance > max_distance: + max_distance = distance + max_index = i + + simplified_points = [] + + # If the maximum distance is greater than epsilon, recursively simplify + if max_distance > epsilon: + rec_results1 = simplify_segments(points[:max_index+1], epsilon) + rec_results2 = simplify_segments(points[max_index:], epsilon) + + # Combine the simplified sub-results + simplified_points.extend(rec_results1[:-1]) + simplified_points.extend(rec_results2) + else: + # The maximum distance is less than epsilon, retain the endpoints + simplified_points.append(points[0]) + simplified_points.append(points[end_index]) + + return simplified_points From 323111f2f6eef4701d6ecf6aa8379d2bd6020d3d Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Fri, 26 Apr 2024 12:38:15 +0200 Subject: [PATCH 07/69] First implementation of curve surface failed (not enough precision) --- main.py | 42 +++++++++++++--------------------------- networks/Curve.py | 12 +++++++----- networks/CurveSurface.py | 30 ++++++++++++++++++++++++++++ networks/roads/Lane.py | 3 +++ 4 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 networks/CurveSurface.py create mode 100644 networks/roads/Lane.py diff --git a/main.py b/main.py index 11a9d75..edaa077 100644 --- a/main.py +++ b/main.py @@ -1,39 +1,23 @@ from gdpc import Editor, Block, geometry import networks.Curve as curve +import networks.CurveSurface as CurveSurface import networks.Segment as segment import numpy as np editor = Editor(buffering=True) -# # Get a block -# block = editor.getBlock((0,48,0)) +y = 20 +coordinates = [(-854, 87+y, -210), (-770, 99+y, -207), (-736, 85+y, -184)] +resolution, distance = curve.resolution_distance(coordinates, 10) -# # Place a block -# editor.placeBlock((394, 132, 741), Block("stone")) - -# # Build a cube -# geometry.placeCuboid(editor, (458, 92, 488), (468, 99, 471), Block("oak_planks")) - -# curve = curve.Curve([(396, 132, 740), (435, 138, 730), -# (443, 161, 758), (417, 73, 729)]) -# curve.compute_curve() - -# for point in curve.computed_points: -# print(point) -# editor.placeBlock(point, Block("stone")) - - -# print(segment.parallel(((0, 0, 0), (0, 0, 10)), 10)) -# print(segment.orthogonal((0, 0, 0), (1, 0, 0), 10)) -# print(curve.curvature(np.array(([0, 0, 0], [0, 1, 1], [1, 0, 1])))) - - -coordinates = [(-854, 77, -210), (-770, 89, -207), (-736, 75, -184)] - -resolution = curve.resolution_from_spacing(coordinates, 10) - -i = 10 curve_points = curve.curve(coordinates, resolution) +curve_surface = CurveSurface.CurveSurface(curve_points) +curve_surface.compute_curvature() +curve_surface.compute_surface(50, curve_surface.curvature, 1) + +for line_range in range(len(curve_surface.offset_points[0])): + for coordinate in curve_surface.offset_points[line_range]: + editor.placeBlock(coordinate, Block("white_concrete")) # offset = curve.offset(curve_points, i) @@ -45,7 +29,7 @@ curve_points = curve.curve(coordinates, resolution) # for coordinate in offset: # editor.placeBlock(coordinate, Block("red_concrete")) -for coordinate in curve_points: - editor.placeBlock(coordinate, Block("white_concrete")) +# for coordinate in curve_points: +# editor.placeBlock(coordinate, Block("white_concrete")) ### diff --git a/networks/Curve.py b/networks/Curve.py index 93d78fa..2139c7a 100644 --- a/networks/Curve.py +++ b/networks/Curve.py @@ -77,12 +77,14 @@ def curvature(curve): return normal -def offset(curve, distance): - curvature_values = curvature(curve) +def offset(curve, distance, normals): + if len(normals) != len(curve): + raise ValueError( + 'Number of normals and number of points in the curve do not match') # Offsetting offset_segments = [segment.parallel( - (curve[i], curve[i+1]), distance, curvature_values[i]) for i in range(len(curve) - 1)] + (curve[i], curve[i+1]), distance, normals[i]) for i in range(len(curve) - 1)] # Combining segments combined_curve = [] @@ -95,7 +97,7 @@ def offset(curve, distance): return combined_curve -def resolution_from_spacing(target_points, spacing_distance): +def resolution_distance(target_points, spacing_distance): length = 0 for i in range(len(target_points) - 1): length += sqrt( @@ -103,7 +105,7 @@ def resolution_from_spacing(target_points, spacing_distance): + ((target_points[i][1] - target_points[i + 1][1]) ** 2) + ((target_points[i][2] - target_points[i + 1][2]) ** 2) ) - return round(length / spacing_distance) + return round(length / spacing_distance), length def simplify_segments(points, epsilon): diff --git a/networks/CurveSurface.py b/networks/CurveSurface.py new file mode 100644 index 0000000..7eb9946 --- /dev/null +++ b/networks/CurveSurface.py @@ -0,0 +1,30 @@ +import networks.Curve as curve +import networks.Segment as segment +import numpy as np + + +class CurveSurface: + def __init__(self, points, reshape=True, spacing_distance=10): + self.points = np.array(points) + if reshape: + self.resolution, self.length = curve.resolution_distance( + self.points, spacing_distance=spacing_distance) + self.curve = curve.curve(self.points, self.resolution) + else: # Point can also be given already in curved form + self.curve = self.points + + def compute_curvature(self): + self.curvature = curve.curvature(self.curve) + + def compute_surface(self, width, normals, resolution): + self.offset_points = [None] * (width * resolution) + self.surface = [] + for line_range in range(width * resolution): + self.offset_points[line_range] = curve.offset( + self.curve, line_range/resolution, normals) + + for i in range(len(self.offset_points[line_range])-1): + self.surface.extend(segment.discrete_segment( + self.offset_points[line_range][i], self.offset_points[line_range][i+1], pixel_perfect=False)) + + print(self.surface) diff --git a/networks/roads/Lane.py b/networks/roads/Lane.py new file mode 100644 index 0000000..1fb14c4 --- /dev/null +++ b/networks/roads/Lane.py @@ -0,0 +1,3 @@ +class Lane: + def __init__(self, coordinates, lane_type): + pass From 751e935b32021188a72d2c0db4ef64225dc0c07b Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Fri, 26 Apr 2024 19:47:55 +0200 Subject: [PATCH 08/69] Curve surface okayish implementation --- main.py | 40 +++++++++++++++++++---- networks/Curve.py | 4 +-- networks/CurveSurface.py | 69 ++++++++++++++++++++++++++++++++++------ networks/Segment.py | 9 ++++-- 4 files changed, 102 insertions(+), 20 deletions(-) diff --git a/main.py b/main.py index edaa077..3d49ce8 100644 --- a/main.py +++ b/main.py @@ -7,17 +7,43 @@ import numpy as np editor = Editor(buffering=True) y = 20 -coordinates = [(-854, 87+y, -210), (-770, 99+y, -207), (-736, 85+y, -184)] -resolution, distance = curve.resolution_distance(coordinates, 10) + +# Over the hill +# coordinates = [(-854, 87+y, -210), (-770, 99+y, -207), (-736, 85+y, -184)] + +# Along the river +# coordinates = [(-456, 69, -283), (-588, 106, -374), (-720, 71, -384), (-775, 67, -289), (-822, 84, -265), (-868, 77, -188), (-927, 96, -127), +# (-926, 65, -29), (-906, 98, 42), (-902, 137, 2), (-909, 115, -62), (-924, 76, -6), (-985, 76, 37), (-1043, 76, 28), (-1102, 66, 63)] + +# Though the loop +coordinates = [(-1005, 113, -19), (-896, 113, 7), + (-807, 76, 54), (-738, 76, -10), (-678, 76, -86)] + +resolution, distance = curve.resolution_distance(coordinates, 6) curve_points = curve.curve(coordinates, resolution) -curve_surface = CurveSurface.CurveSurface(curve_points) +curve_surface = CurveSurface.CurveSurface(coordinates) curve_surface.compute_curvature() -curve_surface.compute_surface(50, curve_surface.curvature, 1) -for line_range in range(len(curve_surface.offset_points[0])): - for coordinate in curve_surface.offset_points[line_range]: - editor.placeBlock(coordinate, Block("white_concrete")) +curvature = [] +for i in range(len(curve_surface.curvature)): + curvature.append((0, 1, 0)) + +curve_surface.compute_surface(10, curvature) + +# for coordinate in curve_surface.offset_points: +# editor.placeBlock(coordinate, Block("white_concrete")) + +for coordinate in curve_surface.surface: + editor.placeBlock(coordinate, Block("black_concrete")) + +for coordinate in curve_surface.curve: + editor.placeBlock(coordinate, Block("red_concrete")) + + +# for line_range in range(len(curve_surface.offset_points[0])): +# for coordinate in curve_surface.offset_points[line_range]: +# editor.placeBlock(coordinate, Block("red_concrete")) # offset = curve.offset(curve_points, i) diff --git a/networks/Curve.py b/networks/Curve.py index 2139c7a..0925080 100644 --- a/networks/Curve.py +++ b/networks/Curve.py @@ -21,7 +21,7 @@ def curve(target_points, resolution=40): z = coords[:, 2] # Compute - tck, u = interpolate.splprep([x, y, z], s=2, k=2) + tck, u = interpolate.splprep([x, y, z], s=3, k=2) x_knots, y_knots, z_knots = interpolate.splev(tck[0], tck) u_fine = np.linspace(0, 1, resolution) x_fine, y_fine, z_fine = interpolate.splev(u_fine, tck) @@ -71,7 +71,7 @@ def curvature(curve): dT_dt = np.array([[deriv_tangent_x[i], deriv_tangent_y[i], deriv_tangent_z[i]] for i in range(deriv_tangent_x.size)]) length_dT_dt = np.sqrt( - deriv_tangent_x * deriv_tangent_x + deriv_tangent_y * deriv_tangent_y + deriv_tangent_z * deriv_tangent_z) + deriv_tangent_x * deriv_tangent_x + deriv_tangent_y * deriv_tangent_y + deriv_tangent_z * deriv_tangent_z + 0.0001) normal = np.array([1/length_dT_dt]).transpose() * dT_dt return normal diff --git a/networks/CurveSurface.py b/networks/CurveSurface.py index 7eb9946..4807bb6 100644 --- a/networks/CurveSurface.py +++ b/networks/CurveSurface.py @@ -16,15 +16,66 @@ class CurveSurface: def compute_curvature(self): self.curvature = curve.curvature(self.curve) - def compute_surface(self, width, normals, resolution): - self.offset_points = [None] * (width * resolution) + def compute_surface(self, width, normals): + self.offset_left = curve.offset(self.curve, width, normals) + self.offset_right = curve.offset(self.curve, -width, normals) + self.perpendicular_segment = [] + + for i in range(len(self.offset_left)): + self.perpendicular_segment.append(segment.discrete_segment( + self.offset_left[i], self.offset_right[i], pixel_perfect=False)) + self.surface = [] - for line_range in range(width * resolution): - self.offset_points[line_range] = curve.offset( - self.curve, line_range/resolution, normals) - for i in range(len(self.offset_points[line_range])-1): - self.surface.extend(segment.discrete_segment( - self.offset_points[line_range][i], self.offset_points[line_range][i+1], pixel_perfect=False)) + for i in range(len(self.perpendicular_segment)-1): + for j in range(len(self.perpendicular_segment[i])): + # Hypothesis + max_length_index = i + min_length_index = i+1 + proportion = len( + self.perpendicular_segment[min_length_index])/len(self.perpendicular_segment[max_length_index]) - print(self.surface) + # Reverse order if wrong hypothesis + if proportion > 1: + max_length_index = i+1 + min_length_index = i + proportion = len( + self.perpendicular_segment[min_length_index])/len(self.perpendicular_segment[max_length_index]) + + for k in range(len(self.perpendicular_segment[max_length_index])): + self.surface.extend(segment.discrete_segment( + self.perpendicular_segment[max_length_index][k], self.perpendicular_segment[min_length_index][round(k * proportion)-1], pixel_perfect=False)) + + # for i in range(len(self.offset_points)): + # self.perpendicular_segment[i].append( + # segment.discrete_segment(self.offset_points[i], self.curve[i])) + + # for j in range(len(self.offset_points)-1): + # # Hypothesis + # max_length_index = j + # min_length_index = j+1 + # proportion = len( + # self.perpendicular_segment[min_length_index])/len(self.perpendicular_segment[max_length_index]) + + # # Reverse order if wrong hypothesis + # if proportion > 1: + # max_length_index = j+1 + # min_length_index = j + # proportion = len( + # self.perpendicular_segment[min_length_index])/len(self.perpendicular_segment[max_length_index]) + + # for k in range(len(self.perpendicular_segment[max_length_index])): + # # print(self.perpendicular_segment[max_length_index][k], + # # self.perpendicular_segment[min_length_index][round(k * proportion)]) + # self.surface.extend(segment.discrete_segment( + # self.perpendicular_segment[max_length_index][k], self.perpendicular_segment[min_length_index][round(k * proportion)])) + + # for line_range in range(width * resolution): + # self.offset_points[line_range] = curve.offset( + # self.curve, line_range/resolution, normals) + + # for i in range(len(self.offset_points[line_range])-1): + # self.surface.extend(segment.discrete_segment( + # self.offset_points[line_range][i], self.offset_points[line_range][i+1], pixel_perfect=False)) + + # print(self.surface) diff --git a/networks/Segment.py b/networks/Segment.py index 3434f11..e2c661e 100644 --- a/networks/Segment.py +++ b/networks/Segment.py @@ -19,8 +19,11 @@ def parallel(segment, distance, normal=np.array([0, 1, 0])): def normalized(vector): magnitude = np.linalg.norm(vector) - normalized_vector = vector / magnitude - return normalized_vector + if magnitude != 0: + normalized_vector = vector / magnitude + return normalized_vector + else: + return [0, 0, 0] def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): @@ -47,6 +50,8 @@ def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): orthogonal = np.cross(normalized_vector, normalized_normal) if np.array_equal(orthogonal, np.zeros((3,))): + print(normalized_vector, normalized_normal, orthogonal, normal) + print(origin, point, distance) raise ValueError("The input vectors are not linearly independent.") orthogonal = np.round( From 4f19a900489bb4d47a48d50819f4b34f5050a2a9 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Fri, 26 Apr 2024 20:49:40 +0200 Subject: [PATCH 09/69] Clean --- main.py | 23 ----------------------- networks/CurveSurface.py | 34 ---------------------------------- 2 files changed, 57 deletions(-) diff --git a/main.py b/main.py index 3d49ce8..99c7214 100644 --- a/main.py +++ b/main.py @@ -31,31 +31,8 @@ for i in range(len(curve_surface.curvature)): curve_surface.compute_surface(10, curvature) -# for coordinate in curve_surface.offset_points: -# editor.placeBlock(coordinate, Block("white_concrete")) - for coordinate in curve_surface.surface: editor.placeBlock(coordinate, Block("black_concrete")) for coordinate in curve_surface.curve: editor.placeBlock(coordinate, Block("red_concrete")) - - -# for line_range in range(len(curve_surface.offset_points[0])): -# for coordinate in curve_surface.offset_points[line_range]: -# editor.placeBlock(coordinate, Block("red_concrete")) - -# offset = curve.offset(curve_points, i) - -# for coordinate in offset: -# editor.placeBlock(coordinate, Block("blue_concrete")) - -# offset = curve.offset(curve_points, -i) - -# for coordinate in offset: -# editor.placeBlock(coordinate, Block("red_concrete")) - -# for coordinate in curve_points: -# editor.placeBlock(coordinate, Block("white_concrete")) - -### diff --git a/networks/CurveSurface.py b/networks/CurveSurface.py index 4807bb6..ba5368f 100644 --- a/networks/CurveSurface.py +++ b/networks/CurveSurface.py @@ -45,37 +45,3 @@ class CurveSurface: for k in range(len(self.perpendicular_segment[max_length_index])): self.surface.extend(segment.discrete_segment( self.perpendicular_segment[max_length_index][k], self.perpendicular_segment[min_length_index][round(k * proportion)-1], pixel_perfect=False)) - - # for i in range(len(self.offset_points)): - # self.perpendicular_segment[i].append( - # segment.discrete_segment(self.offset_points[i], self.curve[i])) - - # for j in range(len(self.offset_points)-1): - # # Hypothesis - # max_length_index = j - # min_length_index = j+1 - # proportion = len( - # self.perpendicular_segment[min_length_index])/len(self.perpendicular_segment[max_length_index]) - - # # Reverse order if wrong hypothesis - # if proportion > 1: - # max_length_index = j+1 - # min_length_index = j - # proportion = len( - # self.perpendicular_segment[min_length_index])/len(self.perpendicular_segment[max_length_index]) - - # for k in range(len(self.perpendicular_segment[max_length_index])): - # # print(self.perpendicular_segment[max_length_index][k], - # # self.perpendicular_segment[min_length_index][round(k * proportion)]) - # self.surface.extend(segment.discrete_segment( - # self.perpendicular_segment[max_length_index][k], self.perpendicular_segment[min_length_index][round(k * proportion)])) - - # for line_range in range(width * resolution): - # self.offset_points[line_range] = curve.offset( - # self.curve, line_range/resolution, normals) - - # for i in range(len(self.offset_points[line_range])-1): - # self.surface.extend(segment.discrete_segment( - # self.offset_points[line_range][i], self.offset_points[line_range][i+1], pixel_perfect=False)) - - # print(self.surface) From a5714343e7223fce39c0e689c1f46dd6b1a34c0d Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Fri, 26 Apr 2024 23:11:00 +0200 Subject: [PATCH 10/69] Testing curve surface usage case --- main.py | 54 ++++++++++++++++++++++++++++++++++------ networks/CurveSurface.py | 19 +++++++++++--- networks/Segment.py | 3 +-- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index 99c7214..0f22098 100644 --- a/main.py +++ b/main.py @@ -3,10 +3,13 @@ import networks.Curve as curve import networks.CurveSurface as CurveSurface import networks.Segment as segment import numpy as np +import random editor = Editor(buffering=True) -y = 20 +y = 25 +block_list = ["blue_concrete", "red_concrete", "green_concrete", + "yellow_concrete", "purple_concrete", "pink_concrete"] # Over the hill # coordinates = [(-854, 87+y, -210), (-770, 99+y, -207), (-736, 85+y, -184)] @@ -16,8 +19,12 @@ y = 20 # (-926, 65, -29), (-906, 98, 42), (-902, 137, 2), (-909, 115, -62), (-924, 76, -6), (-985, 76, 37), (-1043, 76, 28), (-1102, 66, 63)] # Though the loop -coordinates = [(-1005, 113, -19), (-896, 113, 7), - (-807, 76, 54), (-738, 76, -10), (-678, 76, -86)] +# coordinates = [(-1005, 113+y, -19), (-896, 113+y, 7), +# (-807, 76+y, 54), (-738, 76+y, -10), (-678, 76+y, -86)] + +# Second zone +coordinates = [(-805, 78, 128), (-881, 91, 104), (-950, 119, 69), (-1005, 114, 58), (-1052, 86, 30), + (-1075, 83, 40), (-1104, 77, 63), (-1161, 69, 157), (-1144, 62, 226), (-1189, 76, 265), (-1210, 79, 329)] resolution, distance = curve.resolution_distance(coordinates, 6) @@ -29,10 +36,41 @@ curvature = [] for i in range(len(curve_surface.curvature)): curvature.append((0, 1, 0)) -curve_surface.compute_surface(10, curvature) -for coordinate in curve_surface.surface: - editor.placeBlock(coordinate, Block("black_concrete")) +# Perpendicular +curve_surface.compute_surface_perpendicular(10, curvature) +for i in range(len(curve_surface.surface)): + for j in range(len(curve_surface.surface[i])): + # block = random.choice(block_list) + for k in range(len(curve_surface.surface[i][j])): + if k-16 < len(block_list) and k-16 >= 0: + editor.placeBlock( + curve_surface.surface[i][j][k], Block(block_list[k-16])) + else: + editor.placeBlock( + curve_surface.surface[i][j][k], Block("stone")) -for coordinate in curve_surface.curve: - editor.placeBlock(coordinate, Block("red_concrete")) +offset = curve.offset(curve_surface.curve, -9, curvature) +for i in range(len(offset)-1): + line = segment.discrete_segment(offset[i], offset[i+1]) + for coordinate in line: + editor.placeBlock(coordinate, Block("white_concrete")) + +offset = curve.offset(curve_surface.curve, 9, curvature) +for i in range(len(offset)-1): + line = segment.discrete_segment(offset[i], offset[i+1]) + for coordinate in line: + editor.placeBlock(coordinate, Block("white_concrete")) + +# for coordinate in curve_surface.surface: +# editor.placeBlock(coordinate, Block("black_concrete")) + +# for coordinate in curve_surface.curve: +# editor.placeBlock(coordinate, Block("red_concrete")) + +# # Parallel +# curve_surface.compute_surface_parallel(0, 10, 8, curvature) + +# for current_range in range(len(curve_surface.left_side)): +# for coordinate in curve_surface.left_side[current_range]: +# editor.placeBlock(coordinate, Block("yellow_concrete")) diff --git a/networks/CurveSurface.py b/networks/CurveSurface.py index ba5368f..350681b 100644 --- a/networks/CurveSurface.py +++ b/networks/CurveSurface.py @@ -16,7 +16,7 @@ class CurveSurface: def compute_curvature(self): self.curvature = curve.curvature(self.curve) - def compute_surface(self, width, normals): + def compute_surface_perpendicular(self, width, normals): self.offset_left = curve.offset(self.curve, width, normals) self.offset_right = curve.offset(self.curve, -width, normals) self.perpendicular_segment = [] @@ -28,6 +28,7 @@ class CurveSurface: self.surface = [] for i in range(len(self.perpendicular_segment)-1): + self.surface.append([]) for j in range(len(self.perpendicular_segment[i])): # Hypothesis max_length_index = i @@ -42,6 +43,16 @@ class CurveSurface: proportion = len( self.perpendicular_segment[min_length_index])/len(self.perpendicular_segment[max_length_index]) - for k in range(len(self.perpendicular_segment[max_length_index])): - self.surface.extend(segment.discrete_segment( - self.perpendicular_segment[max_length_index][k], self.perpendicular_segment[min_length_index][round(k * proportion)-1], pixel_perfect=False)) + self.surface[i].append([]) + for k in range(len(self.perpendicular_segment[max_length_index])-1): + self.surface[i][j].append(segment.discrete_segment( + self.perpendicular_segment[max_length_index][k], self.perpendicular_segment[min_length_index][round(k * proportion)], pixel_perfect=False)) + + def compute_surface_parallel(self, inner_range, outer_range, resolution, normals): + self.left_side = [] + self.right_side = [] + for current_range in range(inner_range * resolution, outer_range * resolution): + self.left_side.append(curve.offset( + self.curve, current_range/resolution, normals)) + self.right_side.append(curve.offset( + self.curve, -current_range/resolution, normals)) diff --git a/networks/Segment.py b/networks/Segment.py index e2c661e..f45f6ed 100644 --- a/networks/Segment.py +++ b/networks/Segment.py @@ -54,8 +54,7 @@ def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): print(origin, point, distance) raise ValueError("The input vectors are not linearly independent.") - orthogonal = np.round( - np.add(np.multiply(orthogonal, distance), origin)).astype(int) + orthogonal = np.add(np.multiply(orthogonal, distance), origin).astype(int) return orthogonal From 54e25222025545151cacb24c9a4698ac3cbabaae Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sun, 26 May 2024 16:19:18 +0200 Subject: [PATCH 11/69] Rename files --- networks/{Curve.py => curve.py} | 0 networks/{Segment.py => segment.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename networks/{Curve.py => curve.py} (100%) rename networks/{Segment.py => segment.py} (100%) diff --git a/networks/Curve.py b/networks/curve.py similarity index 100% rename from networks/Curve.py rename to networks/curve.py diff --git a/networks/Segment.py b/networks/segment.py similarity index 100% rename from networks/Segment.py rename to networks/segment.py From 0d4bd5906be7215c8da875749d5f75b6d9e0a2ea Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sun, 26 May 2024 16:26:32 +0200 Subject: [PATCH 12/69] Fix location folders --- networks/{ => geometry}/CurveSurface.py | 4 ++-- networks/{ => geometry}/curve.py | 2 +- networks/{ => geometry}/segment.py | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename networks/{ => geometry}/CurveSurface.py (96%) rename networks/{ => geometry}/curve.py (99%) rename networks/{ => geometry}/segment.py (100%) diff --git a/networks/CurveSurface.py b/networks/geometry/CurveSurface.py similarity index 96% rename from networks/CurveSurface.py rename to networks/geometry/CurveSurface.py index 350681b..ca830b4 100644 --- a/networks/CurveSurface.py +++ b/networks/geometry/CurveSurface.py @@ -1,5 +1,5 @@ -import networks.Curve as curve -import networks.Segment as segment +import networks.geometry.curve as curve +import networks.geometry.segment as segment import numpy as np diff --git a/networks/curve.py b/networks/geometry/curve.py similarity index 99% rename from networks/curve.py rename to networks/geometry/curve.py index 0925080..5d3d00c 100644 --- a/networks/curve.py +++ b/networks/geometry/curve.py @@ -1,5 +1,5 @@ import numpy as np -import networks.Segment as segment +import networks.geometry.segment as segment from scipy import interpolate from math import sqrt diff --git a/networks/segment.py b/networks/geometry/segment.py similarity index 100% rename from networks/segment.py rename to networks/geometry/segment.py From 2e66a539cfd1a4963396b0abba94f81f86d014fa Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sun, 26 May 2024 18:37:33 +0200 Subject: [PATCH 13/69] Add line object --- main.py | 131 ++++++++++++++++++---------------- networks/lanes/Lane.py | 36 ++++++++++ networks/lanes/materials.json | 7 ++ networks/lines/Line.py | 41 +++++++++++ networks/lines/line.json | 9 +++ networks/roads/Lane.py | 3 - networks/roads/Road.py | 6 +- 7 files changed, 166 insertions(+), 67 deletions(-) create mode 100644 networks/lanes/Lane.py create mode 100644 networks/lanes/materials.json create mode 100644 networks/lines/Line.py create mode 100644 networks/lines/line.json delete mode 100644 networks/roads/Lane.py diff --git a/main.py b/main.py index c4d24f8..2fb0396 100644 --- a/main.py +++ b/main.py @@ -1,25 +1,26 @@ +import networks.lines.Line as Line from gdpc import Editor, Block, geometry -import networks.Curve as curve -import networks.CurveSurface as CurveSurface -import networks.Segment as segment +import networks.geometry.curve as curve +import networks.geometry.CurveSurface as CurveSurface +import networks.geometry.segment as segment import numpy as np import json from buildings.Building import Building import random -editor = Editor(buffering=True) +# editor = Editor(buffering=True) -f = open('buildings\shapes.json') -shapes = json.load(f) +# f = open('buildings\shapes.json') +# shapes = json.load(f) -# F = Foundations((0,0), (20,20), shapes[0]['matrice']) -# F.polygon.fill_polygon(editor, "stone", -60) -geometry.placeCuboid(editor, (-10, -60, -10), (85, -55, 85), Block("air")) -B = Building((0, 0), (75, 75), shapes[7]['matrice']) -B.foundations.polygon.fill_vertice(editor, "pink_wool", -60) -for collumn in B.foundations.collumns: - collumn.fill(editor, "white_concrete", -60, -55) -B.foundations.polygon.fill_polygon(editor, "white_concrete", -60) +# # F = Foundations((0,0), (20,20), shapes[0]['matrice']) +# # F.polygon.fill_polygon(editor, "stone", -60) +# geometry.placeCuboid(editor, (-10, -60, -10), (85, -55, 85), Block("air")) +# B = Building((0, 0), (75, 75), shapes[7]['matrice']) +# B.foundations.polygon.fill_vertice(editor, "pink_wool", -60) +# for collumn in B.foundations.collumns: +# collumn.fill(editor, "white_concrete", -60, -55) +# B.foundations.polygon.fill_polygon(editor, "white_concrete", -60) y = 25 block_list = ["blue_concrete", "red_concrete", "green_concrete", @@ -28,63 +29,71 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # Over the hill # coordinates = [(-854, 87+y, -210), (-770, 99+y, -207), (-736, 85+y, -184)] -# Along the river -# coordinates = [(-456, 69, -283), (-588, 106, -374), (-720, 71, -384), (-775, 67, -289), (-822, 84, -265), (-868, 77, -188), (-927, 96, -127), -# (-926, 65, -29), (-906, 98, 42), (-902, 137, 2), (-909, 115, -62), (-924, 76, -6), (-985, 76, 37), (-1043, 76, 28), (-1102, 66, 63)] +# # Along the river +# # coordinates = [(-456, 69, -283), (-588, 106, -374), (-720, 71, -384), (-775, 67, -289), (-822, 84, -265), (-868, 77, -188), (-927, 96, -127), +# # (-926, 65, -29), (-906, 98, 42), (-902, 137, 2), (-909, 115, -62), (-924, 76, -6), (-985, 76, 37), (-1043, 76, 28), (-1102, 66, 63)] -# Though the loop -# coordinates = [(-1005, 113+y, -19), (-896, 113+y, 7), -# (-807, 76+y, 54), (-738, 76+y, -10), (-678, 76+y, -86)] +# # Though the loop +# # coordinates = [(-1005, 113+y, -19), (-896, 113+y, 7), +# # (-807, 76+y, 54), (-738, 76+y, -10), (-678, 76+y, -86)] -# Second zone -coordinates = [(-805, 78, 128), (-881, 91, 104), (-950, 119, 69), (-1005, 114, 58), (-1052, 86, 30), - (-1075, 83, 40), (-1104, 77, 63), (-1161, 69, 157), (-1144, 62, 226), (-1189, 76, 265), (-1210, 79, 329)] +# # Second zone +# coordinates = [(-805, 78, 128), (-881, 91, 104), (-950, 119, 69), (-1005, 114, 58), (-1052, 86, 30), +# (-1075, 83, 40), (-1104, 77, 63), (-1161, 69, 157), (-1144, 62, 226), (-1189, 76, 265), (-1210, 79, 329)] -resolution, distance = curve.resolution_distance(coordinates, 6) +# resolution, distance = curve.resolution_distance(coordinates, 6) -curve_points = curve.curve(coordinates, resolution) -curve_surface = CurveSurface.CurveSurface(coordinates) -curve_surface.compute_curvature() +# curve_points = curve.curve(coordinates, resolution) +# curve_surface = CurveSurface.CurveSurface(coordinates) +# curve_surface.compute_curvature() -curvature = [] -for i in range(len(curve_surface.curvature)): - curvature.append((0, 1, 0)) +# curvature = [] +# for i in range(len(curve_surface.curvature)): +# curvature.append((0, 1, 0)) -# Perpendicular -curve_surface.compute_surface_perpendicular(10, curvature) -for i in range(len(curve_surface.surface)): - for j in range(len(curve_surface.surface[i])): - # block = random.choice(block_list) - for k in range(len(curve_surface.surface[i][j])): - if k-16 < len(block_list) and k-16 >= 0: - editor.placeBlock( - curve_surface.surface[i][j][k], Block(block_list[k-16])) - else: - editor.placeBlock( - curve_surface.surface[i][j][k], Block("stone")) +# # Perpendicular +# curve_surface.compute_surface_perpendicular(10, curvature) +# for i in range(len(curve_surface.surface)): +# for j in range(len(curve_surface.surface[i])): +# # block = random.choice(block_list) +# for k in range(len(curve_surface.surface[i][j])): +# if k-16 < len(block_list) and k-16 >= 0: +# editor.placeBlock( +# curve_surface.surface[i][j][k], Block(block_list[k-16])) +# else: +# editor.placeBlock( +# curve_surface.surface[i][j][k], Block("stone")) -offset = curve.offset(curve_surface.curve, -9, curvature) -for i in range(len(offset)-1): - line = segment.discrete_segment(offset[i], offset[i+1]) - for coordinate in line: - editor.placeBlock(coordinate, Block("white_concrete")) +# offset = curve.offset(curve_surface.curve, -9, curvature) +# for i in range(len(offset)-1): +# line = segment.discrete_segment(offset[i], offset[i+1]) +# for coordinate in line: +# editor.placeBlock(coordinate, Block("white_concrete")) -offset = curve.offset(curve_surface.curve, 9, curvature) -for i in range(len(offset)-1): - line = segment.discrete_segment(offset[i], offset[i+1]) - for coordinate in line: - editor.placeBlock(coordinate, Block("white_concrete")) +# offset = curve.offset(curve_surface.curve, 9, curvature) +# for i in range(len(offset)-1): +# line = segment.discrete_segment(offset[i], offset[i+1]) +# for coordinate in line: +# editor.placeBlock(coordinate, Block("white_concrete")) -# for coordinate in curve_surface.surface: -# editor.placeBlock(coordinate, Block("black_concrete")) +# # for coordinate in curve_surface.surface: +# # editor.placeBlock(coordinate, Block("black_concrete")) -# for coordinate in curve_surface.curve: -# editor.placeBlock(coordinate, Block("red_concrete")) +# # for coordinate in curve_surface.curve: +# # editor.placeBlock(coordinate, Block("red_concrete")) -# # Parallel -# curve_surface.compute_surface_parallel(0, 10, 8, curvature) +# # # Parallel +# # curve_surface.compute_surface_parallel(0, 10, 8, curvature) -# for current_range in range(len(curve_surface.left_side)): -# for coordinate in curve_surface.left_side[current_range]: -# editor.placeBlock(coordinate, Block("yellow_concrete")) +# # for current_range in range(len(curve_surface.left_side)): +# # for coordinate in curve_surface.left_side[current_range]: +# # editor.placeBlock(coordinate, Block("yellow_concrete")) + + +coordinates = [(0, 0, 0), (0, 10, 0), (0, 20, 0)] + +with open('networks/lines/line.json') as f: + lines_type = json.load(f) + l = Line.Line(coordinates, lines_type.get('solid_white')) + print(l.get_surface()) diff --git a/networks/lanes/Lane.py b/networks/lanes/Lane.py new file mode 100644 index 0000000..dd1e5eb --- /dev/null +++ b/networks/lanes/Lane.py @@ -0,0 +1,36 @@ +import networks.geometry.curve as curve +import networks.geometry.CurveSurface as CurveSurface +import networks.geometry.segment as segment +import random + + +class Lane: + def __init__(self, coordinates, width, lane_type): + self.coordinates = coordinates + self.width = width + self.lane_type = lane_type + self.lane_materials = lane_materials + self.surface = [] + + def create_surface(self, coordinates): + resolution, distance = curve.resolution_distance(coordinates, 6) + + curve_points = curve.curve(coordinates, resolution) + curve_surface = CurveSurface.CurveSurface(coordinates) + curve_surface.compute_curvature() + + # Set the road to be flat + normals = [] + for i in range(len(curve_surface.curvature)): + normals.append((0, 1, 0)) + + # Compute each line + for distance in range(width): + offset = curve.offset(curve_surface.curve, distance, normals) + for i in range(len(offset)-1): + line = segment.discrete_segment(offset[i], offset[i+1]) + for coordinate in line: + self.surface.append((coordinate, random.choices( + list(lane_materials.keys()), + weights=lane_materials.values(), + k=1,))) diff --git a/networks/lanes/materials.json b/networks/lanes/materials.json new file mode 100644 index 0000000..c6602f8 --- /dev/null +++ b/networks/lanes/materials.json @@ -0,0 +1,7 @@ +{ + "classic_lane": {"stone": 3, "andesite": 1}, + "modern_lane": {"black_concrete": 3, "black_concrete_powder": 1}, + + "bike_lane_green": {"green_concrete": 3, "green_concrete_powder": 1}, + "bike_lane_red": {"red_concrete": 3, "red_concrete_powder": 1} +} \ No newline at end of file diff --git a/networks/lines/Line.py b/networks/lines/Line.py new file mode 100644 index 0000000..86e6dbd --- /dev/null +++ b/networks/lines/Line.py @@ -0,0 +1,41 @@ +import networks.geometry.curve as curve +import networks.geometry.segment as segment +import random + + +class Line: + def __init__(self, coordinates, line_type): + self.coordinates = coordinates + self.line_type = line_type + self.surface = [] + + def get_surface(self): + resolution, distance = curve.resolution_distance(self.coordinates, 6) + + curve_points = curve.curve(self.coordinates, resolution) + + # Compute the line + + pattern_length = 0 + pattern_materials = [] + for key, value in self.line_type.items(): + pattern_length += int(key) + for _ in range(int(key)): + pattern_materials.append(value) + + pattern_iteration = 0 + for i in range(len(curve_points)-1): + line = segment.discrete_segment(curve_points[i], curve_points[i+1]) + for coordinate in line: + block = random.choices( + list(pattern_materials[pattern_iteration].keys()), + weights=pattern_materials[pattern_iteration].values(), + k=1)[0] + if block != 'None': + self.surface.append((coordinate, block)) + + pattern_iteration += 1 + if pattern_iteration >= pattern_length: + pattern_iteration = 0 + + return self.surface diff --git a/networks/lines/line.json b/networks/lines/line.json new file mode 100644 index 0000000..d331aa5 --- /dev/null +++ b/networks/lines/line.json @@ -0,0 +1,9 @@ +{ + "solid_white": { + "1": {"white_concrete": 3, "white_concrete_powder": 1} + }, + "broken_white": { + "3": {"white_concrete": 3, "white_concrete_powder": 1}, + "1": {"None": 1} + } +} \ No newline at end of file diff --git a/networks/roads/Lane.py b/networks/roads/Lane.py deleted file mode 100644 index 1fb14c4..0000000 --- a/networks/roads/Lane.py +++ /dev/null @@ -1,3 +0,0 @@ -class Lane: - def __init__(self, coordinates, lane_type): - pass diff --git a/networks/roads/Road.py b/networks/roads/Road.py index f05719a..5a12aa2 100644 --- a/networks/roads/Road.py +++ b/networks/roads/Road.py @@ -1,7 +1,7 @@ class Road: - def __init__(self, coordinates, road_type): - self.coordinates = coordinates # List of tuples (x1, y1, z1) in order + def __init__(self, coordinates): + self.coordinates = coordinates # List of tuples (x1, y1, z1) in order self.road_type = road_type # 'road', 'highway' def place_roads(self): - pass \ No newline at end of file + pass From 154353048861ff2fc1ba2fd74b18457c4696b4a8 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sun, 26 May 2024 18:47:15 +0200 Subject: [PATCH 14/69] Add lane object --- main.py | 16 +++++++++++----- networks/lanes/Lane.py | 19 ++++++++++--------- networks/lanes/{materials.json => lanes.json} | 0 networks/lines/Line.py | 6 +++--- networks/lines/{line.json => lines.json} | 0 5 files changed, 24 insertions(+), 17 deletions(-) rename networks/lanes/{materials.json => lanes.json} (100%) rename networks/lines/{line.json => lines.json} (100%) diff --git a/main.py b/main.py index 2fb0396..a7aa278 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import networks.lines.Line as Line +import networks.lanes.Lane as Lane from gdpc import Editor, Block, geometry import networks.geometry.curve as curve import networks.geometry.CurveSurface as CurveSurface @@ -91,9 +92,14 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # editor.placeBlock(coordinate, Block("yellow_concrete")) -coordinates = [(0, 0, 0), (0, 10, 0), (0, 20, 0)] +coordinates = [(0, 0, 0), (0, 0, 10), (0, 0, 20)] -with open('networks/lines/line.json') as f: - lines_type = json.load(f) - l = Line.Line(coordinates, lines_type.get('solid_white')) - print(l.get_surface()) +# with open('networks/lines/lines.json') as f: +# lines_type = json.load(f) +# l = Line.Line(coordinates, lines_type.get('solid_white')) +# print(l.get_surface()) + +# with open('networks/lanes/lanes.json') as f: +# lanes_type = json.load(f) +# l = Lane.Lane(coordinates, lanes_type.get('classic_lane'), 5) +# print(l.get_surface()) diff --git a/networks/lanes/Lane.py b/networks/lanes/Lane.py index dd1e5eb..3e3824e 100644 --- a/networks/lanes/Lane.py +++ b/networks/lanes/Lane.py @@ -5,18 +5,17 @@ import random class Lane: - def __init__(self, coordinates, width, lane_type): + def __init__(self, coordinates, lane_materials, width): self.coordinates = coordinates self.width = width - self.lane_type = lane_type self.lane_materials = lane_materials self.surface = [] - def create_surface(self, coordinates): - resolution, distance = curve.resolution_distance(coordinates, 6) + def get_surface(self): + resolution, distance = curve.resolution_distance(self.coordinates, 6) - curve_points = curve.curve(coordinates, resolution) - curve_surface = CurveSurface.CurveSurface(coordinates) + curve_points = curve.curve(self.coordinates, resolution) + curve_surface = CurveSurface.CurveSurface(self.coordinates) curve_surface.compute_curvature() # Set the road to be flat @@ -25,12 +24,14 @@ class Lane: normals.append((0, 1, 0)) # Compute each line - for distance in range(width): + for distance in range(self.width): offset = curve.offset(curve_surface.curve, distance, normals) for i in range(len(offset)-1): line = segment.discrete_segment(offset[i], offset[i+1]) for coordinate in line: self.surface.append((coordinate, random.choices( - list(lane_materials.keys()), - weights=lane_materials.values(), + list(self.lane_materials.keys()), + weights=self.lane_materials.values(), k=1,))) + + return self.surface diff --git a/networks/lanes/materials.json b/networks/lanes/lanes.json similarity index 100% rename from networks/lanes/materials.json rename to networks/lanes/lanes.json diff --git a/networks/lines/Line.py b/networks/lines/Line.py index 86e6dbd..0222eef 100644 --- a/networks/lines/Line.py +++ b/networks/lines/Line.py @@ -4,9 +4,9 @@ import random class Line: - def __init__(self, coordinates, line_type): + def __init__(self, coordinates, line_materials): self.coordinates = coordinates - self.line_type = line_type + self.line_materials = line_materials self.surface = [] def get_surface(self): @@ -18,7 +18,7 @@ class Line: pattern_length = 0 pattern_materials = [] - for key, value in self.line_type.items(): + for key, value in self.line_materials.items(): pattern_length += int(key) for _ in range(int(key)): pattern_materials.append(value) diff --git a/networks/lines/line.json b/networks/lines/lines.json similarity index 100% rename from networks/lines/line.json rename to networks/lines/lines.json From 198c18173ac368ed30d6b20b98d489cd7e3768c7 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sun, 26 May 2024 19:44:21 +0200 Subject: [PATCH 15/69] Better json structure --- main.py | 8 ++++---- networks/lines/Line.py | 9 +++++---- networks/lines/lines.json | 15 ++++++++------- networks/roads/Road.py | 4 ++-- networks/roads/roads.json | 10 ++++++++++ 5 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 networks/roads/roads.json diff --git a/main.py b/main.py index a7aa278..8955bf5 100644 --- a/main.py +++ b/main.py @@ -94,10 +94,10 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", coordinates = [(0, 0, 0), (0, 0, 10), (0, 0, 20)] -# with open('networks/lines/lines.json') as f: -# lines_type = json.load(f) -# l = Line.Line(coordinates, lines_type.get('solid_white')) -# print(l.get_surface()) +with open('networks/lines/lines.json') as f: + lines_type = json.load(f) + l = Line.Line(coordinates, lines_type.get('solid_white')) + print(l.get_surface()) # with open('networks/lanes/lanes.json') as f: # lanes_type = json.load(f) diff --git a/networks/lines/Line.py b/networks/lines/Line.py index 0222eef..0393d63 100644 --- a/networks/lines/Line.py +++ b/networks/lines/Line.py @@ -18,10 +18,11 @@ class Line: pattern_length = 0 pattern_materials = [] - for key, value in self.line_materials.items(): - pattern_length += int(key) - for _ in range(int(key)): - pattern_materials.append(value) + + for pattern in self.line_materials: + pattern_length += pattern[1] + for _ in range(pattern[1]): + pattern_materials.append(pattern[0]) pattern_iteration = 0 for i in range(len(curve_points)-1): diff --git a/networks/lines/lines.json b/networks/lines/lines.json index d331aa5..170d590 100644 --- a/networks/lines/lines.json +++ b/networks/lines/lines.json @@ -1,9 +1,10 @@ { - "solid_white": { - "1": {"white_concrete": 3, "white_concrete_powder": 1} - }, - "broken_white": { - "3": {"white_concrete": 3, "white_concrete_powder": 1}, - "1": {"None": 1} - } + "solid_white": [ + [{"white_concrete": 3, "white_concrete_powder": 1}, 1] + ], + + "broken_white": [ + [{"white_concrete": 3, "white_concrete_powder": 1}, 3], + [{"None": 1}, 1] + ] } \ No newline at end of file diff --git a/networks/roads/Road.py b/networks/roads/Road.py index 5a12aa2..176c9f9 100644 --- a/networks/roads/Road.py +++ b/networks/roads/Road.py @@ -1,7 +1,7 @@ class Road: - def __init__(self, coordinates): + def __init__(self, coordinates, road_configuration): self.coordinates = coordinates # List of tuples (x1, y1, z1) in order - self.road_type = road_type # 'road', 'highway' + self.road_configuration = road_configuration # 'road', 'highway' def place_roads(self): pass diff --git a/networks/roads/roads.json b/networks/roads/roads.json new file mode 100644 index 0000000..83aeae8 --- /dev/null +++ b/networks/roads/roads.json @@ -0,0 +1,10 @@ +{ + "high_way": { + "3": {"classic_lane": 3} + + }, + "broken_white": { + "3": {"white_concrete": 3, "white_concrete_powder": 1}, + "1": {"None": 1} + } +} \ No newline at end of file From 9bece74ffd2999d92cf60272d1eae6c8c02b1383 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Mon, 27 May 2024 17:52:49 +0200 Subject: [PATCH 16/69] Add intersections utilities --- main.py | 24 +- networks/geometry/point.py | 468 +++++++++++++++++++++++++ networks/intersections/Intersection.py | 3 + networks/roads/roads.json | 17 +- 4 files changed, 498 insertions(+), 14 deletions(-) create mode 100644 networks/geometry/point.py create mode 100644 networks/intersections/Intersection.py diff --git a/main.py b/main.py index 8955bf5..947e690 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,9 @@ import json from buildings.Building import Building import random -# editor = Editor(buffering=True) +from networks.geometry.point import curveCornerIntersectionLine, curveCornerIntersectionPoints + +editor = Editor(buffering=True) # f = open('buildings\shapes.json') # shapes = json.load(f) @@ -92,14 +94,24 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # editor.placeBlock(coordinate, Block("yellow_concrete")) -coordinates = [(0, 0, 0), (0, 0, 10), (0, 0, 20)] +# coordinates = [(0, 0, 0), (0, 0, 10), (0, 0, 20)] -with open('networks/lines/lines.json') as f: - lines_type = json.load(f) - l = Line.Line(coordinates, lines_type.get('solid_white')) - print(l.get_surface()) +# with open('networks/lines/lines.json') as f: +# lines_type = json.load(f) +# l = Line.Line(coordinates, lines_type.get('solid_white')) +# print(l.get_surface()) # with open('networks/lanes/lanes.json') as f: # lanes_type = json.load(f) # l = Lane.Lane(coordinates, lanes_type.get('classic_lane'), 5) # print(l.get_surface()) + + +circle = curveCornerIntersectionLine( + ((-1313, 392), (-1378, 415)), ((-1371, 348), (-1341, 439)), 30, angleAdaptation=True) + +print(circle[0]) + +for coordinate in circle[0]: + editor.placeBlock( + (round(coordinate[0]), 100, round(coordinate[1])), Block("green_concrete")) diff --git a/networks/geometry/point.py b/networks/geometry/point.py new file mode 100644 index 0000000..e659af4 --- /dev/null +++ b/networks/geometry/point.py @@ -0,0 +1,468 @@ +from math import sqrt, cos, pi, sin +import numpy as np + + +def circle(xyC, r): + """ + Can be used for circle or disc. + + Args: + xyC (tuple): Coordinates of the center. + r (int): Radius of the circle. + + Returns: + dict: Keys are distance from the circle. Value is a list of all + coordinates at this distance. 0 for a circle. Negative values + for a disc, positive values for a hole. + """ + area = ( + (round(xyC[0]) - round(r), round(xyC[1]) - round(r)), + (round(xyC[0]) + round(r) + 1, round(xyC[1]) + round(r) + 1), + ) + + circle = {} + for x in range(area[0][0], area[1][0]): + for y in range(area[0][1], area[1][1]): + d = round(distance2D((x, y), (xyC))) - r + if circle.get(d) == None: + circle[d] = [] + circle[d].append((x, y)) + return circle + + +def InTriangle(point, xy0, xy1, xy2): + # https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. + dX = point[0] - xy0[0] + dY = point[1] - xy0[1] + dX20 = xy2[0] - xy0[0] + dY20 = xy2[1] - xy0[1] + dX10 = xy1[0] - xy0[0] + dY10 = xy1[1] - xy0[1] + + s_p = (dY20 * dX) - (dX20 * dY) + t_p = (dX10 * dY) - (dY10 * dX) + D = (dX10 * dY20) - (dY10 * dX20) + + if D > 0: + return (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= D + else: + return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= D + + +def distance2D(A, B): # TODO : Can be better. + return sqrt((B[0] - A[0]) ** 2 + (B[1] - A[1]) ** 2) + + +def getAngle(xy0, xy1, xy2): + """ + Compute angle (in degrees) for xy0, xy1, xy2 corner. + + https://stackoverflow.com/questions/13226038/calculating-angle-between-two-vectors-in-python + + Args: + xy0 (numpy.ndarray): Points in the form of [x,y]. + xy1 (numpy.ndarray): Points in the form of [x,y]. + xy2 (numpy.ndarray): Points in the form of [x,y]. + + Returns: + float: Angle negative for counterclockwise angle, angle positive + for counterclockwise angle. + """ + if xy2 is None: + xy2 = xy1 + np.array([1, 0]) + v0 = np.array(xy0) - np.array(xy1) + v1 = np.array(xy2) - np.array(xy1) + + angle = np.math.atan2(np.linalg.det([v0, v1]), np.dot(v0, v1)) + return np.degrees(angle) + + +def circlePoints(center_point, radius, number=100): + # https://stackoverflow.com/questions/8487893/generate-all-the-points-on-the-circumference-of-a-circle + points = [ + (cos(2 * pi / number * x) * radius, sin(2 * pi / number * x) * radius) + for x in range(0, number + 1) + ] + + for i in range(len(points)): + points[i] = ( + points[i][0] + center_point[0], + points[i][1] + center_point[1], + ) + + return points + + +def optimizedPath(points, start=None): + # https://stackoverflow.com/questions/45829155/sort-points-in-order-to-have-a-continuous-curve-using-python + if start is None: + start = points[0] + pass_by = points + path = [start] + pass_by.remove(start) + while pass_by: + nearest = min(pass_by, key=lambda x: distance2D(path[-1], x)) + path.append(nearest) + pass_by.remove(nearest) + return path + + +def nearest(points, start): + return min(points, key=lambda x: distance2D(start, x)) + + +def sortRotation(points): + """ + Sort point in a rotation order. Works in 2d but supports 3d. + + https://stackoverflow.com/questions/58377015/counterclockwise-sorting-of-x-y-data + + Args: + points: List of points to sort in the form of [(x, y, z), (x, y, + z)] or [(x, y), (x, y), (x, y), (x, y)]... + + Returns: + list: List of tuples of coordinates sorted (2d or 3d). + + >>> sortRotation([(0, 45, 100), (4, -5, 5),(-5, 36, -2)]) + [(0, 45, 100), (-5, 36, -2), (4, -5, 5)] + """ + x, y = [], [] + for i in range(len(points)): + x.append(points[i][0]) + y.append(points[i][-1]) + x, y = np.array(x), np.array(y) + + x0 = np.mean(x) + y0 = np.mean(y) + + r = np.sqrt((x - x0) ** 2 + (y - y0) ** 2) + + angles = np.where( + (y - y0) > 0, + np.arccos((x - x0) / r), + 2 * np.pi - np.arccos((x - x0) / r), + ) + + mask = np.argsort(angles) + + x_sorted = list(x[mask]) + y_sorted = list(y[mask]) + + # Rearrange tuples to get the right coordinates. + sortedPoints = [] + for i in range(len(points)): + j = 0 + while (x_sorted[i] != points[j][0]) and (y_sorted[i] != points[j][-1]): + j += 1 + else: + if len(points[0]) == 3: + sortedPoints.append((x_sorted[i], points[j][1], y_sorted[i])) + else: + sortedPoints.append((x_sorted[i], y_sorted[i])) + + return sortedPoints + + +def lineIntersection(line0, line1, fullLine=True): + """ + Find (or not) intersection between two lines. Works in 2d but + supports 3d. + + https://stackoverflow.com/questions/20677795/how-do-i-compute-the-intersection-point-of-two-lines + + Args: + line0 (tuple): Tuple of tuple of coordinates. + line1 (tuple): Tuple of tuple of coordinates. + fullLine (bool, optional): True to find intersections along + full line - not just in the segment. + + Returns: + tuple: Coordinates (2d). + + >>> lineIntersection(((0, 0), (0, 5)), ((2.5, 2.5), (-2.5, 2.5))) + """ + xdiff = (line0[0][0] - line0[1][0], line1[0][0] - line1[1][0]) + ydiff = (line0[0][-1] - line0[1][-1], line1[0][-1] - line1[1][-1]) + + def det(a, b): + return a[0] * b[-1] - a[-1] * b[0] + + div = det(xdiff, ydiff) + if div == 0: + return None + + d = (det(*line0), det(*line1)) + x = det(d, xdiff) / div + y = det(d, ydiff) / div + + if not fullLine: + if ( + min(line0[0][0], line0[1][0]) <= x <= max(line0[0][0], line0[1][0]) + and min(line1[0][0], line1[1][0]) + <= x + <= max(line1[0][0], line1[1][0]) + and min(line0[0][-1], line0[1][-1]) + <= y + <= max(line0[0][-1], line0[1][-1]) + and min(line1[0][-1], line1[1][-1]) + <= y + <= max(line1[0][-1], line1[1][-1]) + ): + return x, y + else: + return None + else: + return x, y + + +def circleLineSegmentIntersection( + circleCenter, circleRadius, xy0, xy1, fullLine=True, tangentTol=1e-9 +): + """ + Find the points at which a circle intersects a line-segment. This + can happen at 0, 1, or 2 points. Works in 2d but supports 3d. + + https://stackoverflow.com/questions/30844482/what-is-most-efficient-way-to-find-the-intersection-of-a-line-and-a-circle-in-py + Note: We follow: http://mathworld.wolfram.com/Circle-LineIntersection.html + + Args: + circleCenter (tuple): The (x, y) location of the circle center. + circleRadius (int): The radius of the circle. + xy0 (tuple): The (x, y) location of the first point of the + segment. + xy1 ([tuple]): The (x, y) location of the second point of the + segment. + fullLine (bool, optional): True to find intersections along + full line - not just in the segment. False will just return + intersections within the segment. Defaults to True. + tangentTol (float, optional): Numerical tolerance at which we + decide the intersections are close enough to consider it a + tangent. Defaults to 1e-9. + + Returns: + list: A list of length 0, 1, or 2, where each element is a point + at which the circle intercepts a line segment (2d). + """ + + (p1x, p1y), (p2x, p2y), (cx, cy) = ( + (xy0[0], xy0[-1]), + (xy1[0], xy1[-1]), + (circleCenter[0], circleCenter[1]), + ) + (x1, y1), (x2, y2) = (p1x - cx, p1y - cy), (p2x - cx, p2y - cy) + dx, dy = (x2 - x1), (y2 - y1) + dr = (dx ** 2 + dy ** 2) ** 0.5 + big_d = x1 * y2 - x2 * y1 + discriminant = circleRadius ** 2 * dr ** 2 - big_d ** 2 + + if discriminant < 0: # No intersection between circle and line + return [] + else: # There may be 0, 1, or 2 intersections with the segment + intersections = [ + ( + cx + + ( + big_d * dy + + sign * (-1 if dy < 0 else 1) * dx * discriminant ** 0.5 + ) + / dr ** 2, + cy + + (-big_d * dx + sign * abs(dy) * discriminant ** 0.5) + / dr ** 2, + ) + for sign in ((1, -1) if dy < 0 else (-1, 1)) + ] # This makes sure the order along the segment is correct + if ( + not fullLine + ): # If only considering the segment, filter out intersections that do not fall within the segment + fraction_along_segment = [ + (xi - p1x) / dx if abs(dx) > abs(dy) else (yi - p1y) / dy + for xi, yi in intersections + ] + intersections = [ + pt + for pt, frac in zip(intersections, fraction_along_segment) + if 0 <= frac <= 1 + ] + if ( + len(intersections) == 2 and abs(discriminant) <= tangentTol + ): # If line is tangent to circle, return just one point (as both intersections have same location) + return [intersections[0]] + else: + return intersections + + +def perpendicular(distance, xy1, xy2): + """ + Return a tuple of the perpendicular coordinates. + + Args: + distance (int): Distance from the line[xy1;xy2]. + xy1 (tuple): First coordinates. + xy2 (tuple): Second coordinates. + + Returns: + tuple: Coordinates of the line length distance, perpendicular + to [xy1; xy2] at xy1. + """ + (x1, y1) = xy1 + (x2, y2) = xy2 + dx = x1 - x2 + dy = y1 - y2 + dist = sqrt(dx * dx + dy * dy) + dx /= dist + dy /= dist + x3 = x1 + (distance / 2) * dy + y3 = y1 - (distance / 2) * dx + x4 = x1 - (distance / 2) * dy + y4 = y1 + (distance / 2) * dx + return ((round(x3), round(y3)), (round(x4), round(y4))) + + +def curveCornerIntersectionPoints( + line0, line1, startDistance, angleAdaptation=False +): + """ + Create points between the two lines to smooth the intersection. + + Args: + line0 (tuple): Tuple of tuple. Line coordinates. Order matters. + line1 (tuple): Tuple of tuple. Line coordinates. Order matters. + startDistance (int): distance from the intersection where the + curve should starts. + angleAdaptation (bool, optional): True will adapt the + startDistance depending of the angle between the two lines. + False will force the distance to be startDistance. Defaults to + False. + + Returns: + [list]: List of tuple of coordinates (2d) that forms the curve. + Starts on the line and end on the other line. + + >>> curveCornerIntersectionPoints(((0, 0), (50, 20)), ((-5, 50), (25, -5)), 10) + """ + intersection = lineIntersection(line0, line1, fullLine=True) + + if intersection == None: + return None + + # Define automatically the distance from the intersection, where the curve + # starts. + if angleAdaptation: + angle = getAngle( + (line0[0][0], line0[0][-1]), + intersection, + (line1[0][0], line1[0][-1]), + ) + # Set here the radius of the circle for a square angle. + startDistance = startDistance * abs(1 / (angle / 90)) + + startCurvePoint = circleLineSegmentIntersection( + intersection, startDistance, line0[0], intersection, fullLine=True + )[0] + endCurvePoint = circleLineSegmentIntersection( + intersection, startDistance, line1[0], intersection, fullLine=True + )[0] + # Higher value for better precision + perpendicular0 = perpendicular(10e3, startCurvePoint, intersection)[0] + perpendicular1 = perpendicular(10e3, endCurvePoint, intersection)[1] + + center = lineIntersection( + (perpendicular0, startCurvePoint), (perpendicular1, endCurvePoint) + ) + + # Distance with startCurvePoint and endCurvePoint from the center are the + # same. + radius = distance2D(startCurvePoint, center) + + circle = circlePoints( + center, round(radius), 32 + ) # n=round((2 * pi * radius) / 32) + + # Find the correct point on the circle. + curveCornerPointsTemp = [startCurvePoint] + for point in circle: + if InTriangle(point, intersection, startCurvePoint, endCurvePoint): + curveCornerPointsTemp.append(point) + curveCornerPointsTemp.append(endCurvePoint) + + # Be sure that all the points are in correct order. + curveCornerPoints = optimizedPath(curveCornerPointsTemp, startCurvePoint) + return curveCornerPoints + + +def curveCornerIntersectionLine( + line0, line1, startDistance, angleAdaptation=False, center=() +): + """ + Create a continuous circular line between the two lines to smooth + the intersection. + + Args: + line0 (tuple): Tuple of tuple. Line coordinates. Order matters. + line1 (tuple): Tuple of tuple. Line coordinates. Order matters. + startDistance (int): distance from the intersection where the + curve should starts. + angleAdaptation (bool, optional): True will adapt the + startDistance depending of the angle between the two lines. + False will force the distance to be startDistance. Defaults to + False. + + Returns: + [list]: List of tuple of coordinates (2d) that forms the curve. + Starts on the line and end on the other line. + + TODO: + angleAdaptation : Set circle radius and not startDistance. + Polar coordinates / Unit circle instead of InTriangle. + + >>> curveCornerIntersectionLine(((0, 0), (50, 20)), ((-5, 50), (25, -5)), 10) + """ + intersection = lineIntersection(line0, line1, fullLine=True) + + if intersection == None: + return None + + # Define automatically the distance from the intersection, where the curve + # starts. + if angleAdaptation: + angle = getAngle( + (line0[0][0], line0[0][-1]), + intersection, + (line1[0][0], line1[0][-1]), + ) + # Set here the radius of the circle for a square angle. + startDistance = startDistance * abs(1 / (angle / 90)) + + startCurvePoint = circleLineSegmentIntersection( + intersection, startDistance, line0[0], intersection, fullLine=True + )[0] + endCurvePoint = circleLineSegmentIntersection( + intersection, startDistance, line1[0], intersection, fullLine=True + )[0] + # Higher value for better precision + perpendicular0 = perpendicular(10e3, startCurvePoint, intersection)[0] + perpendicular1 = perpendicular(10e3, endCurvePoint, intersection)[1] + + if center == (): + center = lineIntersection( + (perpendicular0, startCurvePoint), (perpendicular1, endCurvePoint) + ) + + # Distance with startCurvePoint and endCurvePoint from the center + # are almost the same. + radius = distance2D(startCurvePoint, center) + + circleArc = circle(center, round(radius))[0] + + # Find the correct point on the circle. + curveCornerPointsTemp = [startCurvePoint] + for point in circleArc: + if InTriangle(point, intersection, startCurvePoint, endCurvePoint): + curveCornerPointsTemp.append(point) + # curveCornerPointsTemp.append(endCurvePoint) + + # Be sure that all the points are in correct order. + curveCornerPoints = optimizedPath(curveCornerPointsTemp, startCurvePoint) + return curveCornerPoints, center diff --git a/networks/intersections/Intersection.py b/networks/intersections/Intersection.py new file mode 100644 index 0000000..cdd9713 --- /dev/null +++ b/networks/intersections/Intersection.py @@ -0,0 +1,3 @@ +class Intersection: + def __init__(self, Roads): + self.Roads = Roads diff --git a/networks/roads/roads.json b/networks/roads/roads.json index 83aeae8..027af7c 100644 --- a/networks/roads/roads.json +++ b/networks/roads/roads.json @@ -1,10 +1,11 @@ { - "high_way": { - "3": {"classic_lane": 3} - - }, - "broken_white": { - "3": {"white_concrete": 3, "white_concrete_powder": 1}, - "1": {"None": 1} - } + "high_way": [ + {"lane": "classic_lane", "width": 5, "number": 3}, + {"lane": "classic_divider", "width": 5, "number": 1}, + {"lane": "classic_lane", "width": 5, "number": 3} + ], + + "modern_road": [ + {"lane": "classic_lane", "width": 5, "number": 2} + ] } \ No newline at end of file From db3a8b24c1bbac02f5ffe90a0990e0bf78edc8c9 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Mon, 27 May 2024 20:43:49 +0200 Subject: [PATCH 17/69] Fix naming convention --- main.py | 18 +- networks/geometry/point.py | 212 ++++++------------ .../{ => roads}/intersections/Intersection.py | 0 networks/{ => roads}/lanes/Lane.py | 0 networks/{ => roads}/lanes/lanes.json | 0 networks/{ => roads}/lines/Line.py | 0 networks/{ => roads}/lines/lines.json | 0 7 files changed, 83 insertions(+), 147 deletions(-) rename networks/{ => roads}/intersections/Intersection.py (100%) rename networks/{ => roads}/lanes/Lane.py (100%) rename networks/{ => roads}/lanes/lanes.json (100%) rename networks/{ => roads}/lines/Line.py (100%) rename networks/{ => roads}/lines/lines.json (100%) diff --git a/main.py b/main.py index 947e690..27a74d0 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ -import networks.lines.Line as Line -import networks.lanes.Lane as Lane +import networks.roads.lines.Line as Line +import networks.roads.lanes.Lane as Lane from gdpc import Editor, Block, geometry import networks.geometry.curve as curve import networks.geometry.CurveSurface as CurveSurface @@ -9,7 +9,7 @@ import json from buildings.Building import Building import random -from networks.geometry.point import curveCornerIntersectionLine, curveCornerIntersectionPoints +from networks.geometry.point import curved_corner_intersection editor = Editor(buffering=True) @@ -107,11 +107,11 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # print(l.get_surface()) -circle = curveCornerIntersectionLine( - ((-1313, 392), (-1378, 415)), ((-1371, 348), (-1341, 439)), 30, angleAdaptation=True) +circle = curved_corner_intersection( + ((-1313, 392), (-1378, 415)), ((-1371, 348), (-1341, 439)), 30, angle_adaptation=True, output_only_points=False) -print(circle[0]) +print(circle) -for coordinate in circle[0]: - editor.placeBlock( - (round(coordinate[0]), 100, round(coordinate[1])), Block("green_concrete")) +# for coordinate in circle[0]: +# editor.placeBlock( +# (round(coordinate[0]), 100, round(coordinate[1])), Block("green_concrete")) diff --git a/networks/geometry/point.py b/networks/geometry/point.py index e659af4..a195205 100644 --- a/networks/geometry/point.py +++ b/networks/geometry/point.py @@ -2,7 +2,7 @@ from math import sqrt, cos, pi, sin import numpy as np -def circle(xyC, r): +def circle(center, radius): """ Can be used for circle or disc. @@ -16,21 +16,22 @@ def circle(xyC, r): for a disc, positive values for a hole. """ area = ( - (round(xyC[0]) - round(r), round(xyC[1]) - round(r)), - (round(xyC[0]) + round(r) + 1, round(xyC[1]) + round(r) + 1), + (round(center[0]) - round(radius), round(center[1]) - round(radius)), + (round(center[0]) + round(radius) + 1, + round(center[1]) + round(radius) + 1), ) circle = {} for x in range(area[0][0], area[1][0]): for y in range(area[0][1], area[1][1]): - d = round(distance2D((x, y), (xyC))) - r + d = round(distance((x, y), (center))) - radius if circle.get(d) == None: circle[d] = [] circle[d].append((x, y)) return circle -def InTriangle(point, xy0, xy1, xy2): +def is_in_triangle(point, xy0, xy1, xy2): # https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. dX = point[0] - xy0[0] dY = point[1] - xy0[1] @@ -49,11 +50,11 @@ def InTriangle(point, xy0, xy1, xy2): return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= D -def distance2D(A, B): # TODO : Can be better. - return sqrt((B[0] - A[0]) ** 2 + (B[1] - A[1]) ** 2) +def distance(xy1, xy2): # TODO : Can be better. + return sqrt((xy2[0] - xy1[0]) ** 2 + (xy2[1] - xy1[1]) ** 2) -def getAngle(xy0, xy1, xy2): +def get_angle(xy0, xy1, xy2): """ Compute angle (in degrees) for xy0, xy1, xy2 corner. @@ -77,7 +78,7 @@ def getAngle(xy0, xy1, xy2): return np.degrees(angle) -def circlePoints(center_point, radius, number=100): +def circle_points(center_point, radius, number=100): # https://stackoverflow.com/questions/8487893/generate-all-the-points-on-the-circumference-of-a-circle points = [ (cos(2 * pi / number * x) * radius, sin(2 * pi / number * x) * radius) @@ -93,7 +94,7 @@ def circlePoints(center_point, radius, number=100): return points -def optimizedPath(points, start=None): +def optimized_path(points, start=None): # https://stackoverflow.com/questions/45829155/sort-points-in-order-to-have-a-continuous-curve-using-python if start is None: start = points[0] @@ -101,17 +102,17 @@ def optimizedPath(points, start=None): path = [start] pass_by.remove(start) while pass_by: - nearest = min(pass_by, key=lambda x: distance2D(path[-1], x)) + nearest = min(pass_by, key=lambda x: distance(path[-1], x)) path.append(nearest) pass_by.remove(nearest) return path def nearest(points, start): - return min(points, key=lambda x: distance2D(start, x)) + return min(points, key=lambda x: distance(start, x)) -def sortRotation(points): +def sort_by_clockwise(points): """ Sort point in a rotation order. Works in 2d but supports 3d. @@ -124,7 +125,7 @@ def sortRotation(points): Returns: list: List of tuples of coordinates sorted (2d or 3d). - >>> sortRotation([(0, 45, 100), (4, -5, 5),(-5, 36, -2)]) + >>> sort_by_clockwise([(0, 45, 100), (4, -5, 5),(-5, 36, -2)]) [(0, 45, 100), (-5, 36, -2), (4, -5, 5)] """ x, y = [], [] @@ -150,21 +151,21 @@ def sortRotation(points): y_sorted = list(y[mask]) # Rearrange tuples to get the right coordinates. - sortedPoints = [] + sorted_points = [] for i in range(len(points)): j = 0 while (x_sorted[i] != points[j][0]) and (y_sorted[i] != points[j][-1]): j += 1 else: if len(points[0]) == 3: - sortedPoints.append((x_sorted[i], points[j][1], y_sorted[i])) + sorted_points.append((x_sorted[i], points[j][1], y_sorted[i])) else: - sortedPoints.append((x_sorted[i], y_sorted[i])) + sorted_points.append((x_sorted[i], y_sorted[i])) - return sortedPoints + return sorted_points -def lineIntersection(line0, line1, fullLine=True): +def segments_intersection(line0, line1, full_line=True): """ Find (or not) intersection between two lines. Works in 2d but supports 3d. @@ -174,13 +175,13 @@ def lineIntersection(line0, line1, fullLine=True): Args: line0 (tuple): Tuple of tuple of coordinates. line1 (tuple): Tuple of tuple of coordinates. - fullLine (bool, optional): True to find intersections along + full_line (bool, optional): True to find intersections along full line - not just in the segment. Returns: tuple: Coordinates (2d). - >>> lineIntersection(((0, 0), (0, 5)), ((2.5, 2.5), (-2.5, 2.5))) + >>> segments_intersection(((0, 0), (0, 5)), ((2.5, 2.5), (-2.5, 2.5))) """ xdiff = (line0[0][0] - line0[1][0], line1[0][0] - line1[1][0]) ydiff = (line0[0][-1] - line0[1][-1], line1[0][-1] - line1[1][-1]) @@ -196,7 +197,7 @@ def lineIntersection(line0, line1, fullLine=True): x = det(d, xdiff) / div y = det(d, ydiff) / div - if not fullLine: + if not full_line: if ( min(line0[0][0], line0[1][0]) <= x <= max(line0[0][0], line0[1][0]) and min(line1[0][0], line1[1][0]) @@ -216,8 +217,8 @@ def lineIntersection(line0, line1, fullLine=True): return x, y -def circleLineSegmentIntersection( - circleCenter, circleRadius, xy0, xy1, fullLine=True, tangentTol=1e-9 +def circle_segment_intersection( + circle_center, circle_radius, xy0, xy1, full_line=True, tangent_tolerance=1e-9 ): """ Find the points at which a circle intersects a line-segment. This @@ -227,16 +228,16 @@ def circleLineSegmentIntersection( Note: We follow: http://mathworld.wolfram.com/Circle-LineIntersection.html Args: - circleCenter (tuple): The (x, y) location of the circle center. - circleRadius (int): The radius of the circle. + circle_center (tuple): The (x, y) location of the circle center. + circle_radius (int): The radius of the circle. xy0 (tuple): The (x, y) location of the first point of the segment. xy1 ([tuple]): The (x, y) location of the second point of the segment. - fullLine (bool, optional): True to find intersections along + full_line (bool, optional): True to find intersections along full line - not just in the segment. False will just return intersections within the segment. Defaults to True. - tangentTol (float, optional): Numerical tolerance at which we + tangent_tolerance (float, optional): Numerical tolerance at which we decide the intersections are close enough to consider it a tangent. Defaults to 1e-9. @@ -248,13 +249,13 @@ def circleLineSegmentIntersection( (p1x, p1y), (p2x, p2y), (cx, cy) = ( (xy0[0], xy0[-1]), (xy1[0], xy1[-1]), - (circleCenter[0], circleCenter[1]), + (circle_center[0], circle_center[1]), ) (x1, y1), (x2, y2) = (p1x - cx, p1y - cy), (p2x - cx, p2y - cy) dx, dy = (x2 - x1), (y2 - y1) dr = (dx ** 2 + dy ** 2) ** 0.5 big_d = x1 * y2 - x2 * y1 - discriminant = circleRadius ** 2 * dr ** 2 - big_d ** 2 + discriminant = circle_radius ** 2 * dr ** 2 - big_d ** 2 if discriminant < 0: # No intersection between circle and line return [] @@ -274,7 +275,7 @@ def circleLineSegmentIntersection( for sign in ((1, -1) if dy < 0 else (-1, 1)) ] # This makes sure the order along the segment is correct if ( - not fullLine + not full_line ): # If only considering the segment, filter out intersections that do not fall within the segment fraction_along_segment = [ (xi - p1x) / dx if abs(dx) > abs(dy) else (yi - p1y) / dy @@ -286,7 +287,7 @@ def circleLineSegmentIntersection( if 0 <= frac <= 1 ] if ( - len(intersections) == 2 and abs(discriminant) <= tangentTol + len(intersections) == 2 and abs(discriminant) <= tangent_tolerance ): # If line is tangent to circle, return just one point (as both intersections have same location) return [intersections[0]] else: @@ -320,8 +321,8 @@ def perpendicular(distance, xy1, xy2): return ((round(x3), round(y3)), (round(x4), round(y4))) -def curveCornerIntersectionPoints( - line0, line1, startDistance, angleAdaptation=False +def curved_corner_intersection( + line0, line1, start_distance, angle_adaptation=False, full_line=False, center=(), output_only_points=True ): """ Create points between the two lines to smooth the intersection. @@ -329,140 +330,75 @@ def curveCornerIntersectionPoints( Args: line0 (tuple): Tuple of tuple. Line coordinates. Order matters. line1 (tuple): Tuple of tuple. Line coordinates. Order matters. - startDistance (int): distance from the intersection where the + start_distance (int): distance from the intersection where the curve should starts. angleAdaptation (bool, optional): True will adapt the - startDistance depending of the angle between the two lines. - False will force the distance to be startDistance. Defaults to + start_distance depending of the angle between the two lines. + False will force the distance to be start_distance. Defaults to False. Returns: [list]: List of tuple of coordinates (2d) that forms the curve. Starts on the line and end on the other line. - >>> curveCornerIntersectionPoints(((0, 0), (50, 20)), ((-5, 50), (25, -5)), 10) + >>> curved_corner_intersection(((0, 0), (50, 20)), ((-5, 50), (25, -5)), 10) """ - intersection = lineIntersection(line0, line1, fullLine=True) + intersection = segments_intersection(line0, line1, full_line) if intersection == None: return None # Define automatically the distance from the intersection, where the curve # starts. - if angleAdaptation: - angle = getAngle( + if angle_adaptation: + angle = get_angle( (line0[0][0], line0[0][-1]), intersection, (line1[0][0], line1[0][-1]), ) # Set here the radius of the circle for a square angle. - startDistance = startDistance * abs(1 / (angle / 90)) + start_distance = start_distance * abs(1 / (angle / 90)) - startCurvePoint = circleLineSegmentIntersection( - intersection, startDistance, line0[0], intersection, fullLine=True + start_curve_point = circle_segment_intersection( + intersection, start_distance, line0[0], intersection, full_line )[0] - endCurvePoint = circleLineSegmentIntersection( - intersection, startDistance, line1[0], intersection, fullLine=True + start_curve_point = ( + round(start_curve_point[0]), round(start_curve_point[1])) + end_curve_point = circle_segment_intersection( + intersection, start_distance, line1[0], intersection, full_line )[0] + end_curve_point = (round(end_curve_point[0]), round(end_curve_point[1])) # Higher value for better precision - perpendicular0 = perpendicular(10e3, startCurvePoint, intersection)[0] - perpendicular1 = perpendicular(10e3, endCurvePoint, intersection)[1] + perpendicular0 = perpendicular(10e3, start_curve_point, intersection)[0] + perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[1] - center = lineIntersection( - (perpendicular0, startCurvePoint), (perpendicular1, endCurvePoint) - ) + if center == (): + center = segments_intersection( + (perpendicular0, start_curve_point), (perpendicular1, end_curve_point) + ) + center = round(center[0]), round(center[1]) # Distance with startCurvePoint and endCurvePoint from the center are the # same. - radius = distance2D(startCurvePoint, center) + radius = round(distance(start_curve_point, center)) - circle = circlePoints( - center, round(radius), 32 - ) # n=round((2 * pi * radius) / 32) + if output_only_points: + circle_data = circle_points( + center, radius, 32 + ) # n=round((2 * pi * radius) / 32) + else: + circle_data = circle(center, radius)[0] # Find the correct point on the circle. - curveCornerPointsTemp = [startCurvePoint] - for point in circle: - if InTriangle(point, intersection, startCurvePoint, endCurvePoint): - curveCornerPointsTemp.append(point) - curveCornerPointsTemp.append(endCurvePoint) + curved_corner_points_temporary = [start_curve_point] + for point in circle_data: + if is_in_triangle(point, intersection, start_curve_point, end_curve_point): + curved_corner_points_temporary.append( + (round(point[0]), round(point[1]))) + if output_only_points: + curved_corner_points_temporary.append(end_curve_point) # Be sure that all the points are in correct order. - curveCornerPoints = optimizedPath(curveCornerPointsTemp, startCurvePoint) - return curveCornerPoints - - -def curveCornerIntersectionLine( - line0, line1, startDistance, angleAdaptation=False, center=() -): - """ - Create a continuous circular line between the two lines to smooth - the intersection. - - Args: - line0 (tuple): Tuple of tuple. Line coordinates. Order matters. - line1 (tuple): Tuple of tuple. Line coordinates. Order matters. - startDistance (int): distance from the intersection where the - curve should starts. - angleAdaptation (bool, optional): True will adapt the - startDistance depending of the angle between the two lines. - False will force the distance to be startDistance. Defaults to - False. - - Returns: - [list]: List of tuple of coordinates (2d) that forms the curve. - Starts on the line and end on the other line. - - TODO: - angleAdaptation : Set circle radius and not startDistance. - Polar coordinates / Unit circle instead of InTriangle. - - >>> curveCornerIntersectionLine(((0, 0), (50, 20)), ((-5, 50), (25, -5)), 10) - """ - intersection = lineIntersection(line0, line1, fullLine=True) - - if intersection == None: - return None - - # Define automatically the distance from the intersection, where the curve - # starts. - if angleAdaptation: - angle = getAngle( - (line0[0][0], line0[0][-1]), - intersection, - (line1[0][0], line1[0][-1]), - ) - # Set here the radius of the circle for a square angle. - startDistance = startDistance * abs(1 / (angle / 90)) - - startCurvePoint = circleLineSegmentIntersection( - intersection, startDistance, line0[0], intersection, fullLine=True - )[0] - endCurvePoint = circleLineSegmentIntersection( - intersection, startDistance, line1[0], intersection, fullLine=True - )[0] - # Higher value for better precision - perpendicular0 = perpendicular(10e3, startCurvePoint, intersection)[0] - perpendicular1 = perpendicular(10e3, endCurvePoint, intersection)[1] - - if center == (): - center = lineIntersection( - (perpendicular0, startCurvePoint), (perpendicular1, endCurvePoint) - ) - - # Distance with startCurvePoint and endCurvePoint from the center - # are almost the same. - radius = distance2D(startCurvePoint, center) - - circleArc = circle(center, round(radius))[0] - - # Find the correct point on the circle. - curveCornerPointsTemp = [startCurvePoint] - for point in circleArc: - if InTriangle(point, intersection, startCurvePoint, endCurvePoint): - curveCornerPointsTemp.append(point) - # curveCornerPointsTemp.append(endCurvePoint) - - # Be sure that all the points are in correct order. - curveCornerPoints = optimizedPath(curveCornerPointsTemp, startCurvePoint) - return curveCornerPoints, center + curve_corner_points = optimized_path( + curved_corner_points_temporary, start_curve_point) + return curve_corner_points, center, radius diff --git a/networks/intersections/Intersection.py b/networks/roads/intersections/Intersection.py similarity index 100% rename from networks/intersections/Intersection.py rename to networks/roads/intersections/Intersection.py diff --git a/networks/lanes/Lane.py b/networks/roads/lanes/Lane.py similarity index 100% rename from networks/lanes/Lane.py rename to networks/roads/lanes/Lane.py diff --git a/networks/lanes/lanes.json b/networks/roads/lanes/lanes.json similarity index 100% rename from networks/lanes/lanes.json rename to networks/roads/lanes/lanes.json diff --git a/networks/lines/Line.py b/networks/roads/lines/Line.py similarity index 100% rename from networks/lines/Line.py rename to networks/roads/lines/Line.py diff --git a/networks/lines/lines.json b/networks/roads/lines/lines.json similarity index 100% rename from networks/lines/lines.json rename to networks/roads/lines/lines.json From ba78bb45370a5c56a1e5990aae624287e3200134 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Mon, 27 May 2024 22:18:44 +0200 Subject: [PATCH 18/69] Rename files --- main.py | 8 +++---- .../geometry/{CurveSurface.py => Strip.py} | 24 +++++++++---------- .../geometry/{curve.py => curve_tools.py} | 6 ++--- .../geometry/{point.py => point_tools.py} | 0 .../geometry/{segment.py => segment_tools.py} | 0 networks/roads/lanes/Lane.py | 17 ++++++------- networks/roads/lines/Line.py | 12 ++++++---- 7 files changed, 35 insertions(+), 32 deletions(-) rename networks/geometry/{CurveSurface.py => Strip.py} (72%) rename networks/geometry/{curve.py => curve_tools.py} (96%) rename networks/geometry/{point.py => point_tools.py} (100%) rename networks/geometry/{segment.py => segment_tools.py} (100%) diff --git a/main.py b/main.py index 27a74d0..62447a1 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,15 @@ import networks.roads.lines.Line as Line import networks.roads.lanes.Lane as Lane from gdpc import Editor, Block, geometry -import networks.geometry.curve as curve -import networks.geometry.CurveSurface as CurveSurface -import networks.geometry.segment as segment +import networks.geometry.curve_tools as curve_tools +import networks.geometry.Strip as Strip +import networks.geometry.segment_tools as segment_tools import numpy as np import json from buildings.Building import Building import random -from networks.geometry.point import curved_corner_intersection +from networks.geometry.point_tools import curved_corner_intersection editor = Editor(buffering=True) diff --git a/networks/geometry/CurveSurface.py b/networks/geometry/Strip.py similarity index 72% rename from networks/geometry/CurveSurface.py rename to networks/geometry/Strip.py index ca830b4..5da1474 100644 --- a/networks/geometry/CurveSurface.py +++ b/networks/geometry/Strip.py @@ -1,28 +1,28 @@ -import networks.geometry.curve as curve -import networks.geometry.segment as segment +import networks.geometry.curve_tools as curve_tools +import networks.geometry.segment_tools as segment_tools import numpy as np -class CurveSurface: +class Strip: def __init__(self, points, reshape=True, spacing_distance=10): self.points = np.array(points) if reshape: - self.resolution, self.length = curve.resolution_distance( + self.resolution, self.length = curve_tools.resolution_distance( self.points, spacing_distance=spacing_distance) - self.curve = curve.curve(self.points, self.resolution) + self.curve = curve_tools.curve(self.points, self.resolution) else: # Point can also be given already in curved form self.curve = self.points def compute_curvature(self): - self.curvature = curve.curvature(self.curve) + self.curvature = curve_tools.curvature(self.curve) def compute_surface_perpendicular(self, width, normals): - self.offset_left = curve.offset(self.curve, width, normals) - self.offset_right = curve.offset(self.curve, -width, normals) + self.offset_left = curve_tools.offset(self.curve, width, normals) + self.offset_right = curve_tools.offset(self.curve, -width, normals) self.perpendicular_segment = [] for i in range(len(self.offset_left)): - self.perpendicular_segment.append(segment.discrete_segment( + self.perpendicular_segment.append(segment_tools.discrete_segment( self.offset_left[i], self.offset_right[i], pixel_perfect=False)) self.surface = [] @@ -45,14 +45,14 @@ class CurveSurface: self.surface[i].append([]) for k in range(len(self.perpendicular_segment[max_length_index])-1): - self.surface[i][j].append(segment.discrete_segment( + self.surface[i][j].append(segment_tools.discrete_segment( self.perpendicular_segment[max_length_index][k], self.perpendicular_segment[min_length_index][round(k * proportion)], pixel_perfect=False)) def compute_surface_parallel(self, inner_range, outer_range, resolution, normals): self.left_side = [] self.right_side = [] for current_range in range(inner_range * resolution, outer_range * resolution): - self.left_side.append(curve.offset( + self.left_side.append(curve_tools.offset( self.curve, current_range/resolution, normals)) - self.right_side.append(curve.offset( + self.right_side.append(curve_tools.offset( self.curve, -current_range/resolution, normals)) diff --git a/networks/geometry/curve.py b/networks/geometry/curve_tools.py similarity index 96% rename from networks/geometry/curve.py rename to networks/geometry/curve_tools.py index 5d3d00c..9b2fa3d 100644 --- a/networks/geometry/curve.py +++ b/networks/geometry/curve_tools.py @@ -1,5 +1,5 @@ import numpy as np -import networks.geometry.segment as segment +import networks.geometry.segment_tools as segment_tools from scipy import interpolate from math import sqrt @@ -83,14 +83,14 @@ def offset(curve, distance, normals): 'Number of normals and number of points in the curve do not match') # Offsetting - offset_segments = [segment.parallel( + offset_segments = [segment_tools.parallel( (curve[i], curve[i+1]), distance, normals[i]) for i in range(len(curve) - 1)] # Combining segments combined_curve = [] combined_curve.append(np.round(offset_segments[0][0]).tolist()) for i in range(0, len(offset_segments)-1): - combined_curve.append(segment.middle_point( + combined_curve.append(segment_tools.middle_point( offset_segments[i][1], offset_segments[i+1][0])) combined_curve.append(np.round(offset_segments[-1][1]).tolist()) diff --git a/networks/geometry/point.py b/networks/geometry/point_tools.py similarity index 100% rename from networks/geometry/point.py rename to networks/geometry/point_tools.py diff --git a/networks/geometry/segment.py b/networks/geometry/segment_tools.py similarity index 100% rename from networks/geometry/segment.py rename to networks/geometry/segment_tools.py diff --git a/networks/roads/lanes/Lane.py b/networks/roads/lanes/Lane.py index 3e3824e..803a0ef 100644 --- a/networks/roads/lanes/Lane.py +++ b/networks/roads/lanes/Lane.py @@ -1,6 +1,6 @@ -import networks.geometry.curve as curve -import networks.geometry.CurveSurface as CurveSurface -import networks.geometry.segment as segment +import networks.geometry.curve_tools as curve_tools +import networks.geometry.Strip as Strip +import networks.geometry.segment_tools as segment_tools import random @@ -12,10 +12,11 @@ class Lane: self.surface = [] def get_surface(self): - resolution, distance = curve.resolution_distance(self.coordinates, 6) + resolution, distance = curve_tools.resolution_distance( + self.coordinates, 6) - curve_points = curve.curve(self.coordinates, resolution) - curve_surface = CurveSurface.CurveSurface(self.coordinates) + curve_points = curve_tools.curve(self.coordinates, resolution) + curve_surface = Strip.Strip(self.coordinates) curve_surface.compute_curvature() # Set the road to be flat @@ -25,9 +26,9 @@ class Lane: # Compute each line for distance in range(self.width): - offset = curve.offset(curve_surface.curve, distance, normals) + offset = curve_tools.offset(curve_surface.curve, distance, normals) for i in range(len(offset)-1): - line = segment.discrete_segment(offset[i], offset[i+1]) + line = segment_tools.discrete_segment(offset[i], offset[i+1]) for coordinate in line: self.surface.append((coordinate, random.choices( list(self.lane_materials.keys()), diff --git a/networks/roads/lines/Line.py b/networks/roads/lines/Line.py index 0393d63..fb00dfc 100644 --- a/networks/roads/lines/Line.py +++ b/networks/roads/lines/Line.py @@ -1,5 +1,5 @@ -import networks.geometry.curve as curve -import networks.geometry.segment as segment +import networks.geometry.curve_tools as curve_tools +import networks.geometry.segment_tools as segment_tools import random @@ -10,9 +10,10 @@ class Line: self.surface = [] def get_surface(self): - resolution, distance = curve.resolution_distance(self.coordinates, 6) + resolution, distance = curve_tools.resolution_distance( + self.coordinates, 6) - curve_points = curve.curve(self.coordinates, resolution) + curve_points = curve_tools.curve(self.coordinates, resolution) # Compute the line @@ -26,7 +27,8 @@ class Line: pattern_iteration = 0 for i in range(len(curve_points)-1): - line = segment.discrete_segment(curve_points[i], curve_points[i+1]) + line = segment_tools.discrete_segment( + curve_points[i], curve_points[i+1]) for coordinate in line: block = random.choices( list(pattern_materials[pattern_iteration].keys()), From e879e9c034c1a17250c17b148d0cdcfa28c3a868 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 28 May 2024 00:51:29 +0200 Subject: [PATCH 19/69] First prototype of roads intersecitons --- main.py | 72 ++++++++++++++++++-- networks/geometry/point_tools.py | 13 +++- networks/roads/Road.py | 1 + networks/roads/intersections/Intersection.py | 29 +++++++- 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index 62447a1..d7fffcc 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,9 @@ import json from buildings.Building import Building import random +from networks.roads import Road as Road +from networks.roads.intersections import Intersection as Intersection + from networks.geometry.point_tools import curved_corner_intersection editor = Editor(buffering=True) @@ -93,25 +96,84 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # for coordinate in curve_surface.left_side[current_range]: # # editor.placeBlock(coordinate, Block("yellow_concrete")) +# --- # coordinates = [(0, 0, 0), (0, 0, 10), (0, 0, 20)] -# with open('networks/lines/lines.json') as f: +# with open('networks/roads/lines/lines.json') as f: # lines_type = json.load(f) # l = Line.Line(coordinates, lines_type.get('solid_white')) # print(l.get_surface()) -# with open('networks/lanes/lanes.json') as f: +# with open('networks/roads/lanes/lanes.json') as f: # lanes_type = json.load(f) # l = Lane.Lane(coordinates, lanes_type.get('classic_lane'), 5) # print(l.get_surface()) -circle = curved_corner_intersection( - ((-1313, 392), (-1378, 415)), ((-1371, 348), (-1341, 439)), 30, angle_adaptation=True, output_only_points=False) +# circle = curved_corner_intersection( +# ((-1313, 392), (-1378, 415)), ((-1371, 348), (-1341, 439)), 30, angle_adaptation=True, output_only_points=False) -print(circle) +# print(circle) # for coordinate in circle[0]: # editor.placeBlock( # (round(coordinate[0]), 100, round(coordinate[1])), Block("green_concrete")) + +# --- + +# r1 = Road.Road((-1341, 100, 439), "None") +# r2 = Road.Road((-1378, 100, 415), "None") + +# i = Intersection.Intersection( +# (-1352, 100, 405), [(-1345, 100, 426), (-1369, 100, 412)], [r1, r2]) + + +# --- + +r1 = Road.Road((-1337, 71, 472), "None") +r2 = Road.Road((-1269, 80, 574), "None") +r3 = Road.Road((-1392, 79, 527), "None") + +i = Intersection.Intersection( + (-1327, 71, 533), [(-1335, 71, 494), (-1298, 75, 553), (-1366, 78, 530)], [r1, r2, r3]) + +# --- + +# y = 100 + +# r1 = Road.Road((-1337, y, 472), "None") +# r2 = Road.Road((-1269, y, 574), "None") +# r3 = Road.Road((-1392, y, 527), "None") + +# i = Intersection.Intersection( +# (-1327, y, 533), [(-1335, y, 494), (-1298, y, 553), (-1366, y, 530)], [r1, r2, r3]) + + +i.compute_curved_corner() + +for j in range(len(i.orthogonal_delimitations)): + + coordinates = segment_tools.discrete_segment( + i.orthogonal_delimitations[j][0][0], i.orthogonal_delimitations[j][0][1]) + for coordinate in coordinates: + editor.placeBlock(coordinate, Block("purple_concrete")) + + coordinates = segment_tools.discrete_segment( + i.orthogonal_delimitations[j][1][0], i.orthogonal_delimitations[j][1][1]) + for coordinate in coordinates: + editor.placeBlock(coordinate, Block("pink_concrete")) + + coordinates = segment_tools.discrete_segment( + i.parallel_delimitations[j][0][0], i.parallel_delimitations[j][0][1]) + for coordinate in coordinates: + editor.placeBlock(coordinate, Block("orange_concrete")) + + coordinates = segment_tools.discrete_segment( + i.parallel_delimitations[j][1][0], i.parallel_delimitations[j][1][1]) + for coordinate in coordinates: + editor.placeBlock(coordinate, Block("yellow_concrete")) + +for coordinate in i.intersections: + if coordinate != None: + editor.placeBlock(coordinate, Block("black_concrete")) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index a195205..9182efe 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -1,5 +1,6 @@ from math import sqrt, cos, pi, sin import numpy as np +from networks.geometry.segment_tools import discrete_segment, middle_point def circle(center, radius): @@ -51,7 +52,7 @@ def is_in_triangle(point, xy0, xy1, xy2): def distance(xy1, xy2): # TODO : Can be better. - return sqrt((xy2[0] - xy1[0]) ** 2 + (xy2[1] - xy1[1]) ** 2) + return sqrt((xy2[0] - xy1[0]) ** 2 + (xy2[-1] - xy1[-1]) ** 2) def get_angle(xy0, xy1, xy2): @@ -210,11 +211,17 @@ def segments_intersection(line0, line1, full_line=True): <= y <= max(line1[0][-1], line1[1][-1]) ): - return x, y + if len(line0[0]) > 2: + return middle_point(nearest(discrete_segment(line1[0], line1[1], pixel_perfect=True), (x, y)), nearest(discrete_segment(line0[0], line0[1], pixel_perfect=True), (x, y))) + else: + return x, y else: return None else: - return x, y + if len(line0[0]) > 2: + return middle_point(nearest(discrete_segment(line1[0], line1[1], pixel_perfect=True), (x, y)), nearest(discrete_segment(line0[0], line0[1], pixel_perfect=True), (x, y))) + else: + return x, y def circle_segment_intersection( diff --git a/networks/roads/Road.py b/networks/roads/Road.py index 176c9f9..4eeb66b 100644 --- a/networks/roads/Road.py +++ b/networks/roads/Road.py @@ -2,6 +2,7 @@ class Road: def __init__(self, coordinates, road_configuration): self.coordinates = coordinates # List of tuples (x1, y1, z1) in order self.road_configuration = road_configuration # 'road', 'highway' + self.width = 10 # TODO def place_roads(self): pass diff --git a/networks/roads/intersections/Intersection.py b/networks/roads/intersections/Intersection.py index cdd9713..a29db30 100644 --- a/networks/roads/intersections/Intersection.py +++ b/networks/roads/intersections/Intersection.py @@ -1,3 +1,30 @@ +from networks.geometry.segment_tools import parallel, orthogonal +from networks.geometry.point_tools import sort_by_clockwise, segments_intersection +from networks.roads import Road + + class Intersection: - def __init__(self, Roads): + def __init__(self, center, coordinates, Roads): + self.center = center + self.coordinates = coordinates self.Roads = Roads + self.parallel_delimitations = [] + self.orthogonal_delimitations = [] + self.intersections = [] + + def compute_curved_corner(self): + # Necessary to test nearby intersection + self.coordinates = sort_by_clockwise(self.coordinates) + + for i, coordinate in enumerate(self.coordinates): + right_side, left_side = parallel((coordinate, self.center), self.Roads[i].width), parallel( + (coordinate, self.center), -self.Roads[i].width) + self.parallel_delimitations.append((right_side, left_side)) + self.orthogonal_delimitations.append( + ((right_side[0], left_side[0]), (right_side[-1], left_side[-1]))) + + for j in range(len(self.Roads)): + self.intersections.append(segments_intersection( + self.parallel_delimitations[j][1], self.parallel_delimitations[(j+1) % len(self.Roads)][0], full_line=False)) + + print(self.intersections) From 01b88be2fbf69203ed1374c3392344e07f106bfd Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 28 May 2024 02:20:29 +0200 Subject: [PATCH 20/69] Fix intersection curved corners --- main.py | 41 +++++++++++--------- networks/geometry/point_tools.py | 21 ++++++---- networks/roads/intersections/Intersection.py | 19 ++++++++- 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/main.py b/main.py index d7fffcc..65ee6d7 100644 --- a/main.py +++ b/main.py @@ -111,14 +111,12 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # print(l.get_surface()) -# circle = curved_corner_intersection( -# ((-1313, 392), (-1378, 415)), ((-1371, 348), (-1341, 439)), 30, angle_adaptation=True, output_only_points=False) +circle = curved_corner_intersection( + ((-1365, 520), (-1326, 523)), ((-1344, 496), (-1336, 535)), 10, angle_adaptation=False, output_only_points=False) -# print(circle) - -# for coordinate in circle[0]: -# editor.placeBlock( -# (round(coordinate[0]), 100, round(coordinate[1])), Block("green_concrete")) +for coordinate in circle[0]: + editor.placeBlock( + (round(coordinate[0]), 125, round(coordinate[1])), Block("green_concrete")) # --- @@ -131,23 +129,23 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # --- -r1 = Road.Road((-1337, 71, 472), "None") -r2 = Road.Road((-1269, 80, 574), "None") -r3 = Road.Road((-1392, 79, 527), "None") +# r1 = Road.Road((-1337, 71, 472), "None") +# r2 = Road.Road((-1269, 80, 574), "None") +# r3 = Road.Road((-1392, 79, 527), "None") -i = Intersection.Intersection( - (-1327, 71, 533), [(-1335, 71, 494), (-1298, 75, 553), (-1366, 78, 530)], [r1, r2, r3]) +# i = Intersection.Intersection( +# (-1327, 71, 533), [(-1335, 71, 494), (-1298, 75, 553), (-1366, 78, 530)], [r1, r2, r3]) # --- -# y = 100 +y = 150 -# r1 = Road.Road((-1337, y, 472), "None") -# r2 = Road.Road((-1269, y, 574), "None") -# r3 = Road.Road((-1392, y, 527), "None") +r1 = Road.Road((-1337, y, 472), "None") +r2 = Road.Road((-1269, y, 574), "None") +r3 = Road.Road((-1392, y, 527), "None") -# i = Intersection.Intersection( -# (-1327, y, 533), [(-1335, y, 494), (-1298, y, 553), (-1366, y, 530)], [r1, r2, r3]) +i = Intersection.Intersection( + (-1327, y, 533), [(-1335, y, 494), (-1298, y, 553), (-1366, y, 530)], [r1, r2, r3]) i.compute_curved_corner() @@ -177,3 +175,10 @@ for j in range(len(i.orthogonal_delimitations)): for coordinate in i.intersections: if coordinate != None: editor.placeBlock(coordinate, Block("black_concrete")) + +for k in range(len(i.intersections_curved)): + for coordinate in i.intersections_curved[k][0]: + if coordinate != None: + if k >= 0: + editor.placeBlock( + (coordinate[0], y, coordinate[1]), Block("cyan_concrete")) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 9182efe..31ef5e5 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -256,7 +256,7 @@ def circle_segment_intersection( (p1x, p1y), (p2x, p2y), (cx, cy) = ( (xy0[0], xy0[-1]), (xy1[0], xy1[-1]), - (circle_center[0], circle_center[1]), + (circle_center[0], circle_center[-1]), ) (x1, y1), (x2, y2) = (p1x - cx, p1y - cy), (p2x - cx, p2y - cy) dx, dy = (x2 - x1), (y2 - y1) @@ -314,8 +314,8 @@ def perpendicular(distance, xy1, xy2): tuple: Coordinates of the line length distance, perpendicular to [xy1; xy2] at xy1. """ - (x1, y1) = xy1 - (x2, y2) = xy2 + (x1, y1) = xy1[0], xy1[-1] + (x2, y2) = xy2[0], xy2[-1] dx = x1 - x2 dy = y1 - y2 dist = sqrt(dx * dx + dy * dy) @@ -329,7 +329,7 @@ def perpendicular(distance, xy1, xy2): def curved_corner_intersection( - line0, line1, start_distance, angle_adaptation=False, full_line=False, center=(), output_only_points=True + line0, line1, start_distance, angle_adaptation=False, full_line=True, center=(), output_only_points=True ): """ Create points between the two lines to smooth the intersection. @@ -350,6 +350,8 @@ def curved_corner_intersection( >>> curved_corner_intersection(((0, 0), (50, 20)), ((-5, 50), (25, -5)), 10) """ + print("\nInput:") + print(line0, line1) intersection = segments_intersection(line0, line1, full_line) if intersection == None: @@ -370,20 +372,20 @@ def curved_corner_intersection( intersection, start_distance, line0[0], intersection, full_line )[0] start_curve_point = ( - round(start_curve_point[0]), round(start_curve_point[1])) + round(start_curve_point[0]), round(start_curve_point[-1])) end_curve_point = circle_segment_intersection( intersection, start_distance, line1[0], intersection, full_line )[0] - end_curve_point = (round(end_curve_point[0]), round(end_curve_point[1])) + end_curve_point = (round(end_curve_point[0]), round(end_curve_point[-1])) # Higher value for better precision perpendicular0 = perpendicular(10e3, start_curve_point, intersection)[0] - perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[1] + perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[-1] if center == (): center = segments_intersection( (perpendicular0, start_curve_point), (perpendicular1, end_curve_point) ) - center = round(center[0]), round(center[1]) + center = round(center[0]), round(center[-1]) # Distance with startCurvePoint and endCurvePoint from the center are the # same. @@ -408,4 +410,7 @@ def curved_corner_intersection( # Be sure that all the points are in correct order. curve_corner_points = optimized_path( curved_corner_points_temporary, start_curve_point) + print("Output:") + print(curve_corner_points) + print("\n") return curve_corner_points, center, radius diff --git a/networks/roads/intersections/Intersection.py b/networks/roads/intersections/Intersection.py index a29db30..dcf9446 100644 --- a/networks/roads/intersections/Intersection.py +++ b/networks/roads/intersections/Intersection.py @@ -1,5 +1,5 @@ from networks.geometry.segment_tools import parallel, orthogonal -from networks.geometry.point_tools import sort_by_clockwise, segments_intersection +from networks.geometry.point_tools import sort_by_clockwise, segments_intersection, curved_corner_intersection from networks.roads import Road @@ -11,6 +11,7 @@ class Intersection: self.parallel_delimitations = [] self.orthogonal_delimitations = [] self.intersections = [] + self.intersections_curved = [] def compute_curved_corner(self): # Necessary to test nearby intersection @@ -27,4 +28,18 @@ class Intersection: self.intersections.append(segments_intersection( self.parallel_delimitations[j][1], self.parallel_delimitations[(j+1) % len(self.Roads)][0], full_line=False)) - print(self.intersections) + test = tuple(self.parallel_delimitations[( + j+1) % len(self.Roads)][0][0]), tuple(self.parallel_delimitations[(j+1) % len(self.Roads)][0][1]) + test0 = tuple(self.parallel_delimitations[j][1][0]), tuple( + self.parallel_delimitations[j][1][1]) + + print("\n\n\n --- \n\n\n") + print(self.parallel_delimitations) + print(self.parallel_delimitations[( + j+1) % len(self.Roads)][0]) + print(self.parallel_delimitations[j][1]) + + self.intersections_curved.append(curved_corner_intersection( + ((test0[0][0], test0[0][-1]), (test0[1][0], test0[1][-1])), ((test[0][0], test[0][-1]), (test[1][0], test[1][-1])), 10, angle_adaptation=False, output_only_points=False)) + + print("\n", test0, test) From bfe6851a6cc31d4a7c0b597ab55c6fd77029c713 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Fri, 31 May 2024 19:33:44 +0200 Subject: [PATCH 21/69] Remove debug prints --- main.py | 27 +++++++++++++++----- networks/geometry/point_tools.py | 3 --- networks/roads/intersections/Intersection.py | 14 +++------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/main.py b/main.py index 65ee6d7..16e1490 100644 --- a/main.py +++ b/main.py @@ -138,15 +138,28 @@ for coordinate in circle[0]: # --- -y = 150 +# y = 150 -r1 = Road.Road((-1337, y, 472), "None") -r2 = Road.Road((-1269, y, 574), "None") -r3 = Road.Road((-1392, y, 527), "None") +# r1 = Road.Road((-1337, y, 472), "None") +# r2 = Road.Road((-1269, y, 574), "None") +# r3 = Road.Road((-1392, y, 527), "None") + +# i = Intersection.Intersection( +# (-1327, y, 533), [(-1335, y, 494), (-1298, y, 553), (-1366, y, 530)], [r1, r2, r3]) + + +# --- + +y = 100 + +r1 = Road.Road((-1380, 75, 406), "None") +r2 = Road.Road((-1365, 75, 468), "None") +r3 = Road.Road((-1411, 75, 501), "None") +r4 = Road.Road((-1451, 75, 449), "None") +r5 = Road.Road((-1432, 75, 423), "None") i = Intersection.Intersection( - (-1327, y, 533), [(-1335, y, 494), (-1298, y, 553), (-1366, y, 530)], [r1, r2, r3]) - + (-1411, 75, 461), [(-1392, 75, 427), (-1385, 75, 465), (-1411, 75, 487), (-1435, 75, 454), (-1426, 75, 435)], [r1, r2, r3, r4, r5]) i.compute_curved_corner() @@ -181,4 +194,4 @@ for k in range(len(i.intersections_curved)): if coordinate != None: if k >= 0: editor.placeBlock( - (coordinate[0], y, coordinate[1]), Block("cyan_concrete")) + (coordinate[0], 75, coordinate[1]), Block("cyan_concrete")) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 31ef5e5..fa5942c 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -410,7 +410,4 @@ def curved_corner_intersection( # Be sure that all the points are in correct order. curve_corner_points = optimized_path( curved_corner_points_temporary, start_curve_point) - print("Output:") - print(curve_corner_points) - print("\n") return curve_corner_points, center, radius diff --git a/networks/roads/intersections/Intersection.py b/networks/roads/intersections/Intersection.py index dcf9446..1e80418 100644 --- a/networks/roads/intersections/Intersection.py +++ b/networks/roads/intersections/Intersection.py @@ -28,18 +28,10 @@ class Intersection: self.intersections.append(segments_intersection( self.parallel_delimitations[j][1], self.parallel_delimitations[(j+1) % len(self.Roads)][0], full_line=False)) - test = tuple(self.parallel_delimitations[( + next_parallel = tuple(self.parallel_delimitations[( j+1) % len(self.Roads)][0][0]), tuple(self.parallel_delimitations[(j+1) % len(self.Roads)][0][1]) - test0 = tuple(self.parallel_delimitations[j][1][0]), tuple( + current_parallel = tuple(self.parallel_delimitations[j][1][0]), tuple( self.parallel_delimitations[j][1][1]) - print("\n\n\n --- \n\n\n") - print(self.parallel_delimitations) - print(self.parallel_delimitations[( - j+1) % len(self.Roads)][0]) - print(self.parallel_delimitations[j][1]) - self.intersections_curved.append(curved_corner_intersection( - ((test0[0][0], test0[0][-1]), (test0[1][0], test0[1][-1])), ((test[0][0], test[0][-1]), (test[1][0], test[1][-1])), 10, angle_adaptation=False, output_only_points=False)) - - print("\n", test0, test) + ((current_parallel[0][0], current_parallel[0][-1]), (current_parallel[1][0], current_parallel[1][-1])), ((next_parallel[0][0], next_parallel[0][-1]), (next_parallel[1][0], next_parallel[1][-1])), 10, angle_adaptation=True, output_only_points=False)) From 639828711618b9009f633552d914c6312cebddce Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 4 Jun 2024 15:59:39 +0200 Subject: [PATCH 22/69] Refactor curved_corner_by_distance --- main.py | 14 +- networks/geometry/point_tools.py | 151 +++++++++++-------- networks/geometry/segment_tools.py | 2 - networks/roads/intersections/Intersection.py | 11 +- 4 files changed, 103 insertions(+), 75 deletions(-) diff --git a/main.py b/main.py index 16e1490..c1256bc 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ import random from networks.roads import Road as Road from networks.roads.intersections import Intersection as Intersection -from networks.geometry.point_tools import curved_corner_intersection +from networks.geometry.point_tools import curved_corner editor = Editor(buffering=True) @@ -111,12 +111,12 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # print(l.get_surface()) -circle = curved_corner_intersection( - ((-1365, 520), (-1326, 523)), ((-1344, 496), (-1336, 535)), 10, angle_adaptation=False, output_only_points=False) +# circle = curved_corner( +# ((-1365, 520), (-1326, 523)), ((-1344, 496), (-1336, 535)), 10, angle_adaptation=False, output_only_points=False) -for coordinate in circle[0]: - editor.placeBlock( - (round(coordinate[0]), 125, round(coordinate[1])), Block("green_concrete")) +# for coordinate in circle[0]: +# editor.placeBlock( +# (round(coordinate[0]), 125, round(coordinate[1])), Block("green_concrete")) # --- @@ -194,4 +194,4 @@ for k in range(len(i.intersections_curved)): if coordinate != None: if k >= 0: editor.placeBlock( - (coordinate[0], 75, coordinate[1]), Block("cyan_concrete")) + (coordinate[0], 75, coordinate[1]), Block("brown_concrete")) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index fa5942c..616d43c 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -1,6 +1,6 @@ from math import sqrt, cos, pi, sin import numpy as np -from networks.geometry.segment_tools import discrete_segment, middle_point +from networks.geometry.segment_tools import discrete_segment, middle_point, parallel def circle(center, radius): @@ -35,11 +35,11 @@ def circle(center, radius): def is_in_triangle(point, xy0, xy1, xy2): # https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. dX = point[0] - xy0[0] - dY = point[1] - xy0[1] + dY = point[-1] - xy0[-1] dX20 = xy2[0] - xy0[0] - dY20 = xy2[1] - xy0[1] + dY20 = xy2[-1] - xy0[-1] dX10 = xy1[0] - xy0[0] - dY10 = xy1[1] - xy0[1] + dY10 = xy1[-1] - xy0[-1] s_p = (dY20 * dX) - (dX20 * dY) t_p = (dX10 * dY) - (dY10 * dX) @@ -328,77 +328,48 @@ def perpendicular(distance, xy1, xy2): return ((round(x3), round(y3)), (round(x4), round(y4))) -def curved_corner_intersection( - line0, line1, start_distance, angle_adaptation=False, full_line=True, center=(), output_only_points=True +def curved_corner( + intersection, xyz0, xyz1, distance, curvature, full_line=True, output_only_points=True ): - """ - Create points between the two lines to smooth the intersection. + # If curvature radius is set, compute the center of the circle as the intersection between the two lines, offseted by the curvature radius. + if curvature != None: + center = segments_intersection(parallel( + (xyz0, intersection), curvature), parallel((xyz1, intersection), -curvature)) - Args: - line0 (tuple): Tuple of tuple. Line coordinates. Order matters. - line1 (tuple): Tuple of tuple. Line coordinates. Order matters. - start_distance (int): distance from the intersection where the - curve should starts. - angleAdaptation (bool, optional): True will adapt the - start_distance depending of the angle between the two lines. - False will force the distance to be start_distance. Defaults to - False. + # If distance is set, compute where the arc should merge on the two intersecting lines. + elif distance != None: + start_curve_point = circle_segment_intersection( + intersection, distance, xy0[0], intersection, full_line + )[0] + start_curve_point = ( + round(start_curve_point[0]), round(start_curve_point[-1])) - Returns: - [list]: List of tuple of coordinates (2d) that forms the curve. - Starts on the line and end on the other line. + end_curve_point = circle_segment_intersection( + intersection, distance, xy1[0], intersection, full_line + )[0] + end_curve_point = ( + round(end_curve_point[0]), round(end_curve_point[-1])) - >>> curved_corner_intersection(((0, 0), (50, 20)), ((-5, 50), (25, -5)), 10) - """ - print("\nInput:") - print(line0, line1) - intersection = segments_intersection(line0, line1, full_line) + # Then compute the center as the intersection between perpendicular segment at the points computed before. + # Higher value for better precision + perpendicular0 = perpendicular( + 10e3, start_curve_point, intersection)[0] + perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[-1] - if intersection == None: - return None - - # Define automatically the distance from the intersection, where the curve - # starts. - if angle_adaptation: - angle = get_angle( - (line0[0][0], line0[0][-1]), - intersection, - (line1[0][0], line1[0][-1]), - ) - # Set here the radius of the circle for a square angle. - start_distance = start_distance * abs(1 / (angle / 90)) - - start_curve_point = circle_segment_intersection( - intersection, start_distance, line0[0], intersection, full_line - )[0] - start_curve_point = ( - round(start_curve_point[0]), round(start_curve_point[-1])) - end_curve_point = circle_segment_intersection( - intersection, start_distance, line1[0], intersection, full_line - )[0] - end_curve_point = (round(end_curve_point[0]), round(end_curve_point[-1])) - # Higher value for better precision - perpendicular0 = perpendicular(10e3, start_curve_point, intersection)[0] - perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[-1] - - if center == (): center = segments_intersection( - (perpendicular0, start_curve_point), (perpendicular1, end_curve_point) - ) + (perpendicular0, start_curve_point), (perpendicular1, end_curve_point)) center = round(center[0]), round(center[-1]) - # Distance with startCurvePoint and endCurvePoint from the center are the - # same. - radius = round(distance(start_curve_point, center)) + curvature = round(distance(start_curve_point, center)) if output_only_points: circle_data = circle_points( - center, radius, 32 - ) # n=round((2 * pi * radius) / 32) + center, curvature, 32 + ) else: - circle_data = circle(center, radius)[0] + circle_data = circle(center, curvature) - # Find the correct point on the circle. + # Find the correct points on the circle. curved_corner_points_temporary = [start_curve_point] for point in circle_data: if is_in_triangle(point, intersection, start_curve_point, end_curve_point): @@ -410,4 +381,58 @@ def curved_corner_intersection( # Be sure that all the points are in correct order. curve_corner_points = optimized_path( curved_corner_points_temporary, start_curve_point) - return curve_corner_points, center, radius + return curve_corner_points, center, curvature + + +def curved_corner_by_distance( + intersection, xyz0, xyz1, distance_from_intersection, resolution, full_line=True +): + # Comute the merging point on the first line + start_curve_point = circle_segment_intersection( + intersection, distance_from_intersection, xyz0, intersection, full_line + )[0] + start_curve_point = ( + round(start_curve_point[0]), round(start_curve_point[-1])) + + # Comute the merging point on the second line + end_curve_point = circle_segment_intersection( + intersection, distance_from_intersection, xyz1, intersection, full_line + )[0] + end_curve_point = ( + round(end_curve_point[0]), round(end_curve_point[-1])) + + # Compute the intersection between perpendicular lines at the merging points + # Higher value for better precision + perpendicular0 = perpendicular( + 10e3, start_curve_point, intersection)[0] + perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[-1] + + center = segments_intersection( + (perpendicular0, start_curve_point), (perpendicular1, end_curve_point)) + center = round(center[0]), round(center[-1]) + + # Compute the curvature for indications + curvature = round(distance(start_curve_point, center)) + + # Return a full discrete circle or only some points of it + if resolution != 0: + circle_data = circle_points( + center, curvature, resolution + ) + else: + circle_data = circle(center, curvature)[0] + + # Find the correct points on the circle. + curved_corner_points_temporary = [start_curve_point] + for point in circle_data: + print(point, intersection, start_curve_point, end_curve_point, is_in_triangle( + point, intersection, start_curve_point, end_curve_point)) + if is_in_triangle(point, intersection, start_curve_point, end_curve_point): + curved_corner_points_temporary.append( + (round(point[0]), round(point[1]))) + curved_corner_points_temporary.append(end_curve_point) + + # Be sure that all the points are in correct order. + curve_corner_points = optimized_path( + curved_corner_points_temporary, start_curve_point) + return curve_corner_points, center, curvature diff --git a/networks/geometry/segment_tools.py b/networks/geometry/segment_tools.py index f45f6ed..e2c5b0a 100644 --- a/networks/geometry/segment_tools.py +++ b/networks/geometry/segment_tools.py @@ -50,8 +50,6 @@ def orthogonal(origin, point, distance, normal=np.array([0, 1, 0])): orthogonal = np.cross(normalized_vector, normalized_normal) if np.array_equal(orthogonal, np.zeros((3,))): - print(normalized_vector, normalized_normal, orthogonal, normal) - print(origin, point, distance) raise ValueError("The input vectors are not linearly independent.") orthogonal = np.add(np.multiply(orthogonal, distance), origin).astype(int) diff --git a/networks/roads/intersections/Intersection.py b/networks/roads/intersections/Intersection.py index 1e80418..44e46c6 100644 --- a/networks/roads/intersections/Intersection.py +++ b/networks/roads/intersections/Intersection.py @@ -1,5 +1,5 @@ from networks.geometry.segment_tools import parallel, orthogonal -from networks.geometry.point_tools import sort_by_clockwise, segments_intersection, curved_corner_intersection +from networks.geometry.point_tools import sort_by_clockwise, segments_intersection, curved_corner_by_distance from networks.roads import Road @@ -33,5 +33,10 @@ class Intersection: current_parallel = tuple(self.parallel_delimitations[j][1][0]), tuple( self.parallel_delimitations[j][1][1]) - self.intersections_curved.append(curved_corner_intersection( - ((current_parallel[0][0], current_parallel[0][-1]), (current_parallel[1][0], current_parallel[1][-1])), ((next_parallel[0][0], next_parallel[0][-1]), (next_parallel[1][0], next_parallel[1][-1])), 10, angle_adaptation=True, output_only_points=False)) + intersection2d = segments_intersection(((current_parallel[0][0], current_parallel[0][-1]), (current_parallel[1][0], current_parallel[1][-1])), (( + next_parallel[0][0], next_parallel[0][-1]), (next_parallel[1][0], next_parallel[1][-1])), full_line=False) + + intersection = ( + round(intersection2d[0]), 100, round(intersection2d[1])) + self.intersections_curved.append(curved_corner_by_distance( + intersection, current_parallel[0], next_parallel[0], 10, 0, full_line=True)) From 0eb39fc4a448d31c43602bc406f7e7d120e92461 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 4 Jun 2024 18:47:22 +0200 Subject: [PATCH 23/69] Redo curved_corner_by_distance --- main.py | 109 ++++++++----- networks/geometry/point_tools.py | 151 ++++++++++--------- networks/roads/intersections/Intersection.py | 7 +- 3 files changed, 156 insertions(+), 111 deletions(-) diff --git a/main.py b/main.py index c1256bc..afbe8f3 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ import random from networks.roads import Road as Road from networks.roads.intersections import Intersection as Intersection -from networks.geometry.point_tools import curved_corner +from networks.geometry.point_tools import curved_corner_by_curvature, curved_corner_by_distance editor = Editor(buffering=True) @@ -150,48 +150,85 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # --- -y = 100 +# y = 100 +# x = -200 -r1 = Road.Road((-1380, 75, 406), "None") -r2 = Road.Road((-1365, 75, 468), "None") -r3 = Road.Road((-1411, 75, 501), "None") -r4 = Road.Road((-1451, 75, 449), "None") -r5 = Road.Road((-1432, 75, 423), "None") +# r1 = Road.Road((-1380+x, 75, 406), "None") +# r2 = Road.Road((-1365+x, 75, 468), "None") +# r3 = Road.Road((-1411+x, 75, 501), "None") +# r4 = Road.Road((-1451+x, 75, 449), "None") +# r5 = Road.Road((-1432+x, 75, 423), "None") -i = Intersection.Intersection( - (-1411, 75, 461), [(-1392, 75, 427), (-1385, 75, 465), (-1411, 75, 487), (-1435, 75, 454), (-1426, 75, 435)], [r1, r2, r3, r4, r5]) +# i = Intersection.Intersection( +# (-1411+x, 75, 461), [(-1392+x, 75, 427), (-1385+x, 75, 465), (-1411+x, 75, 487), (-1435+x, 75, 454), (-1426+x, 75, 435)], [r1, r2, r3, r4, r5]) -i.compute_curved_corner() +# i.compute_curved_corner() -for j in range(len(i.orthogonal_delimitations)): +# for j in range(len(i.orthogonal_delimitations)): - coordinates = segment_tools.discrete_segment( - i.orthogonal_delimitations[j][0][0], i.orthogonal_delimitations[j][0][1]) - for coordinate in coordinates: - editor.placeBlock(coordinate, Block("purple_concrete")) +# coordinates = segment_tools.discrete_segment( +# i.orthogonal_delimitations[j][0][0], i.orthogonal_delimitations[j][0][1]) +# for coordinate in coordinates: +# editor.placeBlock(coordinate, Block("purple_concrete")) - coordinates = segment_tools.discrete_segment( - i.orthogonal_delimitations[j][1][0], i.orthogonal_delimitations[j][1][1]) - for coordinate in coordinates: - editor.placeBlock(coordinate, Block("pink_concrete")) +# coordinates = segment_tools.discrete_segment( +# i.orthogonal_delimitations[j][1][0], i.orthogonal_delimitations[j][1][1]) +# for coordinate in coordinates: +# editor.placeBlock(coordinate, Block("pink_concrete")) - coordinates = segment_tools.discrete_segment( - i.parallel_delimitations[j][0][0], i.parallel_delimitations[j][0][1]) - for coordinate in coordinates: - editor.placeBlock(coordinate, Block("orange_concrete")) +# coordinates = segment_tools.discrete_segment( +# i.parallel_delimitations[j][0][0], i.parallel_delimitations[j][0][1]) +# for coordinate in coordinates: +# editor.placeBlock(coordinate, Block("orange_concrete")) - coordinates = segment_tools.discrete_segment( - i.parallel_delimitations[j][1][0], i.parallel_delimitations[j][1][1]) - for coordinate in coordinates: - editor.placeBlock(coordinate, Block("yellow_concrete")) +# coordinates = segment_tools.discrete_segment( +# i.parallel_delimitations[j][1][0], i.parallel_delimitations[j][1][1]) +# for coordinate in coordinates: +# editor.placeBlock(coordinate, Block("yellow_concrete")) -for coordinate in i.intersections: - if coordinate != None: - editor.placeBlock(coordinate, Block("black_concrete")) +# for coordinate in i.intersections: +# if coordinate != None: +# editor.placeBlock(coordinate, Block("black_concrete")) -for k in range(len(i.intersections_curved)): - for coordinate in i.intersections_curved[k][0]: - if coordinate != None: - if k >= 0: - editor.placeBlock( - (coordinate[0], 75, coordinate[1]), Block("brown_concrete")) +# for k in range(len(i.intersections_curved)): +# for coordinate in i.intersections_curved[k][0]: +# if coordinate != None: +# if k >= 0: +# editor.placeBlock( +# (coordinate[0], 75, coordinate[1]), Block("gray_concrete")) + +# editor.placeBlock( +# (i.intersections_curved[k][1][0], 76, i.intersections_curved[k][1][1]), Block("black_concrete")) + +# coordinates = segment_tools.discrete_segment( +# i.intersections_curved[k][-1][0], i.intersections_curved[k][-1][1]) +# for coordinate in coordinates: +# editor.placeBlock(coordinate, Block("lime_concrete")) + +# coordinates = segment_tools.discrete_segment( +# i.intersections_curved[k][-2][0], i.intersections_curved[k][-2][1]) +# for coordinate in coordinates: +# editor.placeBlock(coordinate, Block("green_concrete")) + +# --- + +intersection = (-1510, 94, 455) +xyz0 = (-1545, 90, 537) +xyz1 = (-1443, 160, 452) +circle = curved_corner_by_distance( + intersection, xyz0, xyz1, 50, 0) + +line0 = segment_tools.discrete_segment(intersection, xyz0) +line1 = segment_tools.discrete_segment(intersection, xyz1) + +for coordinate in circle[0]: + editor.placeBlock( + coordinate, Block("cyan_concrete")) + +for coordinate in line0: + editor.placeBlock( + coordinate, Block("blue_concrete")) + +for coordinate in line1: + editor.placeBlock( + coordinate, Block("red_concrete")) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 616d43c..1e3a77e 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -5,7 +5,7 @@ from networks.geometry.segment_tools import discrete_segment, middle_point, para def circle(center, radius): """ - Can be used for circle or disc. + Can be used for circle or disc. Works in 2d but supports 3d. Args: xyC (tuple): Coordinates of the center. @@ -17,9 +17,9 @@ def circle(center, radius): for a disc, positive values for a hole. """ area = ( - (round(center[0]) - round(radius), round(center[1]) - round(radius)), + (round(center[0]) - round(radius), round(center[-1]) - round(radius)), (round(center[0]) + round(radius) + 1, - round(center[1]) + round(radius) + 1), + round(center[-1]) + round(radius) + 1), ) circle = {} @@ -33,6 +33,7 @@ def circle(center, radius): def is_in_triangle(point, xy0, xy1, xy2): + # Works in 2d but supports 3d. # https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. dX = point[0] - xy0[0] dY = point[-1] - xy0[-1] @@ -52,6 +53,7 @@ def is_in_triangle(point, xy0, xy1, xy2): def distance(xy1, xy2): # TODO : Can be better. + # Works in 2d but supports 3d. return sqrt((xy2[0] - xy1[0]) ** 2 + (xy2[-1] - xy1[-1]) ** 2) @@ -80,6 +82,7 @@ def get_angle(xy0, xy1, xy2): def circle_points(center_point, radius, number=100): + # Works in 2d but supports 3d. # https://stackoverflow.com/questions/8487893/generate-all-the-points-on-the-circumference-of-a-circle points = [ (cos(2 * pi / number * x) * radius, sin(2 * pi / number * x) * radius) @@ -89,7 +92,7 @@ def circle_points(center_point, radius, number=100): for i in range(len(points)): points[i] = ( points[i][0] + center_point[0], - points[i][1] + center_point[1], + points[i][-1] + center_point[-1], ) return points @@ -325,91 +328,36 @@ def perpendicular(distance, xy1, xy2): y3 = y1 - (distance / 2) * dx x4 = x1 - (distance / 2) * dy y4 = y1 + (distance / 2) * dx - return ((round(x3), round(y3)), (round(x4), round(y4))) - - -def curved_corner( - intersection, xyz0, xyz1, distance, curvature, full_line=True, output_only_points=True -): - # If curvature radius is set, compute the center of the circle as the intersection between the two lines, offseted by the curvature radius. - if curvature != None: - center = segments_intersection(parallel( - (xyz0, intersection), curvature), parallel((xyz1, intersection), -curvature)) - - # If distance is set, compute where the arc should merge on the two intersecting lines. - elif distance != None: - start_curve_point = circle_segment_intersection( - intersection, distance, xy0[0], intersection, full_line - )[0] - start_curve_point = ( - round(start_curve_point[0]), round(start_curve_point[-1])) - - end_curve_point = circle_segment_intersection( - intersection, distance, xy1[0], intersection, full_line - )[0] - end_curve_point = ( - round(end_curve_point[0]), round(end_curve_point[-1])) - - # Then compute the center as the intersection between perpendicular segment at the points computed before. - # Higher value for better precision - perpendicular0 = perpendicular( - 10e3, start_curve_point, intersection)[0] - perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[-1] - - center = segments_intersection( - (perpendicular0, start_curve_point), (perpendicular1, end_curve_point)) - center = round(center[0]), round(center[-1]) - - curvature = round(distance(start_curve_point, center)) - - if output_only_points: - circle_data = circle_points( - center, curvature, 32 - ) - else: - circle_data = circle(center, curvature) - - # Find the correct points on the circle. - curved_corner_points_temporary = [start_curve_point] - for point in circle_data: - if is_in_triangle(point, intersection, start_curve_point, end_curve_point): - curved_corner_points_temporary.append( - (round(point[0]), round(point[1]))) - if output_only_points: - curved_corner_points_temporary.append(end_curve_point) - - # Be sure that all the points are in correct order. - curve_corner_points = optimized_path( - curved_corner_points_temporary, start_curve_point) - return curve_corner_points, center, curvature + return (x3, y3), (x4, y4) def curved_corner_by_distance( intersection, xyz0, xyz1, distance_from_intersection, resolution, full_line=True ): - # Comute the merging point on the first line + # Compute the merging point on the first line start_curve_point = circle_segment_intersection( intersection, distance_from_intersection, xyz0, intersection, full_line )[0] start_curve_point = ( - round(start_curve_point[0]), round(start_curve_point[-1])) + round(start_curve_point[0]), nearest(discrete_segment(intersection, xyz0), (start_curve_point[0], 100, start_curve_point[-1]))[1], round(start_curve_point[-1])) - # Comute the merging point on the second line + # Compute the merging point on the second line end_curve_point = circle_segment_intersection( intersection, distance_from_intersection, xyz1, intersection, full_line )[0] end_curve_point = ( - round(end_curve_point[0]), round(end_curve_point[-1])) + round(end_curve_point[0]), nearest(discrete_segment(intersection, xyz1), (end_curve_point[0], 100, end_curve_point[-1]))[1], round(end_curve_point[-1])) # Compute the intersection between perpendicular lines at the merging points # Higher value for better precision - perpendicular0 = perpendicular( - 10e3, start_curve_point, intersection)[0] - perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[-1] + perpendicular0 = perpendicular(10e3, start_curve_point, intersection)[0] + perpendicular0 = (round(perpendicular0[0]), round(perpendicular0[-1])) + perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[1] + perpendicular1 = (round(perpendicular1[0]), round(perpendicular1[-1])) center = segments_intersection( (perpendicular0, start_curve_point), (perpendicular1, end_curve_point)) - center = round(center[0]), round(center[-1]) + center = round(center[0]), middle_point(xyz0, xyz1)[1], round(center[-1]) # Compute the curvature for indications curvature = round(distance(start_curve_point, center)) @@ -420,19 +368,76 @@ def curved_corner_by_distance( center, curvature, resolution ) else: + print(center, curvature, circle(center, curvature)) circle_data = circle(center, curvature)[0] # Find the correct points on the circle. curved_corner_points_temporary = [start_curve_point] for point in circle_data: - print(point, intersection, start_curve_point, end_curve_point, is_in_triangle( - point, intersection, start_curve_point, end_curve_point)) if is_in_triangle(point, intersection, start_curve_point, end_curve_point): - curved_corner_points_temporary.append( - (round(point[0]), round(point[1]))) + curved_corner_points_temporary.append(point) curved_corner_points_temporary.append(end_curve_point) # Be sure that all the points are in correct order. curve_corner_points = optimized_path( curved_corner_points_temporary, start_curve_point) + + for i in range(len(curve_corner_points)): + y = min(start_curve_point[1], end_curve_point[1]) + \ + (i * abs(start_curve_point[1] - + end_curve_point[1])/len(curve_corner_points)) + curve_corner_points[i] = (round(curve_corner_points[i][0]), round( + y), round(curve_corner_points[i][-1])) return curve_corner_points, center, curvature + + +def curved_corner_by_curvature( + intersection, xyz0, xyz1, curvature_radius, resolution, full_line=True +): + print(xyz0, intersection, xyz1) + # Get the center. + center = segments_intersection(parallel( + (xyz0, intersection), -curvature_radius), parallel((xyz1, intersection), curvature_radius)) + center = round(center[0]), round(center[-1]) + + # Return a full discrete circle or only some points of it. + if resolution != 0: + circle_data = circle_points( + center, curvature_radius, resolution + ) + else: + circle_data = circle(center, curvature_radius)[0] + + # Compute the merging point on the first line. + print(center, curvature_radius, xyz0, intersection) + start_curve_point = circle_segment_intersection( + center, curvature_radius, xyz0, intersection, full_line + )[0] + start_curve_point = ( + round(start_curve_point[0]), round(start_curve_point[-1])) + + # Compute the merging point on the second line. + end_curve_point = circle_segment_intersection( + center, curvature_radius, xyz1, intersection, full_line + )[0] + end_curve_point = ( + round(end_curve_point[0]), round(end_curve_point[-1])) + + # Find the correct points on the circle. + curved_corner_points_temporary = [start_curve_point] + for point in circle_data: + # print(point, intersection, start_curve_point, end_curve_point, is_in_triangle( + # point, intersection, start_curve_point, end_curve_point)) + # if is_in_triangle(point, intersection, start_curve_point, end_curve_point): + curved_corner_points_temporary.append( + (round(point[0]), round(point[1]))) + curved_corner_points_temporary.append(end_curve_point) + + # Be sure that all the points are in correct order. + curve_corner_points = optimized_path( + curved_corner_points_temporary, start_curve_point) + + # Distance from intersection just for information + distance_from_intersection = round(distance(start_curve_point, center)) + return curve_corner_points, center, distance_from_intersection, parallel( + (xyz0, intersection), -curvature_radius), parallel((xyz1, intersection), curvature_radius) diff --git a/networks/roads/intersections/Intersection.py b/networks/roads/intersections/Intersection.py index 44e46c6..29b5ed9 100644 --- a/networks/roads/intersections/Intersection.py +++ b/networks/roads/intersections/Intersection.py @@ -1,5 +1,5 @@ from networks.geometry.segment_tools import parallel, orthogonal -from networks.geometry.point_tools import sort_by_clockwise, segments_intersection, curved_corner_by_distance +from networks.geometry.point_tools import sort_by_clockwise, segments_intersection, curved_corner_by_distance, curved_corner_by_curvature from networks.roads import Road @@ -37,6 +37,9 @@ class Intersection: next_parallel[0][0], next_parallel[0][-1]), (next_parallel[1][0], next_parallel[1][-1])), full_line=False) intersection = ( - round(intersection2d[0]), 100, round(intersection2d[1])) + round(intersection2d[0]), 75, round(intersection2d[1])) self.intersections_curved.append(curved_corner_by_distance( intersection, current_parallel[0], next_parallel[0], 10, 0, full_line=True)) + # print("\n\n\nBY DISTANCE\n\n\n") + # self.intersections_curved.append(curved_corner_by_distance( + # intersection, current_parallel[0], next_parallel[0], 10, 0, full_line=True)) From 45c7560352e2db321c0d7dfee27319983b65473f Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 6 Jun 2024 13:06:17 +0200 Subject: [PATCH 24/69] Redo roads --- main.py | 48 +++++++---- networks/geometry/point_tools.py | 134 +++++++++++++++++++++++-------- networks/roads/Road.py | 56 ++++++++++++- 3 files changed, 188 insertions(+), 50 deletions(-) diff --git a/main.py b/main.py index afbe8f3..0769421 100644 --- a/main.py +++ b/main.py @@ -212,23 +212,39 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # --- -intersection = (-1510, 94, 455) -xyz0 = (-1545, 90, 537) -xyz1 = (-1443, 160, 452) -circle = curved_corner_by_distance( - intersection, xyz0, xyz1, 50, 0) +# intersection = (-1510, 94, 455) +# xyz0 = (-1545, 90, 537) +# xyz1 = (-1535, 162, 459) +# circle = curved_corner_by_distance( +# intersection, xyz0, xyz1, 25, 0) -line0 = segment_tools.discrete_segment(intersection, xyz0) -line1 = segment_tools.discrete_segment(intersection, xyz1) +# line0 = segment_tools.discrete_segment(intersection, xyz0) +# line1 = segment_tools.discrete_segment(intersection, xyz1, pixel_perfect=False) -for coordinate in circle[0]: - editor.placeBlock( - coordinate, Block("cyan_concrete")) +# editor.placeBlock( +# circle[1], Block("black_concrete")) -for coordinate in line0: - editor.placeBlock( - coordinate, Block("blue_concrete")) +# editor.placeBlock( +# circle[3], Block("gray_concrete")) +# print(circle[3], "center") +# print(circle[4], "center") -for coordinate in line1: - editor.placeBlock( - coordinate, Block("red_concrete")) +# for coordinate in circle[0]: +# editor.placeBlock( +# coordinate, Block("white_concrete")) +# print(coordinate) + +# for coordinate in line0: +# editor.placeBlock( +# coordinate, Block("blue_concrete")) + +# for coordinate in line1: +# editor.placeBlock( +# coordinate, Block("red_concrete")) + +# --- + +r = Road.Road(((-1572, 64, 518), (-1608, 65, 513), + (-1627, 64, 534), (-1643, 71, 580)), "None") + +r.place_roads() diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 1e3a77e..1bfce76 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -335,65 +335,135 @@ def curved_corner_by_distance( intersection, xyz0, xyz1, distance_from_intersection, resolution, full_line=True ): # Compute the merging point on the first line - start_curve_point = circle_segment_intersection( + start_curve_point_d1 = circle_segment_intersection( intersection, distance_from_intersection, xyz0, intersection, full_line )[0] - start_curve_point = ( - round(start_curve_point[0]), nearest(discrete_segment(intersection, xyz0), (start_curve_point[0], 100, start_curve_point[-1]))[1], round(start_curve_point[-1])) + start_curve_point_d1 = ( + round(start_curve_point_d1[0]), nearest(discrete_segment(intersection, xyz0), (start_curve_point_d1[0], 100, start_curve_point_d1[-1]))[1], round(start_curve_point_d1[-1])) # Compute the merging point on the second line - end_curve_point = circle_segment_intersection( + end_curve_point_d1 = circle_segment_intersection( intersection, distance_from_intersection, xyz1, intersection, full_line )[0] - end_curve_point = ( - round(end_curve_point[0]), nearest(discrete_segment(intersection, xyz1), (end_curve_point[0], 100, end_curve_point[-1]))[1], round(end_curve_point[-1])) + end_curve_point_d1 = ( + round(end_curve_point_d1[0]), nearest(discrete_segment(intersection, xyz1), (end_curve_point_d1[0], 100, end_curve_point_d1[-1]))[1], round(end_curve_point_d1[-1])) + + # Compute the merging point on the first line + start_curve_point_d2 = circle_segment_intersection( + (intersection[0], intersection[1]), distance_from_intersection, ( + xyz0[0], xyz0[1]), (intersection[0], intersection[1]), full_line + )[0] + start_curve_point_d2 = ( + round(start_curve_point_d2[0]), round(start_curve_point_d2[1]), nearest(discrete_segment(intersection, xyz0), (start_curve_point_d1[0], start_curve_point_d2[-1], 100))[-1]) + + # Compute the merging point on the second line + end_curve_point_d2 = circle_segment_intersection( + (intersection[0], intersection[1] + ), distance_from_intersection, (xyz1[0], xyz1[1]), (intersection[0], intersection[1]), full_line + )[0] + end_curve_point_d2 = ( + round(end_curve_point_d2[0]), round(end_curve_point_d2[-1]), nearest(discrete_segment( + intersection, xyz1), (end_curve_point_d2[0], end_curve_point_d2[-1], 100))[-1]) # Compute the intersection between perpendicular lines at the merging points # Higher value for better precision - perpendicular0 = perpendicular(10e3, start_curve_point, intersection)[0] - perpendicular0 = (round(perpendicular0[0]), round(perpendicular0[-1])) - perpendicular1 = perpendicular(10e3, end_curve_point, intersection)[1] - perpendicular1 = (round(perpendicular1[0]), round(perpendicular1[-1])) + perpendicular0_d1 = perpendicular( + 10e3, start_curve_point_d1, intersection)[0] + perpendicular0_d1 = ( + round(perpendicular0_d1[0]), round(perpendicular0_d1[-1])) + perpendicular1_d1 = perpendicular( + 10e3, end_curve_point_d1, intersection)[1] + perpendicular1_d1 = ( + round(perpendicular1_d1[0]), round(perpendicular1_d1[-1])) - center = segments_intersection( - (perpendicular0, start_curve_point), (perpendicular1, end_curve_point)) - center = round(center[0]), middle_point(xyz0, xyz1)[1], round(center[-1]) + perpendicular0_d2 = perpendicular( + 10e3, (start_curve_point_d1[0], start_curve_point_d1[1]), (intersection[0], intersection[1]))[0] + perpendicular0_d2 = ( + round(perpendicular0_d2[0]), round(perpendicular0_d2[1])) + perpendicular1_d2 = perpendicular( + 10e3, (end_curve_point_d1[0], end_curve_point_d1[1]), (intersection[0], intersection[1]))[1] + perpendicular1_d2 = ( + round(perpendicular1_d2[0]), round(perpendicular1_d2[1])) + + # Centers + center_d1 = segments_intersection( + (perpendicular0_d1, start_curve_point_d1), (perpendicular1_d1, end_curve_point_d1)) + center_d1 = round(center_d1[0]), middle_point( + xyz0, xyz1)[1], round(center_d1[-1]) + + center_d2 = segments_intersection( + (perpendicular0_d2, (start_curve_point_d1[0], start_curve_point_d1[1])), (perpendicular1_d2, (end_curve_point_d1[0], end_curve_point_d1[1]))) + center_d2 = round(center_d2[0]), round(center_d2[1]), middle_point( + xyz0, xyz1)[-1] # Compute the curvature for indications - curvature = round(distance(start_curve_point, center)) + curvature_d1 = round(distance(start_curve_point_d1, center_d1)) + curvature_d2 = round( + distance((start_curve_point_d1[0], start_curve_point_d1[1]), center_d2)) # Return a full discrete circle or only some points of it if resolution != 0: - circle_data = circle_points( - center, curvature, resolution + circle_data_d1 = circle_points( + center_d1, curvature_d1, resolution + ) + circle_data_d2 = circle_points( + center_d2, curvature_d2, resolution ) else: - print(center, curvature, circle(center, curvature)) - circle_data = circle(center, curvature)[0] + circle_data_d1 = circle(center_d1, curvature_d1)[0] + circle_data_d2 = circle(center_d2, curvature_d2)[0] # Find the correct points on the circle. - curved_corner_points_temporary = [start_curve_point] - for point in circle_data: - if is_in_triangle(point, intersection, start_curve_point, end_curve_point): - curved_corner_points_temporary.append(point) - curved_corner_points_temporary.append(end_curve_point) + curved_corner_points_temporary_d1 = [start_curve_point_d1] + for point in circle_data_d1: + if is_in_triangle(point, intersection, start_curve_point_d1, end_curve_point_d1): + curved_corner_points_temporary_d1.append(point) + curved_corner_points_temporary_d1.append(end_curve_point_d1) # Be sure that all the points are in correct order. - curve_corner_points = optimized_path( - curved_corner_points_temporary, start_curve_point) + curve_corner_points_d1 = optimized_path( + curved_corner_points_temporary_d1, start_curve_point_d1) - for i in range(len(curve_corner_points)): - y = min(start_curve_point[1], end_curve_point[1]) + \ - (i * abs(start_curve_point[1] - - end_curve_point[1])/len(curve_corner_points)) - curve_corner_points[i] = (round(curve_corner_points[i][0]), round( - y), round(curve_corner_points[i][-1])) - return curve_corner_points, center, curvature + # On the other axis + curved_corner_points_temporary_d2 = [ + (start_curve_point_d1[0], start_curve_point_d1[1])] + for point in circle_data_d2: + + if is_in_triangle(point, (intersection[0], intersection[1]), (start_curve_point_d1[0], start_curve_point_d1[1]), (end_curve_point_d1[0], end_curve_point_d1[1])): + curved_corner_points_temporary_d2.append(point) + curved_corner_points_temporary_d2.append( + (end_curve_point_d1[0], end_curve_point_d1[1])) + + # Be sure that all the points are in correct order. + curve_corner_points_d2 = optimized_path( + curved_corner_points_temporary_d2, (start_curve_point_d1[0], start_curve_point_d1[1])) + + # Determine driving axis + if len(curve_corner_points_d1) <= len(curve_corner_points_d2): + main_points = curve_corner_points_d2 + projected_points = curve_corner_points_d1 + else: + main_points = curve_corner_points_d1 + projected_points = curve_corner_points_d2 + + print("Main\n") + print(main_points) + print("Projected\n") + print(projected_points) + + curve_corner_points = [] + for i in range(len(main_points)): + y = projected_points[round( + i * (len(projected_points)-1)/len(main_points))][-1] + curve_corner_points.append((round(main_points[i][0]), round( + y), round(main_points[i][-1]))) + return curve_corner_points, center_d1, curvature_d1, center_d2, curvature_d2 def curved_corner_by_curvature( intersection, xyz0, xyz1, curvature_radius, resolution, full_line=True ): + # 3d support limited to linear interpollation on the y axis. print(xyz0, intersection, xyz1) # Get the center. center = segments_intersection(parallel( diff --git a/networks/roads/Road.py b/networks/roads/Road.py index 4eeb66b..1522f6c 100644 --- a/networks/roads/Road.py +++ b/networks/roads/Road.py @@ -1,8 +1,60 @@ +import networks.geometry.curve_tools as curve_tools +import networks.geometry.Strip as Strip + +from gdpc import Editor, Block, geometry + + class Road: def __init__(self, coordinates, road_configuration): - self.coordinates = coordinates # List of tuples (x1, y1, z1) in order + self.coordinates = coordinates self.road_configuration = road_configuration # 'road', 'highway' self.width = 10 # TODO def place_roads(self): - pass + editor = Editor(buffering=True) + + self.resolution, self.distance = curve_tools.resolution_distance( + self.coordinates, 6) + + self.curve_points = curve_tools.curve( + self.coordinates, self.resolution) + self.curve_surface = Strip.Strip(self.coordinates) + self.curve_surface.compute_curvature() + + self.curvature = [] + for i in range(len(self.curve_surface.curvature)): + self.curvature.append((0, 1, 0)) + + # Perpendicular + self.curve_surface.compute_surface_perpendicular(10, self.curvature) + for i in range(len(self.curve_surface.surface)): + for j in range(len(self.curve_surface.surface[i])): + # block = random.choice(block_list) + for k in range(len(self.curve_surface.surface[i][j])): + editor.placeBlock( + self.curve_surface.surface[i][j][k], Block("blackstone")) + +# offset = curve.offset(curve_surface.curve, -9, curvature) +# for i in range(len(offset)-1): +# line = segment.discrete_segment(offset[i], offset[i+1]) +# for coordinate in line: +# editor.placeBlock(coordinate, Block("white_concrete")) + +# offset = curve.offset(curve_surface.curve, 9, curvature) +# for i in range(len(offset)-1): +# line = segment.discrete_segment(offset[i], offset[i+1]) +# for coordinate in line: +# editor.placeBlock(coordinate, Block("white_concrete")) + +# # for coordinate in curve_surface.surface: +# # editor.placeBlock(coordinate, Block("black_concrete")) + +# # for coordinate in curve_surface.curve: +# # editor.placeBlock(coordinate, Block("red_concrete")) + +# # # Parallel +# # curve_surface.compute_surface_parallel(0, 10, 8, curvature) + +# # for current_range in range(len(curve_surface.left_side)): +# # for coordinate in curve_surface.left_side[current_range]: +# # editor.placeBlock(coordinate, Block("yellow_concrete")) From 9867909e9fc185a2eafda2294bac02ae5c6ac647 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 6 Jun 2024 19:53:00 +0200 Subject: [PATCH 25/69] Redo Lane --- networks/roads/Road.py | 36 ++++++++++++++++++++++++++++++++---- networks/roads/lanes/Lane.py | 30 ++++++++++++++++++++---------- networks/roads/lines/Line.py | 35 +++++++++++++---------------------- 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/networks/roads/Road.py b/networks/roads/Road.py index 1522f6c..0e69f04 100644 --- a/networks/roads/Road.py +++ b/networks/roads/Road.py @@ -1,5 +1,9 @@ import networks.geometry.curve_tools as curve_tools import networks.geometry.Strip as Strip +import networks.roads.lanes.Lane as Lane +import networks.roads.lines.Line as Line +import json +import random from gdpc import Editor, Block, geometry @@ -25,15 +29,39 @@ class Road: for i in range(len(self.curve_surface.curvature)): self.curvature.append((0, 1, 0)) + with open('networks/roads/lanes/lanes.json') as lanes_materials: + lane_type = json.load(lanes_materials).get('classic_lane') + + # for coordinate, block in surface: + # editor.placeBlock(coordinate, Block(block)) + + with open('networks/roads/lines/lines.json') as lines_materials: + line_type = json.load(lines_materials).get('broken_white') + + print(line_type, lane_type) + + # for coordinate, block in surface: + # editor.placeBlock(coordinate, Block(block)) + # Perpendicular self.curve_surface.compute_surface_perpendicular(10, self.curvature) for i in range(len(self.curve_surface.surface)): for j in range(len(self.curve_surface.surface[i])): - # block = random.choice(block_list) for k in range(len(self.curve_surface.surface[i][j])): - editor.placeBlock( - self.curve_surface.surface[i][j][k], Block("blackstone")) - + for l in range(len(self.curve_surface.surface[i][j][k])): + editor.placeBlock( + self.curve_surface.surface[i][j][k][l], Block(random.choices( + list(lane_type.keys()), + weights=lane_type.values(), + k=1,)[0])) + if k == 0: + block = random.choices( + list(pattern_materials[pattern_iteration].keys()), + weights=pattern_materials[pattern_iteration].values( + ), + k=1)[0] + if block != 'None': + self.surface.append((coordinate, block)) # offset = curve.offset(curve_surface.curve, -9, curvature) # for i in range(len(offset)-1): # line = segment.discrete_segment(offset[i], offset[i+1]) diff --git a/networks/roads/lanes/Lane.py b/networks/roads/lanes/Lane.py index 803a0ef..a60031a 100644 --- a/networks/roads/lanes/Lane.py +++ b/networks/roads/lanes/Lane.py @@ -24,15 +24,25 @@ class Lane: for i in range(len(curve_surface.curvature)): normals.append((0, 1, 0)) - # Compute each line - for distance in range(self.width): - offset = curve_tools.offset(curve_surface.curve, distance, normals) - for i in range(len(offset)-1): - line = segment_tools.discrete_segment(offset[i], offset[i+1]) - for coordinate in line: - self.surface.append((coordinate, random.choices( - list(self.lane_materials.keys()), - weights=self.lane_materials.values(), - k=1,))) + # # Compute each line + # for distance in range(self.width): + # offset = curve_tools.offset(curve_surface.curve, distance, normals) + # for i in range(len(offset)-1): + # line = segment_tools.discrete_segment(offset[i], offset[i+1]) + # for coordinate in line: + # self.surface.append((coordinate, random.choices( + # list(self.lane_materials.keys()), + # weights=self.lane_materials.values(), + # k=1,)[0])) + + curve_surface.compute_surface_perpendicular(self.width, normals) + for i in range(len(curve_surface.surface)): + for j in range(len(curve_surface.surface[i])): + for k in range(len(curve_surface.surface[i][j])): + for l in range(len(curve_surface.surface[i][j][k])): + self.surface.append((curve_surface.surface[i][j][k][l], random.choices( + list(self.lane_materials.keys()), + weights=self.lane_materials.values(), + k=1,)[0])) return self.surface diff --git a/networks/roads/lines/Line.py b/networks/roads/lines/Line.py index fb00dfc..13d257e 100644 --- a/networks/roads/lines/Line.py +++ b/networks/roads/lines/Line.py @@ -5,40 +5,31 @@ import random class Line: def __init__(self, coordinates, line_materials): - self.coordinates = coordinates - self.line_materials = line_materials - self.surface = [] - - def get_surface(self): - resolution, distance = curve_tools.resolution_distance( - self.coordinates, 6) - - curve_points = curve_tools.curve(self.coordinates, resolution) - - # Compute the line + self.coordinates = coordinates # Full lines coordinates, not just endpoints + self.line_materials = line_materials # From lines.json + self.coordinates_with_blocks = [] # Output + def get_blocks(self): pattern_length = 0 pattern_materials = [] + # Create the pattern materials list with correct materials depending on the selected pattern. for pattern in self.line_materials: pattern_length += pattern[1] for _ in range(pattern[1]): pattern_materials.append(pattern[0]) pattern_iteration = 0 - for i in range(len(curve_points)-1): - line = segment_tools.discrete_segment( - curve_points[i], curve_points[i+1]) - for coordinate in line: - block = random.choices( - list(pattern_materials[pattern_iteration].keys()), - weights=pattern_materials[pattern_iteration].values(), - k=1)[0] - if block != 'None': - self.surface.append((coordinate, block)) + for coordinate in self.coordinates: + block = random.choices( + list(pattern_materials[pattern_iteration].keys()), + weights=pattern_materials[pattern_iteration].values(), + k=1)[0] + if block != 'None': + self.coordinates_with_blocks.append((coordinate, block)) pattern_iteration += 1 if pattern_iteration >= pattern_length: pattern_iteration = 0 - return self.surface + return self.coordinates_with_blocks From e8d3b8d29a6e3cdd901b8035942acffc2e2b80be Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 6 Jun 2024 21:55:41 +0200 Subject: [PATCH 26/69] Break random things --- main.py | 4 ++-- networks/geometry/Strip.py | 4 ++-- networks/roads/Road.py | 40 ++++++++++++++++++++++++--------- networks/roads/lines/Line.py | 6 ++--- networks/roads/lines/lines.json | 5 +++-- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/main.py b/main.py index 0769421..6efc190 100644 --- a/main.py +++ b/main.py @@ -244,7 +244,7 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # --- -r = Road.Road(((-1572, 64, 518), (-1608, 65, 513), - (-1627, 64, 534), (-1643, 71, 580)), "None") +r = Road.Road(((-1829, 141, 553), (-1830, 110, 621), (-1711, 69, 625), (-1662, + 65, 627), (-1667, 65, 761), (-1683, 70, 800), (-1721, 70, 834)), "None") r.place_roads() diff --git a/networks/geometry/Strip.py b/networks/geometry/Strip.py index 5da1474..f1fed13 100644 --- a/networks/geometry/Strip.py +++ b/networks/geometry/Strip.py @@ -17,8 +17,8 @@ class Strip: self.curvature = curve_tools.curvature(self.curve) def compute_surface_perpendicular(self, width, normals): - self.offset_left = curve_tools.offset(self.curve, width, normals) - self.offset_right = curve_tools.offset(self.curve, -width, normals) + self.offset_left = curve_tools.offset(self.curve, width/2, normals) + self.offset_right = curve_tools.offset(self.curve, -width/2, normals) self.perpendicular_segment = [] for i in range(len(self.offset_left)): diff --git a/networks/roads/Road.py b/networks/roads/Road.py index 0e69f04..262f187 100644 --- a/networks/roads/Road.py +++ b/networks/roads/Road.py @@ -18,7 +18,7 @@ class Road: editor = Editor(buffering=True) self.resolution, self.distance = curve_tools.resolution_distance( - self.coordinates, 6) + self.coordinates, 12) self.curve_points = curve_tools.curve( self.coordinates, self.resolution) @@ -36,13 +36,18 @@ class Road: # editor.placeBlock(coordinate, Block(block)) with open('networks/roads/lines/lines.json') as lines_materials: - line_type = json.load(lines_materials).get('broken_white') + line_type = json.load(lines_materials).get('solid_white') + with open('networks/roads/lines/lines.json') as lines_materials: + middle_line_type = json.load(lines_materials).get('broken_white') print(line_type, lane_type) # for coordinate, block in surface: # editor.placeBlock(coordinate, Block(block)) + lines_coordinates = [] + middle_lines_coordinates = [] + # Perpendicular self.curve_surface.compute_surface_perpendicular(10, self.curvature) for i in range(len(self.curve_surface.surface)): @@ -54,14 +59,29 @@ class Road: list(lane_type.keys()), weights=lane_type.values(), k=1,)[0])) - if k == 0: - block = random.choices( - list(pattern_materials[pattern_iteration].keys()), - weights=pattern_materials[pattern_iteration].values( - ), - k=1)[0] - if block != 'None': - self.surface.append((coordinate, block)) + editor.placeBlock( + (self.curve_surface.surface[i][j][k][l][0], self.curve_surface.surface[i][j][k][l][1]-1, self.curve_surface.surface[i][j][k][l][2]), Block(random.choices( + list(lane_type.keys()), + weights=lane_type.values(), + k=1,)[0])) + if k == 0 or k == len(self.curve_surface.surface[i][j])-1: + lines_coordinates.extend( + self.curve_surface.surface[i][j][k]) + if k == round((len(self.curve_surface.surface[i][j])-1)/2): + middle_lines_coordinates.extend( + self.curve_surface.surface[i][j][k]) + + line = Line.Line(lines_coordinates, line_type) + line.get_blocks() + middle_line = Line.Line(middle_lines_coordinates, middle_line_type) + middle_line.get_blocks() + for i in range(len(line.coordinates_with_blocks)): + editor.placeBlock( + line.coordinates_with_blocks[i][0], Block(line.coordinates_with_blocks[i][1])) + for i in range(len(middle_line.coordinates_with_blocks)): + editor.placeBlock( + middle_line.coordinates_with_blocks[i][0], Block(middle_line.coordinates_with_blocks[i][1])) + # offset = curve.offset(curve_surface.curve, -9, curvature) # for i in range(len(offset)-1): # line = segment.discrete_segment(offset[i], offset[i+1]) diff --git a/networks/roads/lines/Line.py b/networks/roads/lines/Line.py index 13d257e..ccc9b85 100644 --- a/networks/roads/lines/Line.py +++ b/networks/roads/lines/Line.py @@ -28,8 +28,8 @@ class Line: if block != 'None': self.coordinates_with_blocks.append((coordinate, block)) - pattern_iteration += 1 - if pattern_iteration >= pattern_length: - pattern_iteration = 0 + pattern_iteration += 1 + if pattern_iteration >= pattern_length: + pattern_iteration = 0 return self.coordinates_with_blocks diff --git a/networks/roads/lines/lines.json b/networks/roads/lines/lines.json index 170d590..e57e593 100644 --- a/networks/roads/lines/lines.json +++ b/networks/roads/lines/lines.json @@ -4,7 +4,8 @@ ], "broken_white": [ - [{"white_concrete": 3, "white_concrete_powder": 1}, 3], - [{"None": 1}, 1] + [{"red_concrete": 1}, 3], + [{"green_concrete": 1}, 3], + [{"blue_concrete": 1}, 3] ] } \ No newline at end of file From b82178479618d544cc768031110f8054a2c5ce80 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Fri, 7 Jun 2024 21:18:50 +0200 Subject: [PATCH 27/69] New tests from scratch in quest of pixel perfection --- networks/test.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 networks/test.py diff --git a/networks/test.py b/networks/test.py new file mode 100644 index 0000000..a77782f --- /dev/null +++ b/networks/test.py @@ -0,0 +1,100 @@ +from gdpc import Editor, Block, geometry + + +def cirlce(xm, ym, r): + editor = Editor(buffering=True) + x = -r + y = 0 + err = 2-2*r + while (True): + editor.placeBlock((round(xm-x), 102, round(ym+y)), + Block("white_concrete")) + editor.placeBlock((round(xm-y), 102, round(ym-x)), + Block("red_concrete")) + editor.placeBlock((round(xm+x), 102, round(ym-y)), + Block("blue_concrete")) + editor.placeBlock((round(xm+y), 102, round(ym+x)), + Block("green_concrete")) + print(xm-x, ym+y) + print(xm-y, ym-x) + print(xm+x, ym-y) + print(xm+y, ym+x) + r = err + if (r <= y): + y += 1 + err += y*2+1 + if (r > x or err > y): + x += 1 + err += x*2+1 + if (x < 0): + continue + else: + break + + +print("\n") +cirlce(-1606, 758, 20) + + +class Point: + def __init__(self, x, y, z): + self.x = x + self.y = z + self.z = y + + def __repr__(self): + return f"({self.x} {self.y} {self.z})" + + +def drawLineOverlap(start, end, overlap): + # Direction + delta_x = end.x - start.x + delta_y = end.y - start.y + + if (delta_x < 0): + delta_x = -delta_x + step_x = -1 + else: + step_x = +1 + + if (delta_y < 0): + delta_y = -delta_y + step_y = -1 + else: + step_y = +1 + + delta_2x = 2*delta_x + delta_2y = 2*delta_y + + print(start.x, start.y) + + if (delta_x > delta_y): + error = delta_2y - delta_x + while (start.x != end.x): + start.x += step_x + if (error >= 0): + if (overlap == 'LINE_OVERLAP_MAJOR'): + print(start.x, start.y) + + start.y += step_y + if (overlap == 'LINE_OVERLAP_MINOR'): + print(start.x - step_x, start.y) + error -= delta_2x + error += delta_2y + print(start.x, start.y) + else: + error = delta_2x - delta_y + while (start.y != end.y): + start.y += step_y + if (error >= 0): + if (overlap == 'LINE_OVERLAP_MAJOR'): + print(start) + start.x += step_x + if (overlap == 'LINE_OVERLAP_MINOR'): + print(start.x, start.y - step.y) + error -= delta_2y + error += delta_2x + print(start.x, start.y) + + +drawLineOverlap(Point(-10, 0, 0,), Point(10, 0, 3), "None") From e66372e668d66d8b0de63b9d20162e8f609da574 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Mon, 10 Jun 2024 01:21:48 +0200 Subject: [PATCH 28/69] Achieve pixel perfection on lines and circles with thickness --- networks/test.py | 272 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 247 insertions(+), 25 deletions(-) diff --git a/networks/test.py b/networks/test.py index a77782f..1caf8fc 100644 --- a/networks/test.py +++ b/networks/test.py @@ -1,52 +1,145 @@ from gdpc import Editor, Block, geometry +from enum import Enum +import random -def cirlce(xm, ym, r): +def circle(xm, ym, r, pixel_perfect=True): editor = Editor(buffering=True) + block = random.choices(("white_concrete", "red_concrete", "blue_concrete", "green_concrete", + "yellow_concrete", "black_concrete", "purple_concrete", "pink_concrete"))[0] x = -r y = 0 err = 2-2*r while (True): - editor.placeBlock((round(xm-x), 102, round(ym+y)), - Block("white_concrete")) - editor.placeBlock((round(xm-y), 102, round(ym-x)), - Block("red_concrete")) - editor.placeBlock((round(xm+x), 102, round(ym-y)), - Block("blue_concrete")) - editor.placeBlock((round(xm+y), 102, round(ym+x)), - Block("green_concrete")) + editor.placeBlock((xm-x, 141, ym+y), + Block(block)) + editor.placeBlock((xm-y, 141, ym-x), + Block(block)) + editor.placeBlock((xm+x, 141, ym-y), + Block(block)) + editor.placeBlock((xm+y, 141, ym+x), + Block(block)) print(xm-x, ym+y) print(xm-y, ym-x) print(xm+x, ym-y) print(xm+y, ym+x) r = err + update = False if (r <= y): y += 1 + update = True err += y*2+1 - if (r > x or err > y): - x += 1 - err += x*2+1 + if ((r > x or err > y)): + if (pixel_perfect == True or update == False): + x += 1 + err += x*2+1 + update = True if (x < 0): continue else: break -print("\n") -cirlce(-1606, 758, 20) +def set_pixel(x, y, colour): + editor = Editor(buffering=True) + editor.placeBlock((x, 160, y), + Block(colour)) -class Point: - def __init__(self, x, y, z): +def x_line(x1, x2, y, colour): + while x1 <= x2: + set_pixel(x1, y, colour) + x1 += 1 + + +def y_line(x, y1, y2, colour): + while y1 <= y2: + set_pixel(x, y1, colour) + y1 += 1 + + +def circle2(xc, yc, inner, outer): + # https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm + xo = outer + xi = inner + y = 0 + erro = 1 - xo + erri = 1 - xi + + while xo >= y: + colour = random.choices(("white_concrete", "red_concrete", "blue_concrete", "green_concrete", + "yellow_concrete", "black_concrete", "purple_concrete", "pink_concrete"))[0] + x_line(xc + xi, xc + xo, yc + y, colour) + y_line(xc + y, yc + xi, yc + xo, colour) + x_line(xc - xo, xc - xi, yc + y, colour) + y_line(xc - y, yc + xi, yc + xo, colour) + x_line(xc - xo, xc - xi, yc - y, colour) + y_line(xc - y, yc - xo, yc - xi, colour) + x_line(xc + xi, xc + xo, yc - y, colour) + y_line(xc + y, yc - xo, yc - xi, colour) + + y += 1 + + if erro < 0: + erro += 2 * y + 1 + else: + xo -= 1 + erro += 2 * (y - xo + 1) + + if y > inner: + xi = y + else: + if erri < 0: + erri += 2 * y + 1 + else: + xi -= 1 + erri += 2 * (y - xi + 1) + + +# print("\n") +# circle2(-1606, 758, 5, 15) +# circle2(-1606, 758, 5, 5) +# circle2(-1606, 758, 10, 10) +circle2(-1606, 758, 15, 17) + + +class LineOverlap(Enum): + NONE = 0 + MAJOR = 1 + MINOR = 2 + + +class LineThicknessMode(Enum): + MIDDLE = 0 + DRAW_COUNTERCLOCKWISE = 1 + DRAW_CLOCKWISE = 2 + + +class Point2D: + def __init__(self, x, y): self.x = x - self.y = z - self.z = y + self.y = y def __repr__(self): - return f"({self.x} {self.y} {self.z})" + return f"({self.x} {self.y})" + + def copy(self): + return Point2D(self.x, self.y) + + def get_coordinates(self): + return (self.x, self.y) def drawLineOverlap(start, end, overlap): + y = 120 + block = random.choices(("white_concrete", "red_concrete", "blue_concrete", "green_concrete", + "yellow_concrete", "black_concrete", "purple_concrete", "pink_concrete"))[0] + print(block) + editor = Editor(buffering=True) + + start = start.copy() + end = end.copy() + # Direction delta_x = end.x - start.x delta_y = end.y - start.y @@ -67,34 +160,163 @@ def drawLineOverlap(start, end, overlap): delta_2y = 2*delta_y print(start.x, start.y) + editor.placeBlock((start.x, y, start.y), Block(block)) if (delta_x > delta_y): error = delta_2y - delta_x while (start.x != end.x): start.x += step_x if (error >= 0): - if (overlap == 'LINE_OVERLAP_MAJOR'): + if (overlap == LineOverlap.MAJOR): print(start.x, start.y) + editor.placeBlock((start.x, y, start.y), + Block(block)) start.y += step_y - if (overlap == 'LINE_OVERLAP_MINOR'): + if (overlap == LineOverlap.MINOR): print(start.x - step_x, start.y) + editor.placeBlock((start.x - step_x, y, start.y), + Block(block)) error -= delta_2x error += delta_2y print(start.x, start.y) + editor.placeBlock((start.x, y, start.y), + Block(block)) else: error = delta_2x - delta_y while (start.y != end.y): start.y += step_y if (error >= 0): - if (overlap == 'LINE_OVERLAP_MAJOR'): + if (overlap == LineOverlap.MAJOR): print(start) + editor.placeBlock((start.x, y, start.y), + Block(block)) start.x += step_x - if (overlap == 'LINE_OVERLAP_MINOR'): - print(start.x, start.y - step.y) + if (overlap == LineOverlap.MINOR): + print(start.x, start.y - step.y, start.z, ) + editor.placeBlock((start.x, y, start.y - step.y), + Block(block)) error -= delta_2y error += delta_2x print(start.x, start.y) + editor.placeBlock((start.x, y, start.y), + Block("white_concrete")) -drawLineOverlap(Point(-10, 0, 0,), Point(10, 0, 3), "None") +# drawLineOverlap(Point2D(-10, 0, 0,), Point2D(10, 0, 3), +# LineOverlap.NONE) + + +def drawThickLine(start, end, thickness, thickness_mode): + delta_y = end.x - start.x + delta_x = end.y - start.y + + print("START", start) + + swap = True + if delta_x < 0: + delta_x = -delta_x + step_x = -1 + swap = not swap + else: + step_x = +1 + + if (delta_y < 0): + delta_y = -delta_y + step_y = -1 + swap = not swap + else: + step_y = +1 + + delta_2x = 2 * delta_x + delta_2y = 2 * delta_y + + draw_start_adjust_count = int(thickness / 2) + if (thickness_mode == LineThicknessMode.DRAW_COUNTERCLOCKWISE): + draw_start_adjust_count = thickness - 1 + elif (thickness_mode == LineThicknessMode.DRAW_CLOCKWISE): + draw_start_adjust_count = 0 + print("START", start) + if (delta_x >= delta_y): + if swap: + draw_start_adjust_count = (thickness - 1) - draw_start_adjust_count + step_y = -step_y + else: + step_x = -step_x + + error = delta_2y - delta_x + for i in range(draw_start_adjust_count, 0, -1): + print("START", start) + start.x -= step_x + end.x -= step_x + if error >= 0: + start.y -= step_y + end.y -= step_y + error -= delta_2x + error += delta_2x + print("START", start) + print("First print") + print(start, end) + drawLineOverlap(start, end, LineOverlap.NONE) + print(start, end) + print("End print") + + error = delta_2x - delta_x + for i in range(thickness, 1, -1): + start.x += step_x + end.x += step_x + overlap = LineOverlap.NONE + if (error >= 0): + start.y += step_y + end.y += step_y + error -= delta_2x + overlap = LineOverlap.MAJOR + error += delta_2y + print("Second print") + print(start, end) + drawLineOverlap(start, end, overlap) + print(start, end) + print("End print") + else: + if swap: + step_x = -step_x + else: + draw_start_adjust_count = (thickness - 1) - draw_start_adjust_count + step_y = -step_y + + error = delta_2x - delta_y + for i in range(draw_start_adjust_count, 0, -1): + start.y -= step_y + end.y -= step_y + if (error >= 0): + start.x -= step_x + end.x -= step_x + error -= delta_2y + error += delta_2x + + print("Third line") + print(start, end) + drawLineOverlap(start, end, LineOverlap.NONE) + print(start, end) + print("End line") + error = delta_2x - delta_y + for i in range(thickness, 1, -1): + start.y += step_y + end.y += step_y + overlap = LineOverlap.NONE + if (error >= 0): + start.x += step_x + end.x += step_x + error -= delta_2y + overlap = LineOverlap.MAJOR + error += delta_2x + print("Fourth line") + print(start, end) + drawLineOverlap(start, end, overlap) + print(start, end) + print("End") + + +print("SPACE\n\n") +drawThickLine(Point2D(-1681, 864), Point2D(-1804, 920), + 21, LineThicknessMode.MIDDLE) From 2eb908830b560e6789d362a5f196baf69042f26c Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Mon, 10 Jun 2024 18:02:51 +0200 Subject: [PATCH 29/69] Add radius balance algorithm --- networks/polylines.py | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 networks/polylines.py diff --git a/networks/polylines.py b/networks/polylines.py new file mode 100644 index 0000000..a76522d --- /dev/null +++ b/networks/polylines.py @@ -0,0 +1,64 @@ +from math import sqrt +import numpy as np + + +class Point2D: + def __init__(self, x, y): + self.x = x + self.y = y + + def __repr__(self): + return f"({self.x} {self.y})" + + def copy(self): + return Point2D(self.x, self.y) + + def get_coordinates(self): + return (self.x, self.y) + + +def radius_balance(polylines, i): + """ + Returns the radius that balances the radii on either end segement i. + """ + + vectors = [None] * 3 + lengths = [None] * 3 + unit_vectors = [None] * 3 + tangente = [None] * 3 + + for j in range(3): + vectors[j] = polylines[i+j] - polylines[i+j-1] + lengths[j] = np.linalg.norm(vectors[j]) + unit_vectors[j] = vectors[j]/lengths[j] + + print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") + + for k in range(2): + cross = np.dot(unit_vectors[k+1], unit_vectors[k]) + print(cross) + tangente[k] = sqrt((1+cross)/(1-cross)) + print("\n", tangente[k]) + + alpha_a = min(lengths[0], (lengths[1]*tangente[1]) / + (tangente[0] + tangente[1])) + alpha_b = min(lengths[2], lengths[1]-alpha_a) + + return alpha_a, alpha_b, max(tangente[0]*alpha_a, tangente[1]*alpha_b) + + +def coordinates_to_vectors(coordinates): + vectors = [] + for coordinate in coordinates: + vectors.append(np.array(coordinate.get_coordinates())) + + if (len(vectors) == 1): + return vectors[0] + else: + return vectors + + +polyline = coordinates_to_vectors( + (Point2D(0, 0), Point2D(0, 10), Point2D(10, 10), Point2D(10, 20))) + +print(radius_balance(polyline, 1)) From 7cca576e781bf1571b1591aaa60720c4541ef6dc Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Mon, 10 Jun 2024 18:54:28 +0200 Subject: [PATCH 30/69] Merge into class --- networks/polylines.py | 117 ++++++++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 34 deletions(-) diff --git a/networks/polylines.py b/networks/polylines.py index a76522d..a55be54 100644 --- a/networks/polylines.py +++ b/networks/polylines.py @@ -1,4 +1,4 @@ -from math import sqrt +from math import sqrt, inf import numpy as np @@ -17,36 +17,6 @@ class Point2D: return (self.x, self.y) -def radius_balance(polylines, i): - """ - Returns the radius that balances the radii on either end segement i. - """ - - vectors = [None] * 3 - lengths = [None] * 3 - unit_vectors = [None] * 3 - tangente = [None] * 3 - - for j in range(3): - vectors[j] = polylines[i+j] - polylines[i+j-1] - lengths[j] = np.linalg.norm(vectors[j]) - unit_vectors[j] = vectors[j]/lengths[j] - - print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") - - for k in range(2): - cross = np.dot(unit_vectors[k+1], unit_vectors[k]) - print(cross) - tangente[k] = sqrt((1+cross)/(1-cross)) - print("\n", tangente[k]) - - alpha_a = min(lengths[0], (lengths[1]*tangente[1]) / - (tangente[0] + tangente[1])) - alpha_b = min(lengths[2], lengths[1]-alpha_a) - - return alpha_a, alpha_b, max(tangente[0]*alpha_a, tangente[1]*alpha_b) - - def coordinates_to_vectors(coordinates): vectors = [] for coordinate in coordinates: @@ -58,7 +28,86 @@ def coordinates_to_vectors(coordinates): return vectors -polyline = coordinates_to_vectors( - (Point2D(0, 0), Point2D(0, 10), Point2D(10, 10), Point2D(10, 20))) +class Polyline: + def __init__(self, points): + self.points = coordinates_to_vectors(points) + self.length_polyline = len(points) -print(radius_balance(polyline, 1)) + self.vectors = [None] * self.length_polyline + self.lengths = [None] * self.length_polyline + self.unit_vectors = [None] * self.length_polyline + self.tangente = [None] * self.length_polyline + + self.compute_requirements() + + def compute_requirements(self): + + # Between two points, there is only one segment + for j in range(self.length_polyline-1): + self.vectors[j] = self.points[j+1] - self.points[j] + self.lengths[j] = np.linalg.norm(self.vectors[j]) + self.unit_vectors[j] = self.vectors[j]/self.lengths[j] + + # print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") + + # Between two segments, there is only one angle + for k in range(self.length_polyline-2): + cross = np.dot(self.unit_vectors[k+1], self.unit_vectors[k]) + self.tangente[k] = sqrt((1+cross)/(1-cross)) + + def radius_balance(self, i): + """ + Returns the radius that balances the radii on either end segement i. + """ + + alpha_a = min(self.lengths[i], (self.lengths[i+1]*self.tangente[i+1]) / + (self.tangente[i] + self.tangente[i+1])) + alpha_b = min(self.lengths[i+2], self.lengths[i+1]-alpha_a) + + return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) + + def alpha_assign(polyline, alpha_radii, start_index, end_index): + """ + The Alpha-assign procedure assigning radii based on a polyline. + """ + minimum_radius, minimum_index = inf, end_index + + if start_index + 1 >= end_index: + return + + alpha_b = min(lenghts[start_index] - + alpha_radii[start_index], lenghts[start_index + 1]) + current_radius = max(tangente[start_index] * alpha_radii[start_index], + tangente[start_index + 1] * alpha_b) # Radis at initial segment + + if current_radius < minimum_radius: + minimum_radius, minimum_index = current_radius, start_index + alpha_low, alpha_high = alpha_radii[start_index], alpha_b + + for i in range(start_index + 1, end_index - 2): # Radii for internal segments + alpha_a, alpha_b, current_radius = radius_balance(polyline, i) + if current_radius < minimum_radius: + alpha_low, alpha_high = alpha_a, alpha_radii[end_index] + + # Assign alphas at ends of selected segment + alpha_radii[minimum_index] = alpha_low + alpha_radii[minimum_index+1] = alpha_high + # Recur on lower segments + alpha_assign(alpha_radii, start_index, minimum_index) + alpha_assign(alpha_radii, minimum_index + 1, + end_index) # Recur on higher segments + + def compute_alpha_radii(polyline): + length_array = len(polyline) + apha_radii = [None] * length_array + + alpha_radii[0] = 0 + alpha_radii[length_array-1] = 0 + + for i in range(1, length_array-2): + alpha_radii[i] = min() + + +polyline = Polyline((Point2D(0, 0), Point2D( + 0, 10), Point2D(10, 10), Point2D(10, 20))) +print(polyline.radius_balance(0)) From b9ad9161171f20e8ca4e976de9cbbebfdeee70aa Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Mon, 10 Jun 2024 19:01:10 +0200 Subject: [PATCH 31/69] Fix indexes offset --- networks/polylines.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/networks/polylines.py b/networks/polylines.py index a55be54..c3a404c 100644 --- a/networks/polylines.py +++ b/networks/polylines.py @@ -33,10 +33,10 @@ class Polyline: self.points = coordinates_to_vectors(points) self.length_polyline = len(points) - self.vectors = [None] * self.length_polyline - self.lengths = [None] * self.length_polyline - self.unit_vectors = [None] * self.length_polyline - self.tangente = [None] * self.length_polyline + self.vectors = [0] * self.length_polyline + self.lengths = [0] * self.length_polyline + self.unit_vectors = [0] * self.length_polyline + self.tangente = [0] * self.length_polyline self.compute_requirements() @@ -51,8 +51,8 @@ class Polyline: # print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") # Between two segments, there is only one angle - for k in range(self.length_polyline-2): - cross = np.dot(self.unit_vectors[k+1], self.unit_vectors[k]) + for k in range(1, self.length_polyline-1): + cross = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) self.tangente[k] = sqrt((1+cross)/(1-cross)) def radius_balance(self, i): @@ -60,9 +60,9 @@ class Polyline: Returns the radius that balances the radii on either end segement i. """ - alpha_a = min(self.lengths[i], (self.lengths[i+1]*self.tangente[i+1]) / + alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / (self.tangente[i] + self.tangente[i+1])) - alpha_b = min(self.lengths[i+2], self.lengths[i+1]-alpha_a) + alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) @@ -110,4 +110,4 @@ class Polyline: polyline = Polyline((Point2D(0, 0), Point2D( 0, 10), Point2D(10, 10), Point2D(10, 20))) -print(polyline.radius_balance(0)) +print(polyline.radius_balance(1)) From 23fa58729264e1d06c9712b52957e7ead0727c4d Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Mon, 10 Jun 2024 19:58:32 +0200 Subject: [PATCH 32/69] Working alpha_assign --- networks/polylines.py | 67 ++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/networks/polylines.py b/networks/polylines.py index c3a404c..01268a3 100644 --- a/networks/polylines.py +++ b/networks/polylines.py @@ -33,12 +33,15 @@ class Polyline: self.points = coordinates_to_vectors(points) self.length_polyline = len(points) - self.vectors = [0] * self.length_polyline - self.lengths = [0] * self.length_polyline - self.unit_vectors = [0] * self.length_polyline - self.tangente = [0] * self.length_polyline + self.vectors = [None] * self.length_polyline + self.lengths = [None] * self.length_polyline + self.unit_vectors = [None] * self.length_polyline + self.tangente = [None] * self.length_polyline + + self.alpha_radii = [None] * self.length_polyline self.compute_requirements() + self.compute_alpha_radii() def compute_requirements(self): @@ -55,6 +58,14 @@ class Polyline: cross = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) self.tangente[k] = sqrt((1+cross)/(1-cross)) + def compute_alpha_radii(self): + self.alpha_radii[0] = 0 + self.alpha_radii[self.length_polyline-1] = 0 + + for i in range(1, self.length_polyline-2): + self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] + * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) + def radius_balance(self, i): """ Returns the radius that balances the radii on either end segement i. @@ -66,7 +77,7 @@ class Polyline: return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) - def alpha_assign(polyline, alpha_radii, start_index, end_index): + def alpha_assign(self, start_index, end_index): """ The Alpha-assign procedure assigning radii based on a polyline. """ @@ -75,39 +86,35 @@ class Polyline: if start_index + 1 >= end_index: return - alpha_b = min(lenghts[start_index] - - alpha_radii[start_index], lenghts[start_index + 1]) - current_radius = max(tangente[start_index] * alpha_radii[start_index], - tangente[start_index + 1] * alpha_b) # Radis at initial segment + alpha_b = min( + self.lengths[start_index] - self.alpha_radii[start_index], self.lengths[start_index + 1]) + current_radius = max(self.tangente[start_index] * self.alpha_radii[start_index], + self.tangente[start_index + 1] * alpha_b) # Radis at initial segment if current_radius < minimum_radius: minimum_radius, minimum_index = current_radius, start_index - alpha_low, alpha_high = alpha_radii[start_index], alpha_b + alpha_low, alpha_high = self.alpha_radii[start_index], alpha_b for i in range(start_index + 1, end_index - 2): # Radii for internal segments - alpha_a, alpha_b, current_radius = radius_balance(polyline, i) + alpha_a, alpha_b, current_radius = self.radius_balance(i) if current_radius < minimum_radius: - alpha_low, alpha_high = alpha_a, alpha_radii[end_index] + alpha_low, alpha_high = alpha_a, self.alpha_radii[end_index] # Assign alphas at ends of selected segment - alpha_radii[minimum_index] = alpha_low - alpha_radii[minimum_index+1] = alpha_high + self.alpha_radii[minimum_index] = alpha_low + self.alpha_radii[minimum_index+1] = alpha_high + print(alpha_low, alpha_high) + # Recur on lower segments - alpha_assign(alpha_radii, start_index, minimum_index) - alpha_assign(alpha_radii, minimum_index + 1, - end_index) # Recur on higher segments - - def compute_alpha_radii(polyline): - length_array = len(polyline) - apha_radii = [None] * length_array - - alpha_radii[0] = 0 - alpha_radii[length_array-1] = 0 - - for i in range(1, length_array-2): - alpha_radii[i] = min() + self.alpha_assign(start_index, minimum_index) + # Recur on higher segments + self.alpha_assign(minimum_index + 1, end_index) -polyline = Polyline((Point2D(0, 0), Point2D( - 0, 10), Point2D(10, 10), Point2D(10, 20))) -print(polyline.radius_balance(1)) +polyline = Polyline((Point2D(0, 0), Point2D(0, 10), Point2D( + 10, 10), Point2D(10, 20), Point2D(20, 20), Point2D(20, 30), Point2D(60, 60), Point2D(60, 0))) + +# print(polyline.radius_balance(2)) + +polyline.alpha_assign(1, polyline.length_polyline-1) +print(polyline.alpha_radii) From 0c1841417689c302c623d75cd6a49b1f526bc010 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 01:29:07 +0200 Subject: [PATCH 33/69] Add and clean proper objects for Polyline, Point2D, Circle --- networks/geometry/Circle.py | 68 +++++++++ networks/geometry/Point2D.py | 16 +++ .../{polylines.py => geometry/Polyline.py} | 135 +++++++++--------- networks/geometry/point_tools.py | 11 ++ 4 files changed, 160 insertions(+), 70 deletions(-) create mode 100644 networks/geometry/Circle.py create mode 100644 networks/geometry/Point2D.py rename networks/{polylines.py => geometry/Polyline.py} (66%) diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py new file mode 100644 index 0000000..50c153e --- /dev/null +++ b/networks/geometry/Circle.py @@ -0,0 +1,68 @@ +from typing import Type +import Point2D + + +class Circle: + def __init__(center: Type[Point2D], inner: int, outer: int): + self.center = center + self.inner = inner + self.outer = outer + self.coordinates = [] + + circle(self.center, self.inner, self.outer) + + def circle(center: Type[Point2D], inner: int, outer: int): + """Compute discrete value of a 2d-circle with thickness. + + 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). + """ + xo = outer + xi = inner + y = 0 + erro = 1 - xo + erri = 1 - xi + + while xo >= y: + _x_line(center.x + xi, center.x + xo, center.y + y) + _y_line(center.x + y, center.y + xi, center.y + xo) + _x_line(center.x - xo, center.x - xi, center.y + y) + _y_line(center.x - y, center.y + xi, center.y + xo) + _x_line(center.x - xo, center.x - xi, center.y - y) + _y_line(center.x - y, center.y - xo, center.y - xi) + _x_line(center.x + xi, center.x + xo, center.y - y) + _y_line(center.x + y, center.y - xo, center.y - xi) + + y += 1 + + if erro < 0: + erro += 2 * y + 1 + else: + xo -= 1 + erro += 2 * (y - xo + 1) + + if y > inner: + xi = y + else: + if erri < 0: + erri += 2 * y + 1 + else: + xi -= 1 + erri += 2 * (y - xi + 1) + + def _x_line(x1, x2, y): + while x1 <= x2: + self.coordinates.append(Point2D(x1, y)) + x1 += 1 + + def _y_line(x, y1, y2): + while y1 <= y2: + self.coordinate.append(Point2D(x, y1)) + y1 += 1 + + def __repr__(self): + return f"Circle(center: {self.center}, inner: {self.inner}, outer: {self.outer})" diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py new file mode 100644 index 0000000..435172a --- /dev/null +++ b/networks/geometry/Point2D.py @@ -0,0 +1,16 @@ +from typing import Type + + +class Point2D: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + def copy(self): + return Point2D(self.x, self.y) + + def coordinates(self): + return (self.x, self.y) + + def __repr__(self): + return f"Point2D(x: {self.x}, y: {self.y})" diff --git a/networks/polylines.py b/networks/geometry/Polyline.py similarity index 66% rename from networks/polylines.py rename to networks/geometry/Polyline.py index 01268a3..ace65e5 100644 --- a/networks/polylines.py +++ b/networks/geometry/Polyline.py @@ -1,38 +1,28 @@ +from typing import Type +import Point2D + from math import sqrt, inf import numpy as np -class Point2D: - def __init__(self, x, y): - self.x = x - self.y = y - - def __repr__(self): - return f"({self.x} {self.y})" - - def copy(self): - return Point2D(self.x, self.y) - - def get_coordinates(self): - return (self.x, self.y) - - -def coordinates_to_vectors(coordinates): - vectors = [] - for coordinate in coordinates: - vectors.append(np.array(coordinate.get_coordinates())) - - if (len(vectors) == 1): - return vectors[0] - else: - return vectors - - class Polyline: - def __init__(self, points): + def __init__(self, points: List[Point2D]): + """A polyline with smooth corners, only composed of segments and circle arc. + + Mathematics and algorithms behind this can be found here: https://cdr.lib.unc.edu/concern/dissertations/pz50gw814?locale=en, E2 Construction of arc roads from polylines, page 210. + + Args: + points (List[Point2D]): List of 2d-points in order describing the polyline. + + Raises: + ValueError: At least 4 points required. + """ self.points = coordinates_to_vectors(points) self.length_polyline = len(points) + if self.length_polyline < 4: + raise ValueError("The list must contain at least 4 elements.") + self.vectors = [None] * self.length_polyline self.lengths = [None] * self.length_polyline self.unit_vectors = [None] * self.length_polyline @@ -40,46 +30,14 @@ class Polyline: self.alpha_radii = [None] * self.length_polyline - self.compute_requirements() - self.compute_alpha_radii() + self._compute_requirements() + self._compute_alpha_radii() - def compute_requirements(self): + _alpha_assign(0, self.length_polyline-1) - # Between two points, there is only one segment - for j in range(self.length_polyline-1): - self.vectors[j] = self.points[j+1] - self.points[j] - self.lengths[j] = np.linalg.norm(self.vectors[j]) - self.unit_vectors[j] = self.vectors[j]/self.lengths[j] - - # print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") - - # Between two segments, there is only one angle - for k in range(1, self.length_polyline-1): - cross = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) - self.tangente[k] = sqrt((1+cross)/(1-cross)) - - def compute_alpha_radii(self): - self.alpha_radii[0] = 0 - self.alpha_radii[self.length_polyline-1] = 0 - - for i in range(1, self.length_polyline-2): - self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] - * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) - - def radius_balance(self, i): + def _alpha_assign(self, start_index, end_index): """ - Returns the radius that balances the radii on either end segement i. - """ - - alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / - (self.tangente[i] + self.tangente[i+1])) - alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) - - return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) - - def alpha_assign(self, start_index, end_index): - """ - The Alpha-assign procedure assigning radii based on a polyline. + The alpha-assign procedure assigning radii based on a polyline. """ minimum_radius, minimum_index = inf, end_index @@ -96,7 +54,7 @@ class Polyline: alpha_low, alpha_high = self.alpha_radii[start_index], alpha_b for i in range(start_index + 1, end_index - 2): # Radii for internal segments - alpha_a, alpha_b, current_radius = self.radius_balance(i) + alpha_a, alpha_b, current_radius = self._radius_balance(i) if current_radius < minimum_radius: alpha_low, alpha_high = alpha_a, self.alpha_radii[end_index] @@ -106,15 +64,52 @@ class Polyline: print(alpha_low, alpha_high) # Recur on lower segments - self.alpha_assign(start_index, minimum_index) + self._alpha_assign(start_index, minimum_index) # Recur on higher segments - self.alpha_assign(minimum_index + 1, end_index) + self._alpha_assign(minimum_index + 1, end_index) + + def _radius_balance(self, i: int): + """ + Returns the radius that balances the radii on either end segement i. + """ + + alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / + (self.tangente[i] + self.tangente[i+1])) + alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) + + return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) + + def _compute_requirements(self): + # Between two points, there is only one segment + for j in range(self.length_polyline-1): + self.vectors[j] = self.points[j+1] - self.points[j] + self.lengths[j] = np.linalg.norm(self.vectors[j]) + self.unit_vectors[j] = self.vectors[j]/self.lengths[j] + + # print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") + + # Between two segments, there is only one angle + for k in range(1, self.length_polyline-1): + cross = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) + self.tangente[k] = sqrt((1+cross)/(1-cross)) + + def _compute_alpha_radii(self): + self.alpha_radii[0] = 0 + self.alpha_radii[self.length_polyline-1] = 0 + + for i in range(1, self.length_polyline-2): + self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] + * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) -polyline = Polyline((Point2D(0, 0), Point2D(0, 10), Point2D( - 10, 10), Point2D(10, 20), Point2D(20, 20), Point2D(20, 30), Point2D(60, 60), Point2D(60, 0))) +# polyline = Polyline((Point2D(0, 9), Point2D(0, 10), Point2D( +# 10, 10), Point2D(10, 20), Point2D(20, 20), Point2D(20, 30), Point2D(60, 60), Point2D(-60, -60))) + +polyline = Polyline((Point2D(0, 10), Point2D(-10, -10), + Point2D(20, 0), Point2D(20, 20))) + # print(polyline.radius_balance(2)) -polyline.alpha_assign(1, polyline.length_polyline-1) +polyline._alpha_assign(1, polyline.length_polyline-1) print(polyline.alpha_radii) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 1bfce76..ae85eba 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -511,3 +511,14 @@ def curved_corner_by_curvature( distance_from_intersection = round(distance(start_curve_point, center)) return curve_corner_points, center, distance_from_intersection, parallel( (xyz0, intersection), -curvature_radius), parallel((xyz1, intersection), curvature_radius) + + +def coordinates_to_vectors(coordinates): + vectors = [] + for coordinate in coordinates: + vectors.append(np.array(coordinate.get_coordinates())) + + if (len(vectors) == 1): + return vectors[0] + else: + return vectors From a50fc34ed2a456264172a7ee4f70bb7cb6322f8c Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 01:57:42 +0200 Subject: [PATCH 34/69] Create even more objects --- networks/geometry/Circle.py | 2 +- networks/geometry/Enums.py | 13 +++ networks/geometry/Polyline.py | 2 +- networks/geometry/Segment2D.py | 183 +++++++++++++++++++++++++++++++++ networks/geometry/Segment3D.py | 10 ++ 5 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 networks/geometry/Enums.py create mode 100644 networks/geometry/Segment2D.py create mode 100644 networks/geometry/Segment3D.py diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index 50c153e..117f556 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -1,5 +1,5 @@ from typing import Type -import Point2D +from networks.geometry.Point2D import Point2D class Circle: diff --git a/networks/geometry/Enums.py b/networks/geometry/Enums.py new file mode 100644 index 0000000..dce1267 --- /dev/null +++ b/networks/geometry/Enums.py @@ -0,0 +1,13 @@ +from enum import Enum + + +class LINE_OVERLAP(Enum): + NONE = 0 + MAJOR = 1 + MINOR = 2 + + +class LINE_THICKNESS_MODE(Enum): + MIDDLE = 0 + DRAW_COUNTERCLOCKWISE = 1 + DRAW_CLOCKWISE = 2 diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index ace65e5..39bae5c 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -1,5 +1,5 @@ from typing import Type -import Point2D +from networks.geometry.Point2D import Point2D from math import sqrt, inf import numpy as np diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py new file mode 100644 index 0000000..98eb9c8 --- /dev/null +++ b/networks/geometry/Segment2D.py @@ -0,0 +1,183 @@ +from typing import Type +from networks.geometry.Enums import LINE_OVERLAP, LINE_THICKNESS_MODE +from networks.geometry.Point2D import Point2D + + +class Segment2D: + def __init__(start: Type[Point2D], end: Type[Point2D]): + self.start = start + self.end = end + self.coordinates = [] + + def draw_line_overlap(start: Type[Point2D], end: Type[Point2D], overlap: Type[LINE_OVERLAP]): + """Modified Bresenham draw (line) with optional overlap. + + From https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp + + Args: + start (Type[Point2D]): Start point of the segment. + end (Type[Point2D]): End point of the segment. + overlap (Type[LINE_OVERLAP]): Overlap draws additional pixel when changing minor direction. For standard bresenham overlap, choose LINE_OVERLAP_NONE. Can also be LINE_OVERLAP_MAJOR or LINE_OVERLAP_MINOR. + """ + start = start.copy() + end = end.copy() + + # Direction + delta_x = end.x - start.x + delta_y = end.y - start.y + + if (delta_x < 0): + delta_x = -delta_x + step_x = -1 + else: + step_x = +1 + + if (delta_y < 0): + delta_y = -delta_y + step_y = -1 + else: + step_y = +1 + + delta_2x = 2*delta_x + delta_2y = 2*delta_y + + self.coordinates.append(start) + + if (delta_x > delta_y): + error = delta_2y - delta_x + while (start.x != end.x): + start.x += step_x + if (error >= 0): + if (overlap == LINE_OVERLAP.MAJOR): + self.coordinates.append(start) + + start.y += step_y + if (overlap == LINE_OVERLAP.MINOR): + self.coordinates.append( + Point2D(start.x - step_x, start.y)) + error -= delta_2x + error += delta_2y + self.coordinates.append(start) + 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) + + start.x += step_x + if (overlap == LINE_OVERLAP.MINOR): + self.coordinates.append( + Point2D(start.x, start.y - step_y)) + error -= delta_2y + error += delta_2x + self.coordinates.append(start) + + def draw_thick_line(start: Type[Point2D], end: Type[Point2D], thickness: int, thickness_mode: Type[LINE_THICKNESS_MODE]): + """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 + + Args: + start (Type[Point2D]): Start point of the segment. + end (Type[Point2D]): End point of the segment. + thickness (int): Total width of the surface. Placement relative to the original segment depends on thickness_mode. + thickness_mode (Type[LINE_THICKNESS_MODE]): Can be one of LINE_THICKNESS_MIDDLE, LINE_THICKNESS_DRAW_CLOCKWISE, LINE_THICKNESS_DRAW_COUNTERCLOCKWISE. + """ + delta_y = end.x - start.x + delta_x = end.y - start.y + + swap = True + if delta_x < 0: + delta_x = -delta_x + step_x = -1 + swap = not swap + else: + step_x = +1 + + if (delta_y < 0): + delta_y = -delta_y + step_y = -1 + swap = not swap + else: + step_y = +1 + + delta_2x = 2 * delta_x + delta_2y = 2 * delta_y + + draw_start_adjust_count = int(thickness / 2) + if (thickness_mode == LineThicknessMode.DRAW_COUNTERCLOCKWISE): + draw_start_adjust_count = thickness - 1 + elif (thickness_mode == LineThicknessMode.DRAW_CLOCKWISE): + draw_start_adjust_count = 0 + + if (delta_x >= delta_y): + if swap: + draw_start_adjust_count = ( + thickness - 1) - draw_start_adjust_count + step_y = -step_y + else: + step_x = -step_x + + error = delta_2y - delta_x + for i in range(draw_start_adjust_count, 0, -1): + + start.x -= step_x + end.x -= step_x + if error >= 0: + start.y -= step_y + end.y -= step_y + error -= delta_2x + error += delta_2x + + draw_line_overlap(start, end, LINE_OVERLAP.NONE) + + error = delta_2x - delta_x + for i in range(thickness, 1, -1): + start.x += step_x + end.x += step_x + overlap = LINE_OVERLAP.NONE + if (error >= 0): + start.y += step_y + end.y += step_y + error -= delta_2x + overlap = LINE_OVERLAP.MAJOR + error += delta_2y + + draw_line_overlap(start, end, overlap) + + else: + if swap: + step_x = -step_x + else: + draw_start_adjust_count = ( + thickness - 1) - draw_start_adjust_count + step_y = -step_y + + error = delta_2x - delta_y + for i in range(draw_start_adjust_count, 0, -1): + start.y -= step_y + end.y -= step_y + if (error >= 0): + start.x -= step_x + end.x -= step_x + error -= delta_2y + error += delta_2x + + draw_line_overlap(start, end, LINE_OVERLAP.NONE) + + error = delta_2x - delta_y + for i in range(thickness, 1, -1): + start.y += step_y + end.y += step_y + overlap = LINE_OVERLAP.NONE + if (error >= 0): + start.x += step_x + end.x += step_x + error -= delta_2y + overlap = LINE_OVERLAP.MAJOR + error += delta_2x + + draw_line_overlap(start, end, overlap) diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py new file mode 100644 index 0000000..5179edb --- /dev/null +++ b/networks/geometry/Segment3D.py @@ -0,0 +1,10 @@ +from typing import Type +from networks.geometry.Enums import LINE_OVERLAP +from networks.geometry.Point3D import Point3D + + +class Segment3D: + def __init__(start: Type[Point3D], end: Type[Point3D]): + self.start = start + self.end = end + self.coordinates = [] From 41cab8c2ac72628e880f904c89f20555d0c4c2d7 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 02:13:16 +0200 Subject: [PATCH 35/69] Add 3d objects --- networks/geometry/Point3D.py | 17 ++ networks/geometry/Segment2D.py | 4 +- networks/geometry/Segment3D.py | 93 ++++++++++ networks/test.py | 322 --------------------------------- 4 files changed, 112 insertions(+), 324 deletions(-) create mode 100644 networks/geometry/Point3D.py delete mode 100644 networks/test.py diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py new file mode 100644 index 0000000..c85b250 --- /dev/null +++ b/networks/geometry/Point3D.py @@ -0,0 +1,17 @@ +from typing import Type + + +class Point3D: + def __init__(self, x: int, y: int, z: int): + self.x = x + self.y = y + self.z = z + + 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"Point2D(x: {self.x}, y: {self.y}, z: {self.z})" diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 98eb9c8..e8748f0 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -9,7 +9,7 @@ class Segment2D: self.end = end self.coordinates = [] - def draw_line_overlap(start: Type[Point2D], end: Type[Point2D], overlap: Type[LINE_OVERLAP]): + def compute_segment_overlap(start: Type[Point2D], end: Type[Point2D], overlap: Type[LINE_OVERLAP]): """Modified Bresenham draw (line) with optional overlap. From https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp @@ -74,7 +74,7 @@ class Segment2D: error += delta_2x self.coordinates.append(start) - def draw_thick_line(start: Type[Point2D], end: Type[Point2D], thickness: int, thickness_mode: Type[LINE_THICKNESS_MODE]): + def compute_thick_segment(start: Type[Point2D], end: Type[Point2D], thickness: int, thickness_mode: Type[LINE_THICKNESS_MODE]): """Bresenham with thickness. From https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index 5179edb..98a2a15 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -8,3 +8,96 @@ class Segment3D: self.start = start self.end = end self.coordinates = [] + + def compute_segment(start: Type[Point3D], end: Type[Point3D], overlap=True): + """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 (Type[Point3D]): First coordinates. + end (Type[Point3D]): Second coordinates. + overlap (bool, optional): If true, remove unnecessary coordinates connecting to other coordinates side by side, leaving only a diagonal connection. Defaults to True. + """ + self.coordinates.append(start) + dx = abs(end.x - start.x) + dy = abs(end.y - start.y) + dz = abs(end.z - start.z) + if end.x > start.x: + xs = 1 + else: + xs = -1 + if end.y > start.y: + ys = 1 + else: + ys = -1 + if end.z > start.z: + 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 start.x != end.x: + start.x += xs + self.coordinates.append(start) + if p1 >= 0: + start.y += ys + if not overlap: + if self.coordinates[-1].y != start.y: + self.coordinates.append(start) + p1 -= 2 * dx + if p2 >= 0: + start.z += zs + if not overlap: + if self.coordinates[-1].z != start.z: + self.coordinates.append(start) + 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 start.y != end.y: + start.y += ys + self.coordinates.append(start) + if p1 >= 0: + start.x += xs + if not overlap: + if self.coordinates[-1].x != start.x: + self.coordinates.append(start) + p1 -= 2 * dy + if p2 >= 0: + start.z += zs + if not overlap: + if self.coordinates[-1].z != start.z: + self.coordinates.append(start) + p2 -= 2 * dy + p1 += 2 * dx + p2 += 2 * dz + + # Driving axis is Z-axis + else: + p1 = 2 * dy - dz + p2 = 2 * dx - dz + while start.z != end.z: + start.z += zs + self.coordinates.append(start) + if p1 >= 0: + start.y += ys + if not overlap: + if self.coordinates[-1].y != start.y: + self.coordinates.append(start) + p1 -= 2 * dz + if p2 >= 0: + start.x += xs + if not overlap: + if self.coordinates[-1].x != start.x: + self.coordinates.append(start) + p2 -= 2 * dz + p1 += 2 * dy + p2 += 2 * dx diff --git a/networks/test.py b/networks/test.py deleted file mode 100644 index 1caf8fc..0000000 --- a/networks/test.py +++ /dev/null @@ -1,322 +0,0 @@ -from gdpc import Editor, Block, geometry -from enum import Enum -import random - - -def circle(xm, ym, r, pixel_perfect=True): - editor = Editor(buffering=True) - block = random.choices(("white_concrete", "red_concrete", "blue_concrete", "green_concrete", - "yellow_concrete", "black_concrete", "purple_concrete", "pink_concrete"))[0] - x = -r - y = 0 - err = 2-2*r - while (True): - editor.placeBlock((xm-x, 141, ym+y), - Block(block)) - editor.placeBlock((xm-y, 141, ym-x), - Block(block)) - editor.placeBlock((xm+x, 141, ym-y), - Block(block)) - editor.placeBlock((xm+y, 141, ym+x), - Block(block)) - print(xm-x, ym+y) - print(xm-y, ym-x) - print(xm+x, ym-y) - print(xm+y, ym+x) - r = err - update = False - if (r <= y): - y += 1 - update = True - err += y*2+1 - if ((r > x or err > y)): - if (pixel_perfect == True or update == False): - x += 1 - err += x*2+1 - update = True - if (x < 0): - continue - else: - break - - -def set_pixel(x, y, colour): - editor = Editor(buffering=True) - editor.placeBlock((x, 160, y), - Block(colour)) - - -def x_line(x1, x2, y, colour): - while x1 <= x2: - set_pixel(x1, y, colour) - x1 += 1 - - -def y_line(x, y1, y2, colour): - while y1 <= y2: - set_pixel(x, y1, colour) - y1 += 1 - - -def circle2(xc, yc, inner, outer): - # https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm - xo = outer - xi = inner - y = 0 - erro = 1 - xo - erri = 1 - xi - - while xo >= y: - colour = random.choices(("white_concrete", "red_concrete", "blue_concrete", "green_concrete", - "yellow_concrete", "black_concrete", "purple_concrete", "pink_concrete"))[0] - x_line(xc + xi, xc + xo, yc + y, colour) - y_line(xc + y, yc + xi, yc + xo, colour) - x_line(xc - xo, xc - xi, yc + y, colour) - y_line(xc - y, yc + xi, yc + xo, colour) - x_line(xc - xo, xc - xi, yc - y, colour) - y_line(xc - y, yc - xo, yc - xi, colour) - x_line(xc + xi, xc + xo, yc - y, colour) - y_line(xc + y, yc - xo, yc - xi, colour) - - y += 1 - - if erro < 0: - erro += 2 * y + 1 - else: - xo -= 1 - erro += 2 * (y - xo + 1) - - if y > inner: - xi = y - else: - if erri < 0: - erri += 2 * y + 1 - else: - xi -= 1 - erri += 2 * (y - xi + 1) - - -# print("\n") -# circle2(-1606, 758, 5, 15) -# circle2(-1606, 758, 5, 5) -# circle2(-1606, 758, 10, 10) -circle2(-1606, 758, 15, 17) - - -class LineOverlap(Enum): - NONE = 0 - MAJOR = 1 - MINOR = 2 - - -class LineThicknessMode(Enum): - MIDDLE = 0 - DRAW_COUNTERCLOCKWISE = 1 - DRAW_CLOCKWISE = 2 - - -class Point2D: - def __init__(self, x, y): - self.x = x - self.y = y - - def __repr__(self): - return f"({self.x} {self.y})" - - def copy(self): - return Point2D(self.x, self.y) - - def get_coordinates(self): - return (self.x, self.y) - - -def drawLineOverlap(start, end, overlap): - y = 120 - block = random.choices(("white_concrete", "red_concrete", "blue_concrete", "green_concrete", - "yellow_concrete", "black_concrete", "purple_concrete", "pink_concrete"))[0] - print(block) - editor = Editor(buffering=True) - - start = start.copy() - end = end.copy() - - # Direction - delta_x = end.x - start.x - delta_y = end.y - start.y - - if (delta_x < 0): - delta_x = -delta_x - step_x = -1 - else: - step_x = +1 - - if (delta_y < 0): - delta_y = -delta_y - step_y = -1 - else: - step_y = +1 - - delta_2x = 2*delta_x - delta_2y = 2*delta_y - - print(start.x, start.y) - editor.placeBlock((start.x, y, start.y), Block(block)) - - if (delta_x > delta_y): - error = delta_2y - delta_x - while (start.x != end.x): - start.x += step_x - if (error >= 0): - if (overlap == LineOverlap.MAJOR): - print(start.x, start.y) - editor.placeBlock((start.x, y, start.y), - Block(block)) - - start.y += step_y - if (overlap == LineOverlap.MINOR): - print(start.x - step_x, start.y) - editor.placeBlock((start.x - step_x, y, start.y), - Block(block)) - error -= delta_2x - error += delta_2y - print(start.x, start.y) - editor.placeBlock((start.x, y, start.y), - Block(block)) - else: - error = delta_2x - delta_y - while (start.y != end.y): - start.y += step_y - if (error >= 0): - if (overlap == LineOverlap.MAJOR): - print(start) - editor.placeBlock((start.x, y, start.y), - Block(block)) - start.x += step_x - if (overlap == LineOverlap.MINOR): - print(start.x, start.y - step.y, start.z, ) - editor.placeBlock((start.x, y, start.y - step.y), - Block(block)) - error -= delta_2y - error += delta_2x - print(start.x, start.y) - editor.placeBlock((start.x, y, start.y), - Block("white_concrete")) - - -# drawLineOverlap(Point2D(-10, 0, 0,), Point2D(10, 0, 3), -# LineOverlap.NONE) - - -def drawThickLine(start, end, thickness, thickness_mode): - delta_y = end.x - start.x - delta_x = end.y - start.y - - print("START", start) - - swap = True - if delta_x < 0: - delta_x = -delta_x - step_x = -1 - swap = not swap - else: - step_x = +1 - - if (delta_y < 0): - delta_y = -delta_y - step_y = -1 - swap = not swap - else: - step_y = +1 - - delta_2x = 2 * delta_x - delta_2y = 2 * delta_y - - draw_start_adjust_count = int(thickness / 2) - if (thickness_mode == LineThicknessMode.DRAW_COUNTERCLOCKWISE): - draw_start_adjust_count = thickness - 1 - elif (thickness_mode == LineThicknessMode.DRAW_CLOCKWISE): - draw_start_adjust_count = 0 - print("START", start) - if (delta_x >= delta_y): - if swap: - draw_start_adjust_count = (thickness - 1) - draw_start_adjust_count - step_y = -step_y - else: - step_x = -step_x - - error = delta_2y - delta_x - for i in range(draw_start_adjust_count, 0, -1): - print("START", start) - start.x -= step_x - end.x -= step_x - if error >= 0: - start.y -= step_y - end.y -= step_y - error -= delta_2x - error += delta_2x - print("START", start) - print("First print") - print(start, end) - drawLineOverlap(start, end, LineOverlap.NONE) - print(start, end) - print("End print") - - error = delta_2x - delta_x - for i in range(thickness, 1, -1): - start.x += step_x - end.x += step_x - overlap = LineOverlap.NONE - if (error >= 0): - start.y += step_y - end.y += step_y - error -= delta_2x - overlap = LineOverlap.MAJOR - error += delta_2y - print("Second print") - print(start, end) - drawLineOverlap(start, end, overlap) - print(start, end) - print("End print") - else: - if swap: - step_x = -step_x - else: - draw_start_adjust_count = (thickness - 1) - draw_start_adjust_count - step_y = -step_y - - error = delta_2x - delta_y - for i in range(draw_start_adjust_count, 0, -1): - start.y -= step_y - end.y -= step_y - if (error >= 0): - start.x -= step_x - end.x -= step_x - error -= delta_2y - error += delta_2x - - print("Third line") - print(start, end) - drawLineOverlap(start, end, LineOverlap.NONE) - print(start, end) - print("End line") - error = delta_2x - delta_y - for i in range(thickness, 1, -1): - start.y += step_y - end.y += step_y - overlap = LineOverlap.NONE - if (error >= 0): - start.x += step_x - end.x += step_x - error -= delta_2y - overlap = LineOverlap.MAJOR - error += delta_2x - print("Fourth line") - print(start, end) - drawLineOverlap(start, end, overlap) - print(start, end) - print("End") - - -print("SPACE\n\n") -drawThickLine(Point2D(-1681, 864), Point2D(-1804, 920), - 21, LineThicknessMode.MIDDLE) From 0070fc531d46a09a29361b6c083c577b393ed54d Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 02:23:26 +0200 Subject: [PATCH 36/69] Clean is_in_triangle --- networks/geometry/Point2D.py | 29 +++++++++++++++++++ networks/geometry/point_tools.py | 49 -------------------------------- 2 files changed, 29 insertions(+), 49 deletions(-) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 435172a..54a1366 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -14,3 +14,32 @@ class Point2D: def __repr__(self): return f"Point2D(x: {self.x}, y: {self.y})" + + def is_in_triangle(self, xy0: Type[Point2D], xy1: Type[Point2D], xy2: Type[Point2D]): + """Returns True is the point is in a triangle defined by 3 others points. + + Args: + xy0 (Type[Point2D]): Point of the triangle. + xy1 (Type[Point2D]): Point of the triangle. + xy2 (Type[Point2D]): Point of the triangle. + + Returns: + bool: False if the point is not inside the triangle. + """ + # https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. + dx = self.x - xy0.x + dy = self.y - xy0.y + + dx2 = xy2.x - xy0.x + dy2 = xy2.y - xy0.y + dx1 = xy1.x - xy0.x + dy1 = xy1.y - xy0.y + + s_p = (dy2 * dx) - (dx2 * dy) + t_p = (dx1 * dy) - (dy1 * dx) + d = (dx1 * dy2) - (dy1 * dx2) + + if d > 0: + return (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= d + else: + return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= d diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index ae85eba..24ab3db 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -3,55 +3,6 @@ import numpy as np from networks.geometry.segment_tools import discrete_segment, middle_point, parallel -def circle(center, radius): - """ - Can be used for circle or disc. Works in 2d but supports 3d. - - Args: - xyC (tuple): Coordinates of the center. - r (int): Radius of the circle. - - Returns: - dict: Keys are distance from the circle. Value is a list of all - coordinates at this distance. 0 for a circle. Negative values - for a disc, positive values for a hole. - """ - area = ( - (round(center[0]) - round(radius), round(center[-1]) - round(radius)), - (round(center[0]) + round(radius) + 1, - round(center[-1]) + round(radius) + 1), - ) - - circle = {} - for x in range(area[0][0], area[1][0]): - for y in range(area[0][1], area[1][1]): - d = round(distance((x, y), (center))) - radius - if circle.get(d) == None: - circle[d] = [] - circle[d].append((x, y)) - return circle - - -def is_in_triangle(point, xy0, xy1, xy2): - # Works in 2d but supports 3d. - # https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. - dX = point[0] - xy0[0] - dY = point[-1] - xy0[-1] - dX20 = xy2[0] - xy0[0] - dY20 = xy2[-1] - xy0[-1] - dX10 = xy1[0] - xy0[0] - dY10 = xy1[-1] - xy0[-1] - - s_p = (dY20 * dX) - (dX20 * dY) - t_p = (dX10 * dY) - (dY10 * dX) - D = (dX10 * dY20) - (dY10 * dX20) - - if D > 0: - return (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= D - else: - return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= D - - def distance(xy1, xy2): # TODO : Can be better. # Works in 2d but supports 3d. return sqrt((xy2[0] - xy1[0]) ** 2 + (xy2[-1] - xy1[-1]) ** 2) From 5ea926c9f859a1fcdd3c74c84373791483f042a7 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 02:26:18 +0200 Subject: [PATCH 37/69] Clean distance --- networks/geometry/Point2D.py | 6 +++++- networks/geometry/Point3D.py | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 54a1366..c50a6a7 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -18,6 +18,8 @@ class Point2D: def is_in_triangle(self, xy0: Type[Point2D], xy1: Type[Point2D], xy2: Type[Point2D]): """Returns True is the point is in a triangle defined by 3 others points. + From: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. + Args: xy0 (Type[Point2D]): Point of the triangle. xy1 (Type[Point2D]): Point of the triangle. @@ -26,7 +28,6 @@ class Point2D: Returns: bool: False if the point is not inside the triangle. """ - # https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. dx = self.x - xy0.x dy = self.y - xy0.y @@ -43,3 +44,6 @@ class Point2D: return (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= d else: return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= d + + def distance(self, point: Type[Point2D]): + return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2) diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index c85b250..393fa24 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -15,3 +15,6 @@ class Point3D: def __repr__(self): return f"Point2D(x: {self.x}, y: {self.y}, z: {self.z})" + + def distance(self, point: Type[Point3D]): + return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2 + (point.z - self.z) ** 2) From d993232a1a2aeb30d43f1165e8360ac04de4c25b Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 03:23:15 +0200 Subject: [PATCH 38/69] Clean Polyline and Points --- main.py | 18 +++++++++++--- networks/geometry/Point2D.py | 40 +++++++++++++++++++++++++++----- networks/geometry/Point3D.py | 5 +--- networks/geometry/Polyline.py | 26 ++++++--------------- networks/geometry/point_tools.py | 31 +------------------------ 5 files changed, 58 insertions(+), 62 deletions(-) diff --git a/main.py b/main.py index 6efc190..747834d 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +from networks.geometry.Polyline import Polyline +from networks.geometry.Point2D import Point2D import networks.roads.lines.Line as Line import networks.roads.lanes.Lane as Lane from gdpc import Editor, Block, geometry @@ -244,7 +246,17 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # --- -r = Road.Road(((-1829, 141, 553), (-1830, 110, 621), (-1711, 69, 625), (-1662, - 65, 627), (-1667, 65, 761), (-1683, 70, 800), (-1721, 70, 834)), "None") +# r = Road.Road(((-1829, 141, 553), (-1830, 110, 621), (-1711, 69, 625), (-1662, +# 65, 627), (-1667, 65, 761), (-1683, 70, 800), (-1721, 70, 834)), "None") -r.place_roads() +# r.place_roads() + + +polyline = Polyline((Point2D(0, 0), Point2D(0, 10), + Point2D(50, 10), Point2D(20, 20))) + + +# print(polyline.radius_balance(2)) + +# polyline._alpha_assign(1, polyline.length_polyline-1) +print(polyline.alpha_radii) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index c50a6a7..1d02ab7 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -1,21 +1,20 @@ -from typing import Type +import numpy as np +import math class Point2D: def __init__(self, x: int, y: int): self.x = x self.y = y + self.coordinate = (self.x, self.y) def copy(self): return Point2D(self.x, self.y) - def coordinates(self): - return (self.x, self.y) - def __repr__(self): return f"Point2D(x: {self.x}, y: {self.y})" - def is_in_triangle(self, xy0: Type[Point2D], xy1: Type[Point2D], xy2: Type[Point2D]): + def is_in_triangle(self, xy0: "Point2D", xy1: "Point2D", xy2: "Point2D"): """Returns True is the point is in a triangle defined by 3 others points. From: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. @@ -27,6 +26,9 @@ class Point2D: Returns: bool: False if the point is not inside the triangle. + + >>> Point2D(0, 0).is_in_triangle(Point2D(10, 10), Point2D(-10, 20), Point2D(0, -20))) + True """ dx = self.x - xy0.x dy = self.y - xy0.y @@ -45,5 +47,31 @@ class Point2D: else: return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= d - def distance(self, point: Type[Point2D]): + def distance(self, point: "Point2D"): return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2) + + def angle(self, xy1, xy2): + """ + Compute angle (in degrees). Corner in current point. + + From: https://stackoverflow.com/questions/13226038/calculating-angle-between-two-vectors-in-python + + Args: + xy0 (numpy.ndarray): Points in the form of [x,y]. + xy1 (numpy.ndarray): Points in the form of [x,y]. + xy2 (numpy.ndarray): Points in the form of [x,y]. + + Returns: + float: Angle negative for counterclockwise angle, angle positive + for counterclockwise angle. + + >>> Point2D(0, 0).angle(Point2D(10, 10), Point2D(0, -20)) + -135.0 + """ + if xy2 is None: + xy2 = xy1.coordinate + np.array([1, 0]) + v0 = np.array(xy1.coordinate) - np.array(self.coordinate) + v1 = np.array(xy2.coordinate) - np.array(self.coordinate) + + angle = math.atan2(np.linalg.det([v0, v1]), np.dot(v0, v1)) + return np.degrees(angle) diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index 393fa24..e94598e 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -1,6 +1,3 @@ -from typing import Type - - class Point3D: def __init__(self, x: int, y: int, z: int): self.x = x @@ -16,5 +13,5 @@ class Point3D: def __repr__(self): return f"Point2D(x: {self.x}, y: {self.y}, z: {self.z})" - def distance(self, point: Type[Point3D]): + def distance(self, point: "Point3D"): return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2 + (point.z - self.z) ** 2) diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 39bae5c..6f3a37e 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -1,12 +1,12 @@ -from typing import Type from networks.geometry.Point2D import Point2D +from networks.geometry.point_tools import coordinates_to_vectors from math import sqrt, inf import numpy as np class Polyline: - def __init__(self, points: List[Point2D]): + def __init__(self, points: list["Point2D"]): """A polyline with smooth corners, only composed of segments and circle arc. Mathematics and algorithms behind this can be found here: https://cdr.lib.unc.edu/concern/dissertations/pz50gw814?locale=en, E2 Construction of arc roads from polylines, page 210. @@ -16,6 +16,8 @@ class Polyline: Raises: ValueError: At least 4 points required. + + >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ self.points = coordinates_to_vectors(points) self.length_polyline = len(points) @@ -26,16 +28,16 @@ class Polyline: self.vectors = [None] * self.length_polyline self.lengths = [None] * self.length_polyline self.unit_vectors = [None] * self.length_polyline - self.tangente = [None] * self.length_polyline + self.tangente = [0] * self.length_polyline self.alpha_radii = [None] * self.length_polyline self._compute_requirements() self._compute_alpha_radii() - _alpha_assign(0, self.length_polyline-1) + self._alpha_assign(0, self.length_polyline-1) - def _alpha_assign(self, start_index, end_index): + def _alpha_assign(self, start_index: int, end_index: int): """ The alpha-assign procedure assigning radii based on a polyline. """ @@ -61,7 +63,6 @@ class Polyline: # Assign alphas at ends of selected segment self.alpha_radii[minimum_index] = alpha_low self.alpha_radii[minimum_index+1] = alpha_high - print(alpha_low, alpha_high) # Recur on lower segments self._alpha_assign(start_index, minimum_index) @@ -100,16 +101,3 @@ class Polyline: for i in range(1, self.length_polyline-2): self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) - - -# polyline = Polyline((Point2D(0, 9), Point2D(0, 10), Point2D( -# 10, 10), Point2D(10, 20), Point2D(20, 20), Point2D(20, 30), Point2D(60, 60), Point2D(-60, -60))) - -polyline = Polyline((Point2D(0, 10), Point2D(-10, -10), - Point2D(20, 0), Point2D(20, 20))) - - -# print(polyline.radius_balance(2)) - -polyline._alpha_assign(1, polyline.length_polyline-1) -print(polyline.alpha_radii) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 24ab3db..195ca6d 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -3,35 +3,6 @@ import numpy as np from networks.geometry.segment_tools import discrete_segment, middle_point, parallel -def distance(xy1, xy2): # TODO : Can be better. - # Works in 2d but supports 3d. - return sqrt((xy2[0] - xy1[0]) ** 2 + (xy2[-1] - xy1[-1]) ** 2) - - -def get_angle(xy0, xy1, xy2): - """ - Compute angle (in degrees) for xy0, xy1, xy2 corner. - - https://stackoverflow.com/questions/13226038/calculating-angle-between-two-vectors-in-python - - Args: - xy0 (numpy.ndarray): Points in the form of [x,y]. - xy1 (numpy.ndarray): Points in the form of [x,y]. - xy2 (numpy.ndarray): Points in the form of [x,y]. - - Returns: - float: Angle negative for counterclockwise angle, angle positive - for counterclockwise angle. - """ - if xy2 is None: - xy2 = xy1 + np.array([1, 0]) - v0 = np.array(xy0) - np.array(xy1) - v1 = np.array(xy2) - np.array(xy1) - - angle = np.math.atan2(np.linalg.det([v0, v1]), np.dot(v0, v1)) - return np.degrees(angle) - - def circle_points(center_point, radius, number=100): # Works in 2d but supports 3d. # https://stackoverflow.com/questions/8487893/generate-all-the-points-on-the-circumference-of-a-circle @@ -467,7 +438,7 @@ def curved_corner_by_curvature( def coordinates_to_vectors(coordinates): vectors = [] for coordinate in coordinates: - vectors.append(np.array(coordinate.get_coordinates())) + vectors.append(np.array(coordinate.coordinate)) if (len(vectors) == 1): return vectors[0] From c879f209d807809ce72995d76a1f255b37321010 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 03:27:55 +0200 Subject: [PATCH 39/69] Fix Circle --- main.py | 11 ++++++----- networks/geometry/Circle.py | 30 ++++++++++++++++-------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/main.py b/main.py index 747834d..8d24f4f 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +from networks.geometry.Circle import Circle from networks.geometry.Polyline import Polyline from networks.geometry.Point2D import Point2D import networks.roads.lines.Line as Line @@ -252,11 +253,11 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # r.place_roads() -polyline = Polyline((Point2D(0, 0), Point2D(0, 10), - Point2D(50, 10), Point2D(20, 20))) +# polyline = Polyline((Point2D(0, 0), Point2D(0, 10), +# Point2D(50, 10), Point2D(20, 20))) -# print(polyline.radius_balance(2)) +# # print(polyline.radius_balance(2)) -# polyline._alpha_assign(1, polyline.length_polyline-1) -print(polyline.alpha_radii) +# # polyline._alpha_assign(1, polyline.length_polyline-1) +# print(polyline.alpha_radii) diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index 117f556..7c480bb 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -3,15 +3,15 @@ from networks.geometry.Point2D import Point2D class Circle: - def __init__(center: Type[Point2D], inner: int, outer: int): + def __init__(self, center: Point2D, inner: int, outer: int): self.center = center self.inner = inner self.outer = outer self.coordinates = [] - circle(self.center, self.inner, self.outer) + self.circle(self.center, self.inner, self.outer) - def circle(center: Type[Point2D], inner: int, outer: int): + def circle(self, center: Point2D, inner: int, outer: int): """Compute discrete value of a 2d-circle with thickness. https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm @@ -20,6 +20,8 @@ class Circle: 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). + + >>> Circle(Point2D(0, 0), 5, 10) """ xo = outer xi = inner @@ -28,14 +30,14 @@ class Circle: erri = 1 - xi while xo >= y: - _x_line(center.x + xi, center.x + xo, center.y + y) - _y_line(center.x + y, center.y + xi, center.y + xo) - _x_line(center.x - xo, center.x - xi, center.y + y) - _y_line(center.x - y, center.y + xi, center.y + xo) - _x_line(center.x - xo, center.x - xi, center.y - y) - _y_line(center.x - y, center.y - xo, center.y - xi) - _x_line(center.x + xi, center.x + xo, center.y - y) - _y_line(center.x + y, center.y - xo, center.y - xi) + self._x_line(center.x + xi, center.x + xo, center.y + y) + self._y_line(center.x + y, center.y + xi, center.y + xo) + self._x_line(center.x - xo, center.x - xi, center.y + y) + self._y_line(center.x - y, center.y + xi, center.y + xo) + self._x_line(center.x - xo, center.x - xi, center.y - y) + self._y_line(center.x - y, center.y - xo, center.y - xi) + self._x_line(center.x + xi, center.x + xo, center.y - y) + self._y_line(center.x + y, center.y - xo, center.y - xi) y += 1 @@ -54,14 +56,14 @@ class Circle: xi -= 1 erri += 2 * (y - xi + 1) - def _x_line(x1, x2, y): + def _x_line(self, x1, x2, y): while x1 <= x2: self.coordinates.append(Point2D(x1, y)) x1 += 1 - def _y_line(x, y1, y2): + def _y_line(self, x, y1, y2): while y1 <= y2: - self.coordinate.append(Point2D(x, y1)) + self.coordinates.append(Point2D(x, y1)) y1 += 1 def __repr__(self): From 9433503dddb72f2c08feef10fe4765147e701163 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 03:45:25 +0200 Subject: [PATCH 40/69] Fix Segments --- main.py | 7 ++++++ networks/geometry/Segment2D.py | 46 ++++++++++++++++++++-------------- networks/geometry/Segment3D.py | 17 +++++++++---- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/main.py b/main.py index 8d24f4f..37ff472 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,6 @@ +from networks.geometry.Point3D import Point3D +from networks.geometry.Segment3D import Segment3D +from networks.geometry.Segment2D import Segment2D from networks.geometry.Circle import Circle from networks.geometry.Polyline import Polyline from networks.geometry.Point2D import Point2D @@ -261,3 +264,7 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # polyline._alpha_assign(1, polyline.length_polyline-1) # print(polyline.alpha_radii) + + +s = Segment2D(Point2D(0, 0), Point2D(10, 15), 1) +print(s) diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index e8748f0..413cfe2 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -4,20 +4,28 @@ from networks.geometry.Point2D import Point2D class Segment2D: - def __init__(start: Type[Point2D], end: Type[Point2D]): + def __init__(self, start: Point2D, end: Point2D, thickness: int, thickness_mode: LINE_THICKNESS_MODE = LINE_THICKNESS_MODE.MIDDLE): self.start = start self.end = end self.coordinates = [] + self.thickness = thickness + self.thickness_mode = thickness_mode - def compute_segment_overlap(start: Type[Point2D], end: Type[Point2D], overlap: Type[LINE_OVERLAP]): + self.compute_thick_segment( + self.start, self.end, self.thickness, self.thickness_mode) + + def __repr__(self): + return str(self.coordinates) + + def compute_segment_overlap(self, start: Point2D, end: Point2D, overlap: LINE_OVERLAP): """Modified Bresenham draw (line) with optional overlap. From https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp Args: - start (Type[Point2D]): Start point of the segment. - end (Type[Point2D]): End point of the segment. - overlap (Type[LINE_OVERLAP]): Overlap draws additional pixel when changing minor direction. For standard bresenham overlap, choose LINE_OVERLAP_NONE. Can also be LINE_OVERLAP_MAJOR or LINE_OVERLAP_MINOR. + start (Point2D): Start point of the segment. + end (Point2D): End point of the segment. + overlap (LINE_OVERLAP): Overlap draws additional pixel when changing minor direction. For standard bresenham overlap, choose LINE_OVERLAP_NONE. Can also be LINE_OVERLAP_MAJOR or LINE_OVERLAP_MINOR. """ start = start.copy() end = end.copy() @@ -41,7 +49,7 @@ class Segment2D: delta_2x = 2*delta_x delta_2y = 2*delta_y - self.coordinates.append(start) + self.coordinates.append(start.copy()) if (delta_x > delta_y): error = delta_2y - delta_x @@ -49,7 +57,7 @@ class Segment2D: start.x += step_x if (error >= 0): if (overlap == LINE_OVERLAP.MAJOR): - self.coordinates.append(start) + self.coordinates.append(start.copy()) start.y += step_y if (overlap == LINE_OVERLAP.MINOR): @@ -64,7 +72,7 @@ class Segment2D: start.y += step_y if (error >= 0): if (overlap == LINE_OVERLAP.MAJOR): - self.coordinates.append(start) + self.coordinates.append(start.copy()) start.x += step_x if (overlap == LINE_OVERLAP.MINOR): @@ -72,19 +80,19 @@ class Segment2D: Point2D(start.x, start.y - step_y)) error -= delta_2y error += delta_2x - self.coordinates.append(start) + self.coordinates.append(start.copy()) - def compute_thick_segment(start: Type[Point2D], end: Type[Point2D], thickness: int, thickness_mode: Type[LINE_THICKNESS_MODE]): + def compute_thick_segment(self, start: Point2D, end: Point2D, thickness: int, thickness_mode: LINE_THICKNESS_MODE): """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 Args: - start (Type[Point2D]): Start point of the segment. - end (Type[Point2D]): End point of the segment. + start (Point2D): Start point of the segment. + end (Point2D): End point of the segment. thickness (int): Total width of the surface. Placement relative to the original segment depends on thickness_mode. - thickness_mode (Type[LINE_THICKNESS_MODE]): Can be one of LINE_THICKNESS_MIDDLE, LINE_THICKNESS_DRAW_CLOCKWISE, LINE_THICKNESS_DRAW_COUNTERCLOCKWISE. + thickness_mode (LINE_THICKNESS_MODE): Can be one of LINE_THICKNESS_MIDDLE, LINE_THICKNESS_DRAW_CLOCKWISE, LINE_THICKNESS_DRAW_COUNTERCLOCKWISE. """ delta_y = end.x - start.x delta_x = end.y - start.y @@ -108,9 +116,9 @@ class Segment2D: delta_2y = 2 * delta_y draw_start_adjust_count = int(thickness / 2) - if (thickness_mode == LineThicknessMode.DRAW_COUNTERCLOCKWISE): + if (thickness_mode == LINE_THICKNESS_MODE.DRAW_COUNTERCLOCKWISE): draw_start_adjust_count = thickness - 1 - elif (thickness_mode == LineThicknessMode.DRAW_CLOCKWISE): + elif (thickness_mode == LINE_THICKNESS_MODE.DRAW_CLOCKWISE): draw_start_adjust_count = 0 if (delta_x >= delta_y): @@ -132,7 +140,7 @@ class Segment2D: error -= delta_2x error += delta_2x - draw_line_overlap(start, end, LINE_OVERLAP.NONE) + self.compute_segment_overlap(start, end, LINE_OVERLAP.NONE) error = delta_2x - delta_x for i in range(thickness, 1, -1): @@ -146,7 +154,7 @@ class Segment2D: overlap = LINE_OVERLAP.MAJOR error += delta_2y - draw_line_overlap(start, end, overlap) + self.compute_segmen_overlap(start, end, overlap) else: if swap: @@ -166,7 +174,7 @@ class Segment2D: error -= delta_2y error += delta_2x - draw_line_overlap(start, end, LINE_OVERLAP.NONE) + self.compute_segmen_overlap(start, end, LINE_OVERLAP.NONE) error = delta_2x - delta_y for i in range(thickness, 1, -1): @@ -180,4 +188,4 @@ class Segment2D: overlap = LINE_OVERLAP.MAJOR error += delta_2x - draw_line_overlap(start, end, overlap) + self.compute_segmen_overlap(start, end, overlap) diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index 98a2a15..b90ad4d 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -1,23 +1,30 @@ -from typing import Type from networks.geometry.Enums import LINE_OVERLAP from networks.geometry.Point3D import Point3D class Segment3D: - def __init__(start: Type[Point3D], end: Type[Point3D]): + def __init__(self, start: Point3D, end: Point3D, overlap: bool = True): self.start = start self.end = end self.coordinates = [] + self.overlap = overlap - def compute_segment(start: Type[Point3D], end: Type[Point3D], overlap=True): + 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 = True): """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 (Type[Point3D]): First coordinates. - end (Type[Point3D]): Second coordinates. + start (Point3D): First coordinates. + end (Point3D): Second coordinates. overlap (bool, optional): If true, remove unnecessary coordinates connecting to other coordinates side by side, leaving only a diagonal connection. Defaults to True. + + >>> Segment3D(Point3D(0, 0, 0), Point3D(10, 10, 15)) """ self.coordinates.append(start) dx = abs(end.x - start.x) From b39c9d13dde98f82cc2f8476994bb97d77fa006a Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 15:29:57 +0200 Subject: [PATCH 41/69] Add circle_points to Circle and round() to Points --- main.py | 4 +++ networks/geometry/Circle.py | 45 ++++++++++++++++++++++++++++---- networks/geometry/Point2D.py | 6 +++++ networks/geometry/Point3D.py | 7 +++++ networks/geometry/Segment2D.py | 2 ++ networks/geometry/point_tools.py | 17 ------------ 6 files changed, 59 insertions(+), 22 deletions(-) diff --git a/main.py b/main.py index 37ff472..0b38b73 100644 --- a/main.py +++ b/main.py @@ -268,3 +268,7 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", s = Segment2D(Point2D(0, 0), Point2D(10, 15), 1) print(s) + + +c = Circle(Point2D(0, 0), 5, 10) +print(c.circle_points(10, 10)) diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index 7c480bb..dcd2cfe 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -1,17 +1,25 @@ -from typing import Type from networks.geometry.Point2D import Point2D +from math import cos, sin, pi +from typing import List class Circle: def __init__(self, center: Point2D, inner: int, outer: int): self.center = center + self.inner = inner self.outer = outer self.coordinates = [] + self.radius = None # Used with circle_points() + self.spaced_coordinates = [] + self.circle(self.center, self.inner, self.outer) - def circle(self, center: Point2D, inner: int, outer: int): + def __repr__(self): + return f"Circle(center: {self.center}, inner: {self.inner}, outer: {self.outer})" + + def circle(self, center: Point2D, 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 @@ -21,6 +29,9 @@ class Circle: inner (int): The minimum radius at which the disc is filled (included). outer (int): The maximum radius where disc filling stops (included). + Returns: + list(Point2D): List of 2d-coordinates composing the surface. Note that some coordinates are redondant and are not ordered. + >>> Circle(Point2D(0, 0), 5, 10) """ xo = outer @@ -55,6 +66,33 @@ class Circle: else: xi -= 1 erri += 2 * (y - xi + 1) + return self.coordinates + + def circle_points(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 + + Args: + number (int): Number of coordinates to be returned. + radius (int, optional): Radius of the circle. Defaults to self.inner. + + Returns: + list(Point2D): List of evenly spaced 2d-coordinates forming the circle. + """ + print(self.center.x) + self.spaced_coordinates = [ + Point2D(cos(2 * pi / number * i) * radius, + sin(2 * pi / number * i) * radius) + for i in range(0, number + 1) + ] + + 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 + ).round() + return self.spaced_coordinates def _x_line(self, x1, x2, y): while x1 <= x2: @@ -65,6 +103,3 @@ class Circle: while y1 <= y2: self.coordinates.append(Point2D(x, y1)) y1 += 1 - - def __repr__(self): - return f"Circle(center: {self.center}, inner: {self.inner}, outer: {self.outer})" diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 1d02ab7..0498f61 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -75,3 +75,9 @@ class Point2D: angle = math.atan2(np.linalg.det([v0, v1]), np.dot(v0, v1)) return np.degrees(angle) + + def round(self, ndigits: int = None) -> "Point2D": + self.x = round(self.x, ndigits) + self.y = round(self.y, ndigits) + self.coordinate = (self.x, self.y) + return self diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index e94598e..d280575 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -15,3 +15,10 @@ class Point3D: def distance(self, point: "Point3D"): return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2 + (point.z - self.z) ** 2) + + def round(self, ndigits: int = None) -> "Point3D": + self.x = round(self.x, ndigits) + self.y = round(self.y, ndigits) + self.z = round(self.z, ndigits) + self.coordinate = (self.x, self.y, self.z) + return self diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 413cfe2..59d3015 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -26,6 +26,8 @@ class Segment2D: start (Point2D): Start point of the segment. end (Point2D): End point of the segment. overlap (LINE_OVERLAP): Overlap draws additional pixel when changing minor direction. For standard bresenham overlap, choose LINE_OVERLAP_NONE. Can also be LINE_OVERLAP_MAJOR or LINE_OVERLAP_MINOR. + + >>> Segment2D(Point2D(0, 0), Point2D(10, 15), 1) """ start = start.copy() end = end.copy() diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 195ca6d..14fc45f 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -3,23 +3,6 @@ import numpy as np from networks.geometry.segment_tools import discrete_segment, middle_point, parallel -def circle_points(center_point, radius, number=100): - # Works in 2d but supports 3d. - # https://stackoverflow.com/questions/8487893/generate-all-the-points-on-the-circumference-of-a-circle - points = [ - (cos(2 * pi / number * x) * radius, sin(2 * pi / number * x) * radius) - for x in range(0, number + 1) - ] - - for i in range(len(points)): - points[i] = ( - points[i][0] + center_point[0], - points[i][-1] + center_point[-1], - ) - - return points - - def optimized_path(points, start=None): # https://stackoverflow.com/questions/45829155/sort-points-in-order-to-have-a-continuous-curve-using-python if start is None: From ac86f8588ea40e7c448ab9aaca2a69de2ef9f002 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 17:04:40 +0200 Subject: [PATCH 42/69] Add nearest support to Point3D --- main.py | 8 -------- networks/geometry/Point2D.py | 18 +++++++++++++++--- networks/geometry/Point3D.py | 20 +++++++++++++++++++- networks/geometry/point_tools.py | 4 ---- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/main.py b/main.py index 0b38b73..cb71a71 100644 --- a/main.py +++ b/main.py @@ -264,11 +264,3 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # polyline._alpha_assign(1, polyline.length_polyline-1) # print(polyline.alpha_radii) - - -s = Segment2D(Point2D(0, 0), Point2D(10, 15), 1) -print(s) - - -c = Circle(Point2D(0, 0), 5, 10) -print(c.circle_points(10, 10)) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 0498f61..a251545 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -1,5 +1,6 @@ import numpy as np -import math +from typing import List +from math import atan2, sqrt class Point2D: @@ -47,9 +48,20 @@ class Point2D: else: return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= d - def distance(self, point: "Point2D"): + 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. + + Args: + points (List[Point2D]): List of the points to test. + + Returns: + Point2D: The nearest point, and if multiple, the first in the list. + """ + return min(points, key=lambda point: self.distance(point)) + def angle(self, xy1, xy2): """ Compute angle (in degrees). Corner in current point. @@ -73,7 +85,7 @@ class Point2D: v0 = np.array(xy1.coordinate) - np.array(self.coordinate) v1 = np.array(xy2.coordinate) - np.array(self.coordinate) - angle = math.atan2(np.linalg.det([v0, v1]), np.dot(v0, v1)) + angle = atan2(np.linalg.det([v0, v1]), np.dot(v0, v1)) return np.degrees(angle) def round(self, ndigits: int = None) -> "Point2D": diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index d280575..ddfc80b 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -1,3 +1,7 @@ +from typing import List +from math import atan2, sqrt + + class Point3D: def __init__(self, x: int, y: int, z: int): self.x = x @@ -11,11 +15,25 @@ class Point3D: return (self.x, self.y, self.z) def __repr__(self): - return f"Point2D(x: {self.x}, y: {self.y}, z: {self.z})" + 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 nearest(self, points: List["Point3D"]) -> "Point3D": + """Return the nearest point. If multiple nearest point, returns the first in the list. + + Args: + points (List[Point2D]): List of the points to test. + + Returns: + Point3D: The nearest point, and if multiple, the first in the list. + + >>> print(Point3D(0, 0, 0).nearest((Point3D(-10, 10, 5), Point3D(10, 10, 1)))) + Point3D(x: 10, y: 10, z: 1) + """ + return min(points, key=lambda point: self.distance(point)) + def round(self, ndigits: int = None) -> "Point3D": self.x = round(self.x, ndigits) self.y = round(self.y, ndigits) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 14fc45f..bd9bca0 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -17,10 +17,6 @@ def optimized_path(points, start=None): return path -def nearest(points, start): - return min(points, key=lambda x: distance(start, x)) - - def sort_by_clockwise(points): """ Sort point in a rotation order. Works in 2d but supports 3d. From 9c215a5d249edbf9013957cdd7749d7cf4740753 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 17:39:17 +0200 Subject: [PATCH 43/69] Add optimized path to Points --- main.py | 3 +++ networks/geometry/Point2D.py | 26 ++++++++++++++++++++++++++ networks/geometry/Point3D.py | 26 ++++++++++++++++++++++++++ networks/geometry/point_tools.py | 14 -------------- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index cb71a71..eb6fe96 100644 --- a/main.py +++ b/main.py @@ -264,3 +264,6 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # polyline._alpha_assign(1, polyline.length_polyline-1) # print(polyline.alpha_radii) + +print( + Point2D(-2, -5).optimized_path([Point2D(0, 0), Point2D(10, 5), Point2D(1, 3)])) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index a251545..7b229e6 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -62,6 +62,32 @@ class Point2D: """ return min(points, key=lambda point: self.distance(point)) + def optimized_path(self, points: List["Point2D"]): + """Get an optimized ordered path starting from the current point. + + From: https://stackoverflow.com/questions/45829155/sort-points-in-order-to-have-a-continuous-curve-using-python + + Args: + points (List[Point2D]): List of 2d-points. Could contain the current point. + + Returns: + List[Point2D]: Ordered list of 2d-points starting from the current point. + + >>> Point2D(-2, -5).optimized_path([Point2D(0, 0), Point2D(10, 5), Point2D(1, 3)]) + [Point2D(x: -2, y: -5), Point2D(x: 0, y: 0), Point2D(x: 1, y: 3), Point2D(x: 10, y: 5)] + """ + start = self + if start not in points: + points.append(start) + pass_by = points + path = [start] + pass_by.remove(start) + while pass_by: + nearest = min(pass_by, key=lambda point: point.distance(path[-1])) + path.append(nearest) + pass_by.remove(nearest) + return path + def angle(self, xy1, xy2): """ Compute angle (in degrees). Corner in current point. diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index ddfc80b..1429e9d 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -34,6 +34,32 @@ class Point3D: """ return min(points, key=lambda point: self.distance(point)) + def optimized_path(self, points: List["Point3D"]): + """Get an optimized ordered path starting from the current point. + + From: https://stackoverflow.com/questions/45829155/sort-points-in-order-to-have-a-continuous-curve-using-python + + Args: + points (List[Point2D]): List of 3d-points. Could contain the current point. + + Returns: + List[Point2D]: Ordered list of 3d-points starting from the current point. + + >>> Point3D(-2, -5, 6).optimized_path([Point3D(0, 0, 7), Point3D(10, 5, 1), Point3D(1, 3, 3)]) + [Point3D(x: -2, y: -5, z: 6), Point3D(x: 0, y: 0, z: 7), Point3D(x: 1, y: 3, z: 3), Point3D(x: 10, y: 5, z: 1)] + """ + start = self + if start not in points: + points.append(start) + pass_by = points + path = [start] + pass_by.remove(start) + while pass_by: + nearest = min(pass_by, key=lambda point: point.distance(path[-1])) + path.append(nearest) + pass_by.remove(nearest) + return path + def round(self, ndigits: int = None) -> "Point3D": self.x = round(self.x, ndigits) self.y = round(self.y, ndigits) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index bd9bca0..eb4b46b 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -3,20 +3,6 @@ import numpy as np from networks.geometry.segment_tools import discrete_segment, middle_point, parallel -def optimized_path(points, start=None): - # https://stackoverflow.com/questions/45829155/sort-points-in-order-to-have-a-continuous-curve-using-python - if start is None: - start = points[0] - pass_by = points - path = [start] - pass_by.remove(start) - while pass_by: - nearest = min(pass_by, key=lambda x: distance(path[-1], x)) - path.append(nearest) - pass_by.remove(nearest) - return path - - def sort_by_clockwise(points): """ Sort point in a rotation order. Works in 2d but supports 3d. From 229c43c308b0caee2de3bcace12c4f9df3cb974c Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 18:45:16 +0200 Subject: [PATCH 44/69] Add sort_by_rotation --- main.py | 3 +- networks/geometry/Enums.py | 5 +++ networks/geometry/Point2D.py | 64 +++++++++++++++++++++++++++++++++++- networks/geometry/Point3D.py | 6 ++-- 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index eb6fe96..84cc31e 100644 --- a/main.py +++ b/main.py @@ -16,6 +16,7 @@ from buildings.Building import Building import random from networks.roads import Road as Road +from networks.geometry.Enums import ROTATION from networks.roads.intersections import Intersection as Intersection from networks.geometry.point_tools import curved_corner_by_curvature, curved_corner_by_distance @@ -266,4 +267,4 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # print(polyline.alpha_radii) print( - Point2D(-2, -5).optimized_path([Point2D(0, 0), Point2D(10, 5), Point2D(1, 3)])) + Point2D(-10, -10).sort_by_rotation([Point2D(10, 10), Point2D(-10, 10), Point2D(10, -10)], rotation=ROTATION.CLOCKWISE)) diff --git a/networks/geometry/Enums.py b/networks/geometry/Enums.py index dce1267..1def6ff 100644 --- a/networks/geometry/Enums.py +++ b/networks/geometry/Enums.py @@ -11,3 +11,8 @@ class LINE_THICKNESS_MODE(Enum): MIDDLE = 0 DRAW_COUNTERCLOCKWISE = 1 DRAW_CLOCKWISE = 2 + + +class ROTATION(Enum): + CLOCKWISE = 0 + COUNTERCLOCKWISE = 1 diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 7b229e6..3d905b8 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -1,6 +1,7 @@ import numpy as np from typing import List from math import atan2, sqrt +from networks.geometry.Enums import ROTATION class Point2D: @@ -15,6 +16,11 @@ class Point2D: def __repr__(self): return f"Point2D(x: {self.x}, y: {self.y})" + def __eq__(self, other): + if isinstance(other, Point2D): + return self.x == other.x and self.y == other.y + return False + def is_in_triangle(self, xy0: "Point2D", xy1: "Point2D", xy2: "Point2D"): """Returns True is the point is in a triangle defined by 3 others points. @@ -62,7 +68,7 @@ class Point2D: """ return min(points, key=lambda point: self.distance(point)) - def optimized_path(self, points: List["Point2D"]): + def optimized_path(self, points: List["Point2D"]) -> List["Point2D"]: """Get an optimized ordered path starting from the current point. From: https://stackoverflow.com/questions/45829155/sort-points-in-order-to-have-a-continuous-curve-using-python @@ -88,6 +94,62 @@ class Point2D: pass_by.remove(nearest) return path + def sort_by_rotation(self, points: List["Point2D"], rotation: ROTATION = ROTATION.CLOCKWISE) -> List["Point2D"]: + """Sort points in clockwise order, starting from current point. + + From: https://stackoverflow.com/questions/58377015/counterclockwise-sorting-of-x-y-data + + Args: + points (List[Point2D]): List of 2d-points. Current point can be included here. + rotation (ROTATION): Can be ROTATION.CLOCKWISE or ROTATION.COUNTERCLOCKWISE. Optional. Defaults to ROTATION.CLOCKWISE. + + Returns: + List[Point2D]: List of 2d-points. + + >>> Point2D(-10, -10).sort_by_rotation([Point2D(10, 10), Point2D(-10, 10), Point2D(10, -10)]) + [Point2D(x: -10, y: -10), Point2D(x: 10, y: -10), Point2D(x: 10, y: 10), Point2D(x: -10, y: 10)] + """ + if self not in points: + points.append(self) + x, y = [], [] + for i in range(len(points)): + x.append(points[i].x) + y.append(points[i].y) + x, y = np.array(x), np.array(y) + + x0 = np.mean(x) + y0 = np.mean(y) + + r = np.sqrt((x - x0) ** 2 + (y - y0) ** 2) + + angles = np.where( + (y - y0) > 0, + np.arccos((x - x0) / r), + 2 * np.pi - np.arccos((x - x0) / r), + ) + + mask = np.argsort(angles) + + x_sorted = list(x[mask]) + y_sorted = list(y[mask]) + + # Rearrange tuples to get the correct coordinates. + sorted_points = [] + for i in range(len(points)): + j = 0 + while (x_sorted[i] != points[j].x) and (y_sorted[i] != points[j].y): + j += 1 + else: + sorted_points.append(Point2D(x_sorted[i], y_sorted[i])) + + if rotation == ROTATION.CLOCKWISE: + sorted_points.reverse() + start_index = sorted_points.index(self) + return sorted_points[start_index:] + sorted_points[:start_index] + else: + start_index = sorted_points.index(self) + return sorted_points[start_index:] + sorted_points[:start_index] + def angle(self, xy1, xy2): """ Compute angle (in degrees). Corner in current point. diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index 1429e9d..28fe129 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -34,16 +34,16 @@ class Point3D: """ return min(points, key=lambda point: self.distance(point)) - def optimized_path(self, points: List["Point3D"]): + def optimized_path(self, points: List["Point3D"]) -> List["Point3D"]: """Get an optimized ordered path starting from the current point. From: https://stackoverflow.com/questions/45829155/sort-points-in-order-to-have-a-continuous-curve-using-python Args: - points (List[Point2D]): List of 3d-points. Could contain the current point. + points (List[Point3D]): List of 3d-points. Could contain the current point. Returns: - List[Point2D]: Ordered list of 3d-points starting from the current point. + List[Point3D]: Ordered list of 3d-points starting from the current point. >>> Point3D(-2, -5, 6).optimized_path([Point3D(0, 0, 7), Point3D(10, 5, 1), Point3D(1, 3, 3)]) [Point3D(x: -2, y: -5, z: 6), Point3D(x: 0, y: 0, z: 7), Point3D(x: 1, y: 3, z: 3), Point3D(x: 10, y: 5, z: 1)] From f98af90b3e090bd282d500c4acc503cf1053a8c2 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 18:59:14 +0200 Subject: [PATCH 45/69] Move to_vectors to Points --- main.py | 3 +- networks/geometry/Point2D.py | 11 ++++ networks/geometry/Point3D.py | 12 +++++ networks/geometry/Polyline.py | 6 ++- networks/geometry/point_tools.py | 53 -------------------- networks/roads/intersections/Intersection.py | 2 +- 6 files changed, 29 insertions(+), 58 deletions(-) diff --git a/main.py b/main.py index 84cc31e..b08ddef 100644 --- a/main.py +++ b/main.py @@ -266,5 +266,4 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # polyline._alpha_assign(1, polyline.length_polyline-1) # print(polyline.alpha_radii) -print( - Point2D(-10, -10).sort_by_rotation([Point2D(10, 10), Point2D(-10, 10), Point2D(10, -10)], rotation=ROTATION.CLOCKWISE)) +print(Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20)))) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 3d905b8..472332a 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -181,3 +181,14 @@ class Point2D: self.y = round(self.y, ndigits) self.coordinate = (self.x, self.y) return self + + @staticmethod + def to_vectors(points: List["Point3D"]): + vectors = [] + for point in points: + vectors.append(np.array(point.coordinate)) + + if (len(vectors) == 1): + return vectors[0] + else: + return vectors diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index 28fe129..e3aad33 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -1,5 +1,6 @@ from typing import List from math import atan2, sqrt +import numpy as np class Point3D: @@ -66,3 +67,14 @@ class Point3D: self.z = round(self.z, ndigits) self.coordinate = (self.x, self.y, self.z) return self + + @staticmethod + def to_vectors(points: List["Point3D"]): + vectors = [] + for point in points: + vectors.append(np.array(point.coordinate)) + + if (len(vectors) == 1): + return vectors[0] + else: + return vectors diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 6f3a37e..393b154 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -1,5 +1,4 @@ from networks.geometry.Point2D import Point2D -from networks.geometry.point_tools import coordinates_to_vectors from math import sqrt, inf import numpy as np @@ -19,7 +18,7 @@ class Polyline: >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ - self.points = coordinates_to_vectors(points) + self.points = Point2D.to_vectors(points) self.length_polyline = len(points) if self.length_polyline < 4: @@ -37,6 +36,9 @@ class Polyline: self._alpha_assign(0, self.length_polyline-1) + def __repr__(self): + return str(self.alpha_radii) + def _alpha_assign(self, start_index: int, end_index: int): """ The alpha-assign procedure assigning radii based on a polyline. diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index eb4b46b..d5c889d 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -3,59 +3,6 @@ import numpy as np from networks.geometry.segment_tools import discrete_segment, middle_point, parallel -def sort_by_clockwise(points): - """ - Sort point in a rotation order. Works in 2d but supports 3d. - - https://stackoverflow.com/questions/58377015/counterclockwise-sorting-of-x-y-data - - Args: - points: List of points to sort in the form of [(x, y, z), (x, y, - z)] or [(x, y), (x, y), (x, y), (x, y)]... - - Returns: - list: List of tuples of coordinates sorted (2d or 3d). - - >>> sort_by_clockwise([(0, 45, 100), (4, -5, 5),(-5, 36, -2)]) - [(0, 45, 100), (-5, 36, -2), (4, -5, 5)] - """ - x, y = [], [] - for i in range(len(points)): - x.append(points[i][0]) - y.append(points[i][-1]) - x, y = np.array(x), np.array(y) - - x0 = np.mean(x) - y0 = np.mean(y) - - r = np.sqrt((x - x0) ** 2 + (y - y0) ** 2) - - angles = np.where( - (y - y0) > 0, - np.arccos((x - x0) / r), - 2 * np.pi - np.arccos((x - x0) / r), - ) - - mask = np.argsort(angles) - - x_sorted = list(x[mask]) - y_sorted = list(y[mask]) - - # Rearrange tuples to get the right coordinates. - sorted_points = [] - for i in range(len(points)): - j = 0 - while (x_sorted[i] != points[j][0]) and (y_sorted[i] != points[j][-1]): - j += 1 - else: - if len(points[0]) == 3: - sorted_points.append((x_sorted[i], points[j][1], y_sorted[i])) - else: - sorted_points.append((x_sorted[i], y_sorted[i])) - - return sorted_points - - def segments_intersection(line0, line1, full_line=True): """ Find (or not) intersection between two lines. Works in 2d but diff --git a/networks/roads/intersections/Intersection.py b/networks/roads/intersections/Intersection.py index 29b5ed9..f8ff073 100644 --- a/networks/roads/intersections/Intersection.py +++ b/networks/roads/intersections/Intersection.py @@ -1,5 +1,5 @@ from networks.geometry.segment_tools import parallel, orthogonal -from networks.geometry.point_tools import sort_by_clockwise, segments_intersection, curved_corner_by_distance, curved_corner_by_curvature +from networks.geometry.point_tools import segments_intersection, curved_corner_by_distance, curved_corner_by_curvature from networks.roads import Road From dfc277ddf24f337c3093be874a39cf5ce88cb855 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 19:34:28 +0200 Subject: [PATCH 46/69] Add perpendicular --- main.py | 3 +++ networks/geometry/Point2D.py | 2 +- networks/geometry/Segment2D.py | 33 +++++++++++++++++++++------ networks/geometry/Segment3D.py | 1 + networks/geometry/point_tools.py | 38 -------------------------------- 5 files changed, 31 insertions(+), 46 deletions(-) diff --git a/main.py b/main.py index b08ddef..029ec59 100644 --- a/main.py +++ b/main.py @@ -267,3 +267,6 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # print(polyline.alpha_radii) print(Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20)))) + +s = Segment3D(Point3D(0, 0, 0), Point3D(10, 10, 10)).perpendicular(10) +print(s) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 472332a..f5db5ff 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -183,7 +183,7 @@ class Point2D: return self @staticmethod - def to_vectors(points: List["Point3D"]): + def to_vectors(points: List["Point3D"]) -> List[np.array]: vectors = [] for point in points: vectors.append(np.array(point.coordinate)) diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 59d3015..3801777 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -1,19 +1,17 @@ -from typing import Type +from typing import List from networks.geometry.Enums import LINE_OVERLAP, LINE_THICKNESS_MODE from networks.geometry.Point2D import Point2D +from math import sqrt class Segment2D: - def __init__(self, start: Point2D, end: Point2D, thickness: int, thickness_mode: LINE_THICKNESS_MODE = LINE_THICKNESS_MODE.MIDDLE): + def __init__(self, start: Point2D, end: Point2D, thickness_mode: LINE_THICKNESS_MODE = LINE_THICKNESS_MODE.MIDDLE): self.start = start self.end = end self.coordinates = [] - self.thickness = thickness + self.thickness = None self.thickness_mode = thickness_mode - self.compute_thick_segment( - self.start, self.end, self.thickness, self.thickness_mode) - def __repr__(self): return str(self.coordinates) @@ -27,7 +25,7 @@ class Segment2D: end (Point2D): End point of the segment. overlap (LINE_OVERLAP): Overlap draws additional pixel when changing minor direction. For standard bresenham overlap, choose LINE_OVERLAP_NONE. Can also be LINE_OVERLAP_MAJOR or LINE_OVERLAP_MINOR. - >>> Segment2D(Point2D(0, 0), Point2D(10, 15), 1) + >>> Segment2D(Point2D(0, 0), Point2D(10, 15)) """ start = start.copy() end = end.copy() @@ -95,6 +93,8 @@ class Segment2D: end (Point2D): End point of the segment. thickness (int): Total width of the surface. Placement relative to the original segment depends on thickness_mode. thickness_mode (LINE_THICKNESS_MODE): Can be one of LINE_THICKNESS_MIDDLE, LINE_THICKNESS_DRAW_CLOCKWISE, LINE_THICKNESS_DRAW_COUNTERCLOCKWISE. + + >>> self.compute_thick_segment(self.start, self.end, self.thickness, self.thickness_mode) """ delta_y = end.x - start.x delta_x = end.y - start.y @@ -191,3 +191,22 @@ class Segment2D: error += delta_2x self.compute_segmen_overlap(start, end, overlap) + + def perpendicular(self, distance: int) -> List[Point2D]: + """Compute perpendicular points from both side of the segment placed at start level. + + Args: + distance (int): Distance bewteen the start point and the perpendicular. + + Returns: + List[Point2D]: Two points. First one positioned on the counterclockwise side of the segment, oriented from start to end (meaning left). + """ + delta = self.start.distance(self.end) + dx = (self.start.x - self.end.x) / delta + dy = (self.start.y - self.end.y) / delta + + x3 = self.start.x + (distance / 2) * dy + y3 = self.start.y - (distance / 2) * dx + x4 = self.start.x - (distance / 2) * dy + y4 = self.start.y + (distance / 2) * dx + return Point2D(x3, y3).round(), Point2D(x4, y4).round() diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index b90ad4d..c72b49f 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -1,3 +1,4 @@ +from typing import List from networks.geometry.Enums import LINE_OVERLAP from networks.geometry.Point3D import Point3D diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index d5c889d..1ca56bb 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -138,33 +138,6 @@ def circle_segment_intersection( return intersections -def perpendicular(distance, xy1, xy2): - """ - Return a tuple of the perpendicular coordinates. - - Args: - distance (int): Distance from the line[xy1;xy2]. - xy1 (tuple): First coordinates. - xy2 (tuple): Second coordinates. - - Returns: - tuple: Coordinates of the line length distance, perpendicular - to [xy1; xy2] at xy1. - """ - (x1, y1) = xy1[0], xy1[-1] - (x2, y2) = xy2[0], xy2[-1] - dx = x1 - x2 - dy = y1 - y2 - dist = sqrt(dx * dx + dy * dy) - dx /= dist - dy /= dist - x3 = x1 + (distance / 2) * dy - y3 = y1 - (distance / 2) * dx - x4 = x1 - (distance / 2) * dy - y4 = y1 + (distance / 2) * dx - return (x3, y3), (x4, y4) - - def curved_corner_by_distance( intersection, xyz0, xyz1, distance_from_intersection, resolution, full_line=True ): @@ -345,14 +318,3 @@ def curved_corner_by_curvature( distance_from_intersection = round(distance(start_curve_point, center)) return curve_corner_points, center, distance_from_intersection, parallel( (xyz0, intersection), -curvature_radius), parallel((xyz1, intersection), curvature_radius) - - -def coordinates_to_vectors(coordinates): - vectors = [] - for coordinate in coordinates: - vectors.append(np.array(coordinate.coordinate)) - - if (len(vectors) == 1): - return vectors[0] - else: - return vectors From 57f7e9140eaf216ede19533ff34ab154e642de4a Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 19:36:04 +0200 Subject: [PATCH 47/69] Add tests --- main.py | 2 +- networks/geometry/Segment2D.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 029ec59..1eefd64 100644 --- a/main.py +++ b/main.py @@ -268,5 +268,5 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", print(Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20)))) -s = Segment3D(Point3D(0, 0, 0), Point3D(10, 10, 10)).perpendicular(10) +s = Segment2D(Point2D(0, 0), Point2D(10, 10)).perpendicular(10) print(s) diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 3801777..fd35b7c 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -200,6 +200,9 @@ class Segment2D: Returns: List[Point2D]: Two points. First one positioned on the counterclockwise side of the segment, oriented from start to end (meaning left). + + >>> Segment2D(Point2D(0, 0), Point2D(10, 10)).perpendicular(10) + (Point2D(x: -4, y: 4), Point2D(x: 4, y: -4)) """ delta = self.start.distance(self.end) dx = (self.start.x - self.end.x) / delta From 7ad9b45f6cf546b8f230b99f4f7c6265fa15fa01 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Wed, 12 Jun 2024 16:14:09 +0200 Subject: [PATCH 48/69] Working radii --- main.py | 16 ++++++++-- networks/geometry/Polyline.py | 56 +++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/main.py b/main.py index 1eefd64..123274e 100644 --- a/main.py +++ b/main.py @@ -266,7 +266,17 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # polyline._alpha_assign(1, polyline.length_polyline-1) # print(polyline.alpha_radii) -print(Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20)))) +p = Polyline((Point2D(0, 0), Point2D(8, 0), Point2D( + 8, 8), Point2D(16, 16))) -s = Segment2D(Point2D(0, 0), Point2D(10, 10)).perpendicular(10) -print(s) +# print(p.alpha_radii) + +print(p.get_radius()) + +# s = Segment2D(Point2D(0, 0), Point2D(10, 10)).perpendicular(10) +# print(s) + +# Note: passer parrallel dans Segment2D pour pouvoir calculer l'intersection entre deux segments +# de la Polyline pour trouver le centre du cercle. Faire l'arc de cercle en utilise is_in_triangle +# Okay mb, l'article scientifique explique une procédure sans doute plus efficace. +# alpha n'est pas un angle. diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 393b154..2b6d6fd 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -31,6 +31,9 @@ class Polyline: self.alpha_radii = [None] * self.length_polyline + self.radii = [None] * self.length_polyline + self.centers = [None] * self.length_polyline + self._compute_requirements() self._compute_alpha_radii() @@ -39,6 +42,21 @@ class Polyline: def __repr__(self): return str(self.alpha_radii) + def get_radius(self): + for i in range(1, self.length_polyline-1): + self.radii[i] = self.alpha_radii[i] * self.tangente[i] + return self.radii + + def get_centers(self): + print(self.radii) + for i in range(1, self.length_polyline-2): + print(i) + bi = (self.unit_vectors[i] + self.unit_vectors[i-1]) / \ + np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1]) + self.centers[i] = self.points[i] + \ + sqrt(self.radii[i] ** 2 + self.alpha_radii[i] ** 2) * bi + return self.centers + def _alpha_assign(self, start_index: int, end_index: int): """ The alpha-assign procedure assigning radii based on a polyline. @@ -54,13 +72,26 @@ class Polyline: self.tangente[start_index + 1] * alpha_b) # Radis at initial segment if current_radius < minimum_radius: - minimum_radius, minimum_index = current_radius, start_index + minimum_radius, minimum_index = current_radius, start_index # 8, 0 + # 0, 8 alpha_low, alpha_high = self.alpha_radii[start_index], alpha_b - for i in range(start_index + 1, end_index - 2): # Radii for internal segments - alpha_a, alpha_b, current_radius = self._radius_balance(i) - if current_radius < minimum_radius: - alpha_low, alpha_high = alpha_a, self.alpha_radii[end_index] + for i in range(start_index + 1, end_index - 1): # Radii for internal segments + alpha_a, alpha_b, current_radius = self._radius_balance( + i) # i = 1 # 4, 4, 4, + if current_radius < minimum_radius: # 4 < 8 + minimum_radius, minimum_index = current_radius, i # 4, 1 + alpha_low, alpha_high = alpha_a, alpha_b # 4, 4 + + alpha_a = min(self.lengths[end_index-2], + self.lengths[end_index-1]-self.alpha_radii[end_index]) # 8 + + current_radius = max(self.tangente[end_index-1]*alpha_a, self.tangente[end_index] + * self.alpha_radii[end_index]) # Radius at final segment + + if current_radius < minimum_radius: + minimum_radius, minimum_index = current_radius, end_index - 1 + alpha_low, alpha_high = alpha_a, self.alpha_radii[end_index] # Assign alphas at ends of selected segment self.alpha_radii[minimum_index] = alpha_low @@ -79,7 +110,8 @@ class Polyline: alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / (self.tangente[i] + self.tangente[i+1])) alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) - + print(alpha_a, alpha_b, max( + self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b)) return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) def _compute_requirements(self): @@ -89,17 +121,15 @@ class Polyline: self.lengths[j] = np.linalg.norm(self.vectors[j]) self.unit_vectors[j] = self.vectors[j]/self.lengths[j] - # print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") - # Between two segments, there is only one angle for k in range(1, self.length_polyline-1): - cross = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) - self.tangente[k] = sqrt((1+cross)/(1-cross)) + dot = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) + self.tangente[k] = sqrt((1+dot)/(1-dot)) def _compute_alpha_radii(self): self.alpha_radii[0] = 0 self.alpha_radii[self.length_polyline-1] = 0 - for i in range(1, self.length_polyline-2): - self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] - * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) + # for i in range(1, self.length_polyline-2): + # self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] + # * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) From bbdd59ca9bf7b096e20713e6327b02d7397c8a67 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Wed, 12 Jun 2024 20:49:20 +0200 Subject: [PATCH 49/69] Fix 3d bresenham --- main.py | 40 +++++++++++++++++++++-- networks/geometry/Point3D.py | 1 + networks/geometry/Polyline.py | 59 +++++++++++++++------------------- networks/geometry/Segment3D.py | 26 +++++++-------- 4 files changed, 77 insertions(+), 49 deletions(-) diff --git a/main.py b/main.py index 123274e..77410ef 100644 --- a/main.py +++ b/main.py @@ -266,12 +266,46 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # # polyline._alpha_assign(1, polyline.length_polyline-1) # print(polyline.alpha_radii) -p = Polyline((Point2D(0, 0), Point2D(8, 0), Point2D( - 8, 8), Point2D(16, 16))) +# p = Polyline((Point2D(0, 0), Point2D(8, 0), Point2D( +# 16, 8), Point2D(24, 0))) +# p = Polyline((Point2D(-1420, 867), Point2D(-1362, 738), +# Point2D(-1157, 717), Point2D(-1099, 843))) + +# p = Polyline((Point2D(-1183, 528), Point2D(-1138, 481), +# Point2D(-1188, 451), Point2D(-1152, 416))) + +p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), + Point2D(-1188, 451), Point2D(-1152, 416))) + +# Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) # print(p.alpha_radii) -print(p.get_radius()) +radius = p.get_radii() +center = p.get_centers() + +print(radius) +print(center) +print(p.lengths) + +y = 280 + +for i in range(len(p.coordinates)-1): + if p.coordinates[i] != None: + s = Segment3D(Point3D(p.coordinates[i].x, y, p.coordinates[i].y), Point3D( + p.coordinates[i+1].x, y, p.coordinates[i+1].y)) + for j in range(len(s.coordinates)-1): + editor.placeBlock( + s.coordinates[j].coordinate, Block("cyan_concrete")) + + +for i in range(len(center)): + if center[i]: + circle = Circle(center[i], radius[i], radius[i]) + for j in range(len(circle.coordinates)-1): + editor.placeBlock( + (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) + # s = Segment2D(Point2D(0, 0), Point2D(10, 10)).perpendicular(10) # print(s) diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index e3aad33..b4988de 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -8,6 +8,7 @@ class Point3D: self.x = x self.y = y self.z = z + self.coordinate = (x, y, z) def copy(self): return Point3D(self.x, self.y, self.z) diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 2b6d6fd..c1831d7 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -18,21 +18,22 @@ class Polyline: >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ + self.coordinates = points self.points = Point2D.to_vectors(points) self.length_polyline = len(points) if self.length_polyline < 4: raise ValueError("The list must contain at least 4 elements.") - self.vectors = [None] * self.length_polyline - self.lengths = [None] * self.length_polyline - self.unit_vectors = [None] * self.length_polyline - self.tangente = [0] * self.length_polyline + self.vectors = [None] * self.length_polyline # v + self.lengths = [None] * self.length_polyline # l + self.unit_vectors = [None] * self.length_polyline # n + self.tangente = [0] * self.length_polyline # f - self.alpha_radii = [None] * self.length_polyline + self.alpha_radii = [None] * self.length_polyline # alpha - self.radii = [None] * self.length_polyline - self.centers = [None] * self.length_polyline + self.radii = [None] * self.length_polyline # r + self.centers = [None] * self.length_polyline # c self._compute_requirements() self._compute_alpha_radii() @@ -42,19 +43,19 @@ class Polyline: def __repr__(self): return str(self.alpha_radii) - def get_radius(self): + def get_radii(self): for i in range(1, self.length_polyline-1): - self.radii[i] = self.alpha_radii[i] * self.tangente[i] + self.radii[i] = round(self.alpha_radii[i] * self.tangente[i]) return self.radii def get_centers(self): - print(self.radii) - for i in range(1, self.length_polyline-2): - print(i) - bi = (self.unit_vectors[i] + self.unit_vectors[i-1]) / \ - np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1]) - self.centers[i] = self.points[i] + \ - sqrt(self.radii[i] ** 2 + self.alpha_radii[i] ** 2) * bi + for i in range(1, self.length_polyline-1): + bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / ( + np.linalg.norm(self.unit_vectors[i] + self.unit_vectors[i-1])) + + array = self.points[i] + sqrt(self.radii[i] + ** 2 + self.alpha_radii[i] ** 2) * bisector + self.centers[i] = Point2D(array[0], array[1]).round() return self.centers def _alpha_assign(self, start_index: int, end_index: int): @@ -69,22 +70,21 @@ class Polyline: alpha_b = min( self.lengths[start_index] - self.alpha_radii[start_index], self.lengths[start_index + 1]) current_radius = max(self.tangente[start_index] * self.alpha_radii[start_index], - self.tangente[start_index + 1] * alpha_b) # Radis at initial segment + self.tangente[start_index + 1] * alpha_b) # Radius at initial segment if current_radius < minimum_radius: - minimum_radius, minimum_index = current_radius, start_index # 8, 0 + minimum_radius, minimum_index = current_radius, start_index # 0, 8 alpha_low, alpha_high = self.alpha_radii[start_index], alpha_b for i in range(start_index + 1, end_index - 1): # Radii for internal segments - alpha_a, alpha_b, current_radius = self._radius_balance( - i) # i = 1 # 4, 4, 4, - if current_radius < minimum_radius: # 4 < 8 - minimum_radius, minimum_index = current_radius, i # 4, 1 - alpha_low, alpha_high = alpha_a, alpha_b # 4, 4 + alpha_a, alpha_b, current_radius = self._radius_balance(i) + if current_radius < minimum_radius: + minimum_radius, minimum_index = current_radius, i + alpha_low, alpha_high = alpha_a, alpha_b alpha_a = min(self.lengths[end_index-2], - self.lengths[end_index-1]-self.alpha_radii[end_index]) # 8 + self.lengths[end_index-1]-self.alpha_radii[end_index]) current_radius = max(self.tangente[end_index-1]*alpha_a, self.tangente[end_index] * self.alpha_radii[end_index]) # Radius at final segment @@ -106,13 +106,10 @@ class Polyline: """ Returns the radius that balances the radii on either end segement i. """ - alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / (self.tangente[i] + self.tangente[i+1])) alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) - print(alpha_a, alpha_b, max( - self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b)) - return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) + return alpha_a, alpha_b, min(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) def _compute_requirements(self): # Between two points, there is only one segment @@ -123,13 +120,9 @@ class Polyline: # Between two segments, there is only one angle for k in range(1, self.length_polyline-1): - dot = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) + dot = np.dot(self.unit_vectors[k], -self.unit_vectors[k-1]) self.tangente[k] = sqrt((1+dot)/(1-dot)) def _compute_alpha_radii(self): self.alpha_radii[0] = 0 self.alpha_radii[self.length_polyline-1] = 0 - - # for i in range(1, self.length_polyline-2): - # self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] - # * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index c72b49f..ff08f99 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -10,12 +10,12 @@ class Segment3D: self.coordinates = [] self.overlap = overlap - self.compute_segment(self.start, self.end, self.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 = True): + def _compute_segment(self, start: Point3D, end: Point3D, 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/ @@ -23,11 +23,11 @@ class Segment3D: Args: start (Point3D): First coordinates. end (Point3D): Second coordinates. - overlap (bool, optional): If true, remove unnecessary coordinates connecting to other coordinates side by side, leaving only a diagonal connection. Defaults to True. + 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) + self.coordinates.append(start.copy()) dx = abs(end.x - start.x) dy = abs(end.y - start.y) dz = abs(end.z - start.z) @@ -50,18 +50,18 @@ class Segment3D: p2 = 2 * dz - dx while start.x != end.x: start.x += xs - self.coordinates.append(start) + self.coordinates.append(start.copy()) if p1 >= 0: start.y += ys if not overlap: if self.coordinates[-1].y != start.y: - self.coordinates.append(start) + self.coordinates.append(start.copy()) p1 -= 2 * dx if p2 >= 0: start.z += zs if not overlap: if self.coordinates[-1].z != start.z: - self.coordinates.append(start) + self.coordinates.append(start.copy()) p2 -= 2 * dx p1 += 2 * dy p2 += 2 * dz @@ -72,18 +72,18 @@ class Segment3D: p2 = 2 * dz - dy while start.y != end.y: start.y += ys - self.coordinates.append(start) + self.coordinates.append(start.copy()) if p1 >= 0: start.x += xs if not overlap: if self.coordinates[-1].x != start.x: - self.coordinates.append(start) + self.coordinates.append(start.copy()) p1 -= 2 * dy if p2 >= 0: start.z += zs if not overlap: if self.coordinates[-1].z != start.z: - self.coordinates.append(start) + self.coordinates.append(start.copy()) p2 -= 2 * dy p1 += 2 * dx p2 += 2 * dz @@ -94,18 +94,18 @@ class Segment3D: p2 = 2 * dx - dz while start.z != end.z: start.z += zs - self.coordinates.append(start) + self.coordinates.append(start.copy()) if p1 >= 0: start.y += ys if not overlap: if self.coordinates[-1].y != start.y: - self.coordinates.append(start) + self.coordinates.append(start.copy()) p1 -= 2 * dz if p2 >= 0: start.x += xs if not overlap: if self.coordinates[-1].x != start.x: - self.coordinates.append(start) + self.coordinates.append(start.copy()) p2 -= 2 * dz p1 += 2 * dy p2 += 2 * dx From a84a57d90140700c3195d467d57f3a0b53913dda Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Wed, 12 Jun 2024 21:38:22 +0200 Subject: [PATCH 50/69] Radii in polylines still to big --- main.py | 9 ++++++--- networks/geometry/Polyline.py | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 77410ef..5b090e4 100644 --- a/main.py +++ b/main.py @@ -275,8 +275,11 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # p = Polyline((Point2D(-1183, 528), Point2D(-1138, 481), # Point2D(-1188, 451), Point2D(-1152, 416))) -p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), - Point2D(-1188, 451), Point2D(-1152, 416))) +# p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), +# Point2D(-1188, 451), Point2D(-1176, 409), Point2D(-1179, 399))) + +p = Polyline((Point2D(-1206, 414), Point2D(-1176, 409), + Point2D(-1188, 451), Point2D(-1138, 481), Point2D(-1225, 500))) # Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) # print(p.alpha_radii) @@ -288,7 +291,7 @@ print(radius) print(center) print(p.lengths) -y = 280 +y = 160 for i in range(len(p.coordinates)-1): if p.coordinates[i] != None: diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index c1831d7..9da658b 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -45,6 +45,8 @@ class Polyline: def get_radii(self): for i in range(1, self.length_polyline-1): + print(self.alpha_radii[i] * self.tangente[i], + self.alpha_radii[i], self.tangente[i]) self.radii[i] = round(self.alpha_radii[i] * self.tangente[i]) return self.radii @@ -79,6 +81,7 @@ class Polyline: for i in range(start_index + 1, end_index - 1): # Radii for internal segments alpha_a, alpha_b, current_radius = self._radius_balance(i) + if current_radius < minimum_radius: minimum_radius, minimum_index = current_radius, i alpha_low, alpha_high = alpha_a, alpha_b @@ -106,6 +109,7 @@ class Polyline: """ Returns the radius that balances the radii on either end segement i. """ + alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / (self.tangente[i] + self.tangente[i+1])) alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) From 490dc25a942fcb0653b636d5649e961c60a1141f Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Wed, 12 Jun 2024 22:01:49 +0200 Subject: [PATCH 51/69] Add image visualisation --- main.py | 37 ++++++++++++++++++++++++++++--------- output_image.png | Bin 0 -> 534 bytes 2 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 output_image.png diff --git a/main.py b/main.py index 5b090e4..6915220 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,6 @@ + +from PIL import Image, ImageDraw +import matplotlib.pyplot as plt from networks.geometry.Point3D import Point3D from networks.geometry.Segment3D import Segment3D from networks.geometry.Segment2D import Segment2D @@ -21,6 +24,11 @@ from networks.roads.intersections import Intersection as Intersection from networks.geometry.point_tools import curved_corner_by_curvature, curved_corner_by_distance + +import matplotlib +matplotlib.use('Agg') + + editor = Editor(buffering=True) # f = open('buildings\shapes.json') @@ -278,8 +286,8 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), # Point2D(-1188, 451), Point2D(-1176, 409), Point2D(-1179, 399))) -p = Polyline((Point2D(-1206, 414), Point2D(-1176, 409), - Point2D(-1188, 451), Point2D(-1138, 481), Point2D(-1225, 500))) +p = Polyline((Point2D(32, 0), Point2D(16, 16), + Point2D(16, 0), Point2D(0, 0))) # Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) # print(p.alpha_radii) @@ -293,14 +301,11 @@ print(p.lengths) y = 160 -for i in range(len(p.coordinates)-1): - if p.coordinates[i] != None: - s = Segment3D(Point3D(p.coordinates[i].x, y, p.coordinates[i].y), Point3D( - p.coordinates[i+1].x, y, p.coordinates[i+1].y)) - for j in range(len(s.coordinates)-1): - editor.placeBlock( - s.coordinates[j].coordinate, Block("cyan_concrete")) +width, height = 100, 100 +image = Image.new('RGB', (width, height), 'white') + +draw = ImageDraw.Draw(image) for i in range(len(center)): if center[i]: @@ -308,7 +313,21 @@ for i in range(len(center)): for j in range(len(circle.coordinates)-1): editor.placeBlock( (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) + draw.point((circle.coordinates[j].x+25, + 50-circle.coordinates[j].y), fill='black') +for i in range(len(p.coordinates)-1): + if p.coordinates[i] != None: + s = Segment3D(Point3D(p.coordinates[i].x, y, p.coordinates[i].y), Point3D( + p.coordinates[i+1].x, y, p.coordinates[i+1].y)) + for j in range(len(s.coordinates)-1): + editor.placeBlock( + s.coordinates[j].coordinate, Block("cyan_concrete")) + draw.point((s.coordinates[j].x+25, + 50-s.coordinates[j].z), fill='red') + + +image.save('output_image.png') # s = Segment2D(Point2D(0, 0), Point2D(10, 10)).perpendicular(10) # print(s) diff --git a/output_image.png b/output_image.png new file mode 100644 index 0000000000000000000000000000000000000000..2e233fbab52de977523d68cad54649444a49c424 GIT binary patch literal 534 zcmeAS@N?(olHy`uVBq!ia0vp^DIm94=ygKXt53(bb6s70&*A<1_*Z9=jl_%3S*G}Uqr6vRXsZ1d`PCQdGK{ais(W$on}68CvZS@|+wc-$95IimpJ}be|_k0K*{fox0e21cp)fZtM_;3Le*Vs?_8>PQx?A# sc+3BP$`P;VX~kik#D_Cd)YLMr?Yp#vd7AA_V0<%py85}Sb4q9e03UtsQUCw| literal 0 HcmV?d00001 From aec9db1d0d9d84edfed44d7fd73c2f55f2d8a024 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Wed, 12 Jun 2024 22:38:14 +0200 Subject: [PATCH 52/69] Probably working! --- main.py | 4 ++-- networks/geometry/Polyline.py | 13 +++++++------ output_image.png | Bin 534 -> 857 bytes 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 6915220..4227684 100644 --- a/main.py +++ b/main.py @@ -286,8 +286,8 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), # Point2D(-1188, 451), Point2D(-1176, 409), Point2D(-1179, 399))) -p = Polyline((Point2D(32, 0), Point2D(16, 16), - Point2D(16, 0), Point2D(0, 0))) +p = Polyline((Point2D(64, -20), Point2D(48, 32), + Point2D(16, 0), Point2D(0, 0), Point2D(-10, -10))) # Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) # print(p.alpha_radii) diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 9da658b..5cca24b 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -26,7 +26,7 @@ class Polyline: raise ValueError("The list must contain at least 4 elements.") self.vectors = [None] * self.length_polyline # v - self.lengths = [None] * self.length_polyline # l + self.lengths = [None] * (self.length_polyline - 1) # l self.unit_vectors = [None] * self.length_polyline # n self.tangente = [0] * self.length_polyline # f @@ -45,7 +45,7 @@ class Polyline: def get_radii(self): for i in range(1, self.length_polyline-1): - print(self.alpha_radii[i] * self.tangente[i], + print("\nalpha_radii, tan", self.alpha_radii[i] * self.tangente[i], self.alpha_radii[i], self.tangente[i]) self.radii[i] = round(self.alpha_radii[i] * self.tangente[i]) return self.radii @@ -53,10 +53,11 @@ class Polyline: def get_centers(self): for i in range(1, self.length_polyline-1): bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / ( - np.linalg.norm(self.unit_vectors[i] + self.unit_vectors[i-1])) + np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1])) + print("bi", bisector) - array = self.points[i] + sqrt(self.radii[i] - ** 2 + self.alpha_radii[i] ** 2) * bisector + array = self.points[i] + sqrt((self.radii[i] + ** 2) + (self.alpha_radii[i] ** 2)) * bisector self.centers[i] = Point2D(array[0], array[1]).round() return self.centers @@ -124,7 +125,7 @@ class Polyline: # Between two segments, there is only one angle for k in range(1, self.length_polyline-1): - dot = np.dot(self.unit_vectors[k], -self.unit_vectors[k-1]) + dot = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) self.tangente[k] = sqrt((1+dot)/(1-dot)) def _compute_alpha_radii(self): diff --git a/output_image.png b/output_image.png index 2e233fbab52de977523d68cad54649444a49c424..ea3938969a69ad4732f3afc8cc44066c1daddb5d 100644 GIT binary patch delta 835 zcmV-J1HAl}1lb0VBYy)RNkl_o`|C|Lj{3!r2Hlq`Uf1yHg8N)|xL0w`GkKThMS>brmKyBa0S zIvss5GV++5DC>;70%$bDA#35tQnimWb8IlPCrU}jE)koO#eWS&{)pL22l{Jkt$nzM z!l(*yG!Ze&a!LWsm#VZTt#N(bu_*1P9I(i8y|~PV%gvM+WCdJ??Y^E(QD@?frlDu1 zI+(1myE_~%e^YXkWQCZLJIMNKig(!pWCd5)Lo+cdQ=OfY6>Ca1mIaw2&BQ%rg=ea> zp{zxwWItJEQ-6$^xRtDRnd(ob-elJC%Rf=?rhq| za`dGt$8DF@TJSiObtWd8hW(XM--$LC(ksT&!KLOpMt@AZeQU3?_`B_`U)KG1B(0_V zR8iWWo}f%slkZ51$b1FgT4U`YC*5Sbz*+9tBBs63WyxyBG!x0mx7FG4omtN-grTEo zXKBlJK#BVfJk+E*>r!*~KislgnIQ|Txt~{XOHh#phHLr)f0*jGMd;AA$ud0qT(TIe zlSW+$x_>f}ELA<~tjUug6l*5>J3an*tE|J6@$h4e>j?3_(DOIRVwif#l6;MW%gVHa zFb^~naaqn~Wae<)t6SNb^!tVZ3@@th(ybgqB@3Wr0j%s+{yf(LX5#NF8oO*&vH(gJ zz+zbo?|P3{m4TU9$pR=@0PAI~`6TP*o&DRret*3zS=g-#l0w!lWa(CZ9^7diIx}pd zWQCUOCKAaKe_xYP#bgD%`Dc%bQxnDvxsug68yhL35!J)8!mccoC$epoza1@GxAMct zwl0z^eel6R-vjXO&&l=kb0cH!^xd652dDonH7+T-l{qcX{?xBY0@Ux60;K3x-UY{U z={()PwsUl{&GjM!8P|qsDOms|3!r2Hlq`Uf1yHg8N)|xL0w`Gk{{bb?Fs4TkR$~AF N002ovPDHLkV1jUjo3;P| literal 534 zcmeAS@N?(olHy`uVBq!ia0vp^DIm94=ygKXt53(bb6s70&*A<1_*Z9=jl_%3S*G}Uqr6vRXsZ1d`PCQdGK{ais(W$on}68CvZS@|+wc-$95IimpJ}be|_k0K*{fox0e21cp)fZtM_;3Le*Vs?_8>PQx?A# sc+3BP$`P;VX~kik#D_Cd)YLMr?Yp#vd7AA_V0<%py85}Sb4q9e03UtsQUCw| From 48db1202aab940823b45c1783062b3f6430edbc4 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Wed, 12 Jun 2024 23:01:33 +0200 Subject: [PATCH 53/69] Add random test generator --- main.py | 23 +++++++++++++++-------- output_image.png | Bin 857 -> 40117 bytes 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 4227684..a626f58 100644 --- a/main.py +++ b/main.py @@ -286,8 +286,15 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), # Point2D(-1188, 451), Point2D(-1176, 409), Point2D(-1179, 399))) -p = Polyline((Point2D(64, -20), Point2D(48, 32), - Point2D(16, 0), Point2D(0, 0), Point2D(-10, -10))) +w = 1000 + +n_points = 10 +min_val, max_val = -w, w + +random_points = [Point2D(random.randint(min_val, max_val), random.randint( + min_val, max_val)) for _ in range(n_points)] + +p = Polyline(random_points) # Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) # print(p.alpha_radii) @@ -302,19 +309,19 @@ print(p.lengths) y = 160 -width, height = 100, 100 +width, height = 2*w, 2*w image = Image.new('RGB', (width, height), 'white') draw = ImageDraw.Draw(image) for i in range(len(center)): if center[i]: - circle = Circle(center[i], radius[i], radius[i]) + circle = Circle(center[i], radius[i], radius[i]+1) for j in range(len(circle.coordinates)-1): editor.placeBlock( (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.coordinates[j].x+25, - 50-circle.coordinates[j].y), fill='black') + draw.point((circle.coordinates[j].x+w, + w-circle.coordinates[j].y), fill='black') for i in range(len(p.coordinates)-1): if p.coordinates[i] != None: @@ -323,8 +330,8 @@ for i in range(len(p.coordinates)-1): for j in range(len(s.coordinates)-1): editor.placeBlock( s.coordinates[j].coordinate, Block("cyan_concrete")) - draw.point((s.coordinates[j].x+25, - 50-s.coordinates[j].z), fill='red') + draw.point((s.coordinates[j].x+w, + w-s.coordinates[j].z), fill='red') image.save('output_image.png') diff --git a/output_image.png b/output_image.png index ea3938969a69ad4732f3afc8cc44066c1daddb5d..9a70f46ace7c1b83d9cb8ad9cd0df772042de339 100644 GIT binary patch literal 40117 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
  • eJ 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

    sFy+t^U0 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 BoD2W} literal 857 zcmV-f1E&0mP)_o`|C|Lj{3!r2H zlq`Uf1yHg8N)|xL0w`GkKThMS>brmKyBa0SIvss5GV++5DC>;70%$bDA#35tQnimW zb8IlPCrU}jE)koO#SKONh}laA`fF>geYl6hs0wj35i!ehN&(H6sk zu*h<~xXgyj&6F5q1zd*hzMf4{XX1^fp=YK#n5?k7I~*>5Q*x7Jg_x2%$oguEci94D z1y|TZGchVtot={vYf3hj1(_nv#64w&XR5QItVO0|KUro|jG4HVtaX{{>?3QnDcMFA z#nkjn1Z0sv$^6D|4V)lTo%G2{V@mvXq&d7YwQ#Q+Uhc)L5LLB_TussTx+kKVG+C!9 zXs`c#j8lgIEURDUeZzIU`Q|6OT4U~P+QxG9r7Fj5m(^PEIFxlJCYy%+l~Lb`HW$(> z#?ry1<~l}9yM1e~v-rF1tzXvtcO>XipYEg-&$ksAt&8ryTDoQ z*dnIA(PhbM#xxVj$+y+n@ts-ED}J3an*tE|J6 z@$h4e>j?3_(DOIRVwif#l6;MW%gVHaFb^~naaqn~Wae<)t6SNb^!tVZ3@@th(ybgq zB@3Wr0j%s+{yf(LX5#NF8oO*&vH(gJz+zbo?|P3{m4TU9$pR=@0PAI~`6TP*o&DRr ze!VPN*sThZLe?&1=~jLo+-V&;Gi;({g_i6l63G&OUz1VAWCgtWXOD?f6UGd=lGQmI z8!4j^)x)yFt}K)%vTc>W9W7h8^25lsE|M&L@WDXe1Mu$8$@TMdBV+FL-JL%Nr~fTA zE-AW|IW5or)UQbb)bEr6r07=O1;=vf-M_YTbh6F$A_E!MhG{8T03{2cWC4^cfRY7J jvH(gJK*<6qSpfe5CD1UYM-WzH00000NkvXXu0mjfSO%Mi From 143a5742350b5ebe5bc9503ecc84bdb12eb1328a Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 13 Jun 2024 00:01:11 +0200 Subject: [PATCH 54/69] Fix thick bresenham --- main.py | 44 +++++++++++++++++++-------------- networks/geometry/Polyline.py | 4 +-- networks/geometry/Segment2D.py | 20 +++++++++------ output_image.png | Bin 40117 -> 5757 bytes 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/main.py b/main.py index a626f58..6ef1751 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ +from networks.geometry.Enums import LINE_OVERLAP, LINE_THICKNESS_MODE from PIL import Image, ImageDraw import matplotlib.pyplot as plt from networks.geometry.Point3D import Point3D @@ -286,14 +287,18 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), # Point2D(-1188, 451), Point2D(-1176, 409), Point2D(-1179, 399))) -w = 1000 +w = 250 -n_points = 10 +n_points = 5 min_val, max_val = -w, w random_points = [Point2D(random.randint(min_val, max_val), random.randint( min_val, max_val)) for _ in range(n_points)] + +# random_points = (Point2D(-75, -75), Point2D(0, -75), Point2D(75, 75), +# Point2D(75, -50), Point2D(-50, 50), Point2D(0, 0)) + p = Polyline(random_points) # Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) @@ -301,37 +306,40 @@ p = Polyline(random_points) radius = p.get_radii() center = p.get_centers() - print(radius) print(center) print(p.lengths) -y = 160 +y = 200 +ww = 40 width, height = 2*w, 2*w image = Image.new('RGB', (width, height), 'white') draw = ImageDraw.Draw(image) -for i in range(len(center)): - if center[i]: - circle = Circle(center[i], radius[i], radius[i]+1) - for j in range(len(circle.coordinates)-1): - editor.placeBlock( - (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.coordinates[j].x+w, - w-circle.coordinates[j].y), fill='black') - for i in range(len(p.coordinates)-1): if p.coordinates[i] != None: - s = Segment3D(Point3D(p.coordinates[i].x, y, p.coordinates[i].y), Point3D( - p.coordinates[i+1].x, y, p.coordinates[i+1].y)) + 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) + print(s.coordinates) for j in range(len(s.coordinates)-1): - editor.placeBlock( - s.coordinates[j].coordinate, Block("cyan_concrete")) + # editor.placeBlock( + # s.coordinates[j].coordinate, Block("cyan_concrete")) draw.point((s.coordinates[j].x+w, - w-s.coordinates[j].z), fill='red') + w-s.coordinates[j].y), fill='red') + + +for i in range(len(center)): + if center[i]: + circle = Circle(center[i], radius[i]-ww/2+1, radius[i]+ww/2+1) + for j in range(len(circle.coordinates)-1): + # editor.placeBlock( + # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) + draw.point((circle.coordinates[j].x+w, + w-circle.coordinates[j].y), fill='black') image.save('output_image.png') diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 5cca24b..145fb32 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -98,8 +98,8 @@ class Polyline: alpha_low, alpha_high = alpha_a, self.alpha_radii[end_index] # Assign alphas at ends of selected segment - self.alpha_radii[minimum_index] = alpha_low - self.alpha_radii[minimum_index+1] = alpha_high + self.alpha_radii[minimum_index] = alpha_low/1.5 + self.alpha_radii[minimum_index+1] = alpha_high/1.5 # Recur on lower segments self._alpha_assign(start_index, minimum_index) diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index fd35b7c..2038335 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -27,6 +27,7 @@ class Segment2D: >>> Segment2D(Point2D(0, 0), Point2D(10, 15)) """ + start = start.copy() end = end.copy() @@ -62,10 +63,10 @@ class Segment2D: start.y += step_y if (overlap == LINE_OVERLAP.MINOR): self.coordinates.append( - Point2D(start.x - step_x, start.y)) + Point2D(start.copy().x - step_x, start.copy().y)) error -= delta_2x error += delta_2y - self.coordinates.append(start) + self.coordinates.append(start.copy()) else: error = delta_2x - delta_y while (start.y != end.y): @@ -77,12 +78,12 @@ class Segment2D: start.x += step_x if (overlap == LINE_OVERLAP.MINOR): self.coordinates.append( - Point2D(start.x, start.y - step_y)) + Point2D(start.copy().x, start.copy().y - step_y)) error -= delta_2y error += delta_2x self.coordinates.append(start.copy()) - def compute_thick_segment(self, start: Point2D, end: Point2D, thickness: int, thickness_mode: LINE_THICKNESS_MODE): + def compute_thick_segment(self, thickness: int, thickness_mode: LINE_THICKNESS_MODE): """Bresenham with thickness. From https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp @@ -96,11 +97,14 @@ class Segment2D: >>> self.compute_thick_segment(self.start, self.end, self.thickness, self.thickness_mode) """ + start = self.start.copy() + end = self.end.copy() + delta_y = end.x - start.x delta_x = end.y - start.y swap = True - if delta_x < 0: + if (delta_x < 0): delta_x = -delta_x step_x = -1 swap = not swap @@ -156,7 +160,7 @@ class Segment2D: overlap = LINE_OVERLAP.MAJOR error += delta_2y - self.compute_segmen_overlap(start, end, overlap) + self.compute_segment_overlap(start, end, overlap) else: if swap: @@ -176,7 +180,7 @@ class Segment2D: error -= delta_2y error += delta_2x - self.compute_segmen_overlap(start, end, LINE_OVERLAP.NONE) + self.compute_segment_overlap(start, end, LINE_OVERLAP.NONE) error = delta_2x - delta_y for i in range(thickness, 1, -1): @@ -190,7 +194,7 @@ class Segment2D: overlap = LINE_OVERLAP.MAJOR error += delta_2x - self.compute_segmen_overlap(start, end, overlap) + self.compute_segment_overlap(start, end, overlap) def perpendicular(self, distance: int) -> List[Point2D]: """Compute perpendicular points from both side of the segment placed at start level. diff --git a/output_image.png b/output_image.png index 9a70f46ace7c1b83d9cb8ad9cd0df772042de339..df9422f8fe707286e718084b7324378f49052d62 100644 GIT binary patch literal 5757 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

  • eJ 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

    sFy+t^U0 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 BoD2W} From d76f4aefa9d19b9a3169815c49f6a0529437d6a7 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 13 Jun 2024 15:52:16 +0200 Subject: [PATCH 55/69] Merge Enums to general file --- Enums.py | 23 +++++++++++++++++++++-- networks/geometry/Enums.py | 18 ------------------ networks/geometry/Point2D.py | 2 +- networks/geometry/Segment2D.py | 2 +- networks/geometry/Segment3D.py | 2 +- 5 files changed, 24 insertions(+), 23 deletions(-) delete mode 100644 networks/geometry/Enums.py diff --git a/Enums.py b/Enums.py index 33c7487..3dbf24e 100644 --- a/Enums.py +++ b/Enums.py @@ -1,12 +1,31 @@ from enum import Enum + class DIRECTION(Enum): WEST = 0 EAST = 1 NORTH = 2 SOUTH = 3 - + + class COLLUMN_STYLE(Enum): INNER = 1 OUTER = 2 - BOTH = 3 \ No newline at end of file + BOTH = 3 + + +class LINE_OVERLAP(Enum): + NONE = 0 + MAJOR = 1 + MINOR = 2 + + +class LINE_THICKNESS_MODE(Enum): + MIDDLE = 0 + DRAW_COUNTERCLOCKWISE = 1 + DRAW_CLOCKWISE = 2 + + +class ROTATION(Enum): + CLOCKWISE = 0 + COUNTERCLOCKWISE = 1 diff --git a/networks/geometry/Enums.py b/networks/geometry/Enums.py deleted file mode 100644 index 1def6ff..0000000 --- a/networks/geometry/Enums.py +++ /dev/null @@ -1,18 +0,0 @@ -from enum import Enum - - -class LINE_OVERLAP(Enum): - NONE = 0 - MAJOR = 1 - MINOR = 2 - - -class LINE_THICKNESS_MODE(Enum): - MIDDLE = 0 - DRAW_COUNTERCLOCKWISE = 1 - DRAW_CLOCKWISE = 2 - - -class ROTATION(Enum): - CLOCKWISE = 0 - COUNTERCLOCKWISE = 1 diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index f5db5ff..a03ad31 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -1,7 +1,7 @@ import numpy as np from typing import List from math import atan2, sqrt -from networks.geometry.Enums import ROTATION +from Enums import ROTATION class Point2D: diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 2038335..a1d9c48 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -1,5 +1,5 @@ from typing import List -from networks.geometry.Enums import LINE_OVERLAP, LINE_THICKNESS_MODE +from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE from networks.geometry.Point2D import Point2D from math import sqrt diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index ff08f99..9322146 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -1,5 +1,5 @@ from typing import List -from networks.geometry.Enums import LINE_OVERLAP +from Enums import LINE_OVERLAP from networks.geometry.Point3D import Point3D From f82d02cd06c174c8b1f3b18e08db83b7c62dc91e Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 13 Jun 2024 17:27:50 +0200 Subject: [PATCH 56/69] Clean up --- main.py | 2 +- networks/geometry/Circle.py | 70 ++++++++++++----- networks/geometry/Point2D.py | 6 +- networks/geometry/Point3D.py | 11 +-- networks/geometry/Segment2D.py | 64 ++++++++++------ networks/geometry/Segment3D.py | 22 +++--- networks/geometry/segment_tools.py | 118 ----------------------------- 7 files changed, 115 insertions(+), 178 deletions(-) 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), - ) From 32485d86bc85cc4060c7abdf2dfd9e49449c4887 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 13 Jun 2024 18:34:10 +0200 Subject: [PATCH 57/69] Everything cleaned and tested --- main.py | 33 +++++++++++-------- networks/geometry/Circle.py | 32 +++++++++--------- networks/geometry/Point2D.py | 17 +++++++--- networks/geometry/Point3D.py | 8 ++--- networks/geometry/Polyline.py | 30 +++++++++++++++-- networks/geometry/Segment2D.py | 55 +++++++++++++++++-------------- networks/geometry/Segment3D.py | 42 +++++++++++------------ networks/geometry/point_tools.py | 1 - output_image.png | Bin 5757 -> 4384 bytes 9 files changed, 130 insertions(+), 88 deletions(-) diff --git a/main.py b/main.py index 190bd5d..6ea285c 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ -from networks.geometry.Enums import LINE_OVERLAP, LINE_THICKNESS_MODE +from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE, ROTATION from PIL import Image, ImageDraw import matplotlib.pyplot as plt from networks.geometry.Point3D import Point3D @@ -20,7 +20,6 @@ from buildings.Building import Building import random from networks.roads import Road as Road -from networks.geometry.Enums import ROTATION from networks.roads.intersections import Intersection as Intersection from networks.geometry.point_tools import curved_corner_by_curvature, curved_corner_by_distance @@ -319,27 +318,33 @@ image = Image.new('RGB', (width, height), 'white') draw = ImageDraw.Draw(image) -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)) +for i in range(len(p.output_points)-1): + print("iiii", i) + if p.output_points[i] != None: + s = Segment2D(Point2D(p.output_points[i].x, p.output_points[i].y), Point2D( + p.output_points[i+1].x, p.output_points[i+1].y)) s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) - print(s.coordinates) - for j in range(len(s.coordinates)-1): + + for j in range(len(s.points_thick)-1): + print("j", j) # editor.placeBlock( # s.coordinates[j].coordinate, Block("cyan_concrete")) - draw.point((s.coordinates[j].x+w, - w-s.coordinates[j].y), fill='red') + draw.point((s.points_thick[j].x+w, + w-s.points_thick[j].y), fill='red') + print(s.points_thick[j]) for i in range(len(center)): + print("iiii", i) if center[i]: - circle = Circle(center[i], radius[i]-ww/2+1, radius[i]+ww/2+1) - for j in range(len(circle.coordinates)-1): + circle = Circle(center[i]) + circle.circle_thick(radius[i]-ww/2+1, radius[i]+ww/2+1) + for j in range(len(circle.points_thick)-1): # editor.placeBlock( # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.coordinates[j].x+w, - w-circle.coordinates[j].y), fill='black') + draw.point((circle.points_thick[j].x+w, + w-circle.points_thick[j].y), fill='black') + print(circle.points_thick[j]) image.save('output_image.png') diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index 1c17a7a..c6f78a2 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -8,14 +8,14 @@ class Circle: self.center = center self.radius = None - self.coordinates = [] + self.points = [] self.inner = None self.outer = None - self.coordinates_thick = [] + self.points_thick = [] self.spaced_radius = None - self.spaced_coordinates = [] + self.spaced_points = [] def __repr__(self): return f"Circle(center: {self.center}, radius: {self.radius}, spaced_radius: {self.spaced_radius}, inner: {self.inner}, outer: {self.outer})" @@ -28,10 +28,10 @@ class Circle: 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)) + self.points.append(Point2D(center.x-x, center.y+y)) + self.points.append(Point2D(center.x-y, center.y-x)) + self.points.append(Point2D(center.x+x, center.y-y)) + self.points.append(Point2D(center.x+y, center.y+x)) r = error if (r <= y): y += 1 @@ -96,7 +96,7 @@ class Circle: else: xi -= 1 erri += 2 * (y - xi + 1) - return self.coordinates_thick + return self.points_thick def circle_spaced(self, number: int, radius: int) -> List[Point2D]: """Get evenly spaced coordinates of the circle. @@ -113,25 +113,25 @@ class Circle: self.spaced_radius = radius center = self.center - self.spaced_coordinates = [ + self.spaced_points = [ Point2D(cos(2 * pi / number * i) * radius, sin(2 * pi / number * i) * radius) for i in range(0, number + 1) ] - for i in range(len(self.spaced_coordinates)): - self.spaced_coordinates[i] = Point2D( - self.spaced_coordinates[i].x + center.x, - self.spaced_coordinates[i].y + center.y + for i in range(len(self.spaced_points)): + self.spaced_points[i] = Point2D( + self.spaced_points[i].x + center.x, + self.spaced_points[i].y + center.y ).round() - return self.spaced_coordinates + return self.spaced_points def _x_line(self, x1, x2, y): while x1 <= x2: - self.coordinates_thick.append(Point2D(x1, y)) + self.points_thick.append(Point2D(x1, y)) x1 += 1 def _y_line(self, x, y1, y2): while y1 <= y2: - self.coordinates_thick.append(Point2D(x, y1)) + self.points_thick.append(Point2D(x, y1)) y1 += 1 diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 4d2b49a..b2bedff 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -8,7 +8,7 @@ class Point2D: def __init__(self, x: int, y: int): self.x = x self.y = y - self.coordinate = (self.x, self.y) + self.coordinates = (self.x, self.y) def copy(self): return Point2D(self.x, self.y) @@ -167,8 +167,8 @@ class Point2D: """ if xy2 is None: xy2 = xy1.coordinate + np.array([1, 0]) - v0 = np.array(xy1.coordinate) - np.array(self.coordinate) - v1 = np.array(xy2.coordinate) - np.array(self.coordinate) + v0 = np.array(xy1.coordinate) - np.array(self.coordinates) + v1 = np.array(xy2.coordinate) - np.array(self.coordinates) angle = atan2(np.linalg.det([v0, v1]), np.dot(v0, v1)) return np.degrees(angle) @@ -176,17 +176,24 @@ class Point2D: def round(self, ndigits: int = None) -> "Point2D": self.x = round(self.x, ndigits) self.y = round(self.y, ndigits) - self.coordinate = (self.x, self.y) + self.coordinates = (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 collinear(p0: "Point2D", p1: "Point2D", p2: "Point2D") -> bool: + # https://stackoverflow.com/questions/9608148/python-script-to-determine-if-x-y-coordinates-are-colinear-getting-some-e + x1, y1 = p1.x - p0.x, p1.y - p0.y + x2, y2 = p2.x - p0.x, p2.y - p0.y + return abs(x1 * y2 - x2 * y1) < 1e-12 + @staticmethod def to_vectors(points: List["Point3D"]) -> List[np.array]: vectors = [] for point in points: - vectors.append(np.array(point.coordinate)) + vectors.append(np.array(point.coordinates)) if (len(vectors) == 1): return vectors[0] diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index 03474de..7873612 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -8,7 +8,7 @@ class Point3D: self.x = x self.y = y self.z = z - self.coordinate = (x, y, z) + self.coordinates = (x, y, z) def copy(self): return Point3D(self.x, self.y, self.z) @@ -29,7 +29,7 @@ class Point3D: Returns: Point3D: The nearest point, and if multiple, the first in the list. - >>> print(Point3D(0, 0, 0).nearest((Point3D(-10, 10, 5), Point3D(10, 10, 1)))) + >>> Point3D(0, 0, 0).nearest((Point3D(-10, 10, 5), Point3D(10, 10, 1))) Point3D(x: 10, y: 10, z: 1) """ return min(points, key=lambda point: self.distance(point)) @@ -64,7 +64,7 @@ class Point3D: self.x = round(self.x, ndigits) self.y = round(self.y, ndigits) self.z = round(self.z, ndigits) - self.coordinate = (self.x, self.y, self.z) + self.coordinates = (self.x, self.y, self.z) return self def distance(self, point: "Point3D"): @@ -74,7 +74,7 @@ class Point3D: def to_vectors(points: List["Point3D"]): vectors = [] for point in points: - vectors.append(np.array(point.coordinate)) + vectors.append(np.array(point.coordinates)) if (len(vectors) == 1): return vectors[0] diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 145fb32..6988140 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -18,8 +18,7 @@ class Polyline: >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ - self.coordinates = points - self.points = Point2D.to_vectors(points) + self.points = Point2D.to_vectors(self._remove_collinear_points(points)) self.length_polyline = len(points) if self.length_polyline < 4: @@ -34,12 +33,15 @@ class Polyline: self.radii = [None] * self.length_polyline # r self.centers = [None] * self.length_polyline # c + self.connections = [None] * self.length_polyline self._compute_requirements() self._compute_alpha_radii() self._alpha_assign(0, self.length_polyline-1) + self.output_points = points + def __repr__(self): return str(self.alpha_radii) @@ -51,6 +53,9 @@ class Polyline: return self.radii def get_centers(self): + if self.radii == [None] * self.length_polyline: + raise ValueError("No radii found. Run get_radii before.") + for i in range(1, self.length_polyline-1): bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / ( np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1])) @@ -61,6 +66,15 @@ class Polyline: self.centers[i] = Point2D(array[0], array[1]).round() return self.centers + def get_arcs(self): + for i in range(1, self.length_polyline-1): + point_1 = self.points[i] - \ + self.alpha_radii[i] * self.unit_vectors[i-1] + point_2 = self.points[i] + \ + self.alpha_radii[i] * self.unit_vectors[i] + self.connections[i] = (point_1, point_2) + return self.connections + def _alpha_assign(self, start_index: int, end_index: int): """ The alpha-assign procedure assigning radii based on a polyline. @@ -131,3 +145,15 @@ class Polyline: def _compute_alpha_radii(self): self.alpha_radii[0] = 0 self.alpha_radii[self.length_polyline-1] = 0 + + @staticmethod + def _remove_collinear_points(points): + output_points = [points[0]] + + for i in range(1, len(points) - 1): + if not Point2D.collinear( + points[i-1], points[i], points[i+1]): + output_points.append(points[i]) + + output_points.append(points[-1]) + return output_points diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 15a1067..46cc70c 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -8,14 +8,14 @@ class Segment2D: def __init__(self, start: Point2D, end: Point2D): self.start = start self.end = end - self.coordinates = [] - self.coordinates_thick = [] + self.points = [] + self.points_thick = [] self.thickness = None def __repr__(self): - return str(self.coordinates) + return str(self.points) - def segment(self, overlap: LINE_OVERLAP = LINE_OVERLAP.NONE, is_computing_thickness: bool = False) -> List[Point2D]: + def segment(self, start: Point2D = None, end: Point2D = None, 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 @@ -24,12 +24,17 @@ class Segment2D: start (Point2D): Start point of the segment. end (Point2D): End point of the segment. overlap (LINE_OVERLAP): Overlap draws additional pixel when changing minor direction. For standard bresenham overlap, choose LINE_OVERLAP_NONE. Can also be LINE_OVERLAP_MAJOR or LINE_OVERLAP_MINOR. + _is_computing_thickness (bool, optionnal): Used by segment_thick. Don't touch. >>> Segment2D(Point2D(0, 0), Point2D(10, 15)) """ - start = self.start.copy() - end = self.end.copy() + if start == None or end == None: + start = self.start.copy() + end = self.end.copy() + else: + start = start.copy() + end = end.copy() # Direction delta_x = end.x - start.x @@ -50,7 +55,7 @@ class Segment2D: delta_2x = 2*delta_x delta_2y = 2*delta_y - self._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) if (delta_x > delta_y): error = delta_2y - delta_x @@ -58,33 +63,33 @@ class Segment2D: start.x += step_x if (error >= 0): if (overlap == LINE_OVERLAP.MAJOR): - self._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) start.y += step_y if (overlap == LINE_OVERLAP.MINOR): - self._add_coordinates( - Point2D(start.copy().x - step_x, start.copy().y), is_computing_thickness) + self._add_points( + Point2D(start.copy().x - step_x, start.copy().y), _is_computing_thickness) error -= delta_2x error += delta_2y - self._add_coordinates(start, is_computing_thickness) + self._add_points(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._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) start.x += step_x if (overlap == LINE_OVERLAP.MINOR): - self._add_coordinates( - Point2D(start.copy().x, start.copy().y - step_y), is_computing_thickness) + self._add_points( + Point2D(start.copy().x, start.copy().y - step_y), _is_computing_thickness) error -= delta_2y error += delta_2x - self._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) - if not is_computing_thickness: - return self.coordinates + if not _is_computing_thickness: + return self.points def segment_thick(self, thickness: int, thickness_mode: LINE_THICKNESS_MODE) -> List[Point2D]: """Bresenham with thickness. @@ -150,7 +155,7 @@ class Segment2D: error += delta_2x self.segment( - start, end, LINE_OVERLAP.NONE, is_computing_thickness=True) + start, end, overlap=LINE_OVERLAP.NONE, _is_computing_thickness=True) error = delta_2x - delta_x for i in range(thickness, 1, -1): @@ -165,7 +170,7 @@ class Segment2D: error += delta_2y self.segment( - start, end, overlap, is_computing_thickness=True) + start, end, overlap=overlap, _is_computing_thickness=True) else: if swap: @@ -186,7 +191,7 @@ class Segment2D: error += delta_2x self.segment( - start, end, LINE_OVERLAP.NONE, is_computing_thickness=True) + start, end, overlap=LINE_OVERLAP.NONE, _is_computing_thickness=True) error = delta_2x - delta_y for i in range(thickness, 1, -1): @@ -201,9 +206,9 @@ class Segment2D: error += delta_2x self.segment( - start, end, overlap, is_computing_thickness=True) + start, end, overlap=overlap, _is_computing_thickness=True) - return self.coordinates + return self.points def perpendicular(self, distance: int) -> List[Point2D]: """Compute perpendicular points from both side of the segment placed at start level. @@ -232,8 +237,8 @@ class Segment2D: np.round((self.start.y + self.end.y) / 2.0).astype(int), ) - def _add_coordinates(self, coordinates, is_computing_thickness): + def _add_points(self, points, is_computing_thickness): if is_computing_thickness: - self.coordinates_thick.append(coordinates.copy()) + self.points_thick.append(points.copy()) else: - self.coordinates.append(coordinates.copy()) + self.points.append(points.copy()) diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index 89fbe96..e5dac0f 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -7,22 +7,22 @@ class Segment3D: def __init__(self, start: Point3D, end: Point3D): self.start = start self.end = end - self.coordinates = [] + self.output_points = [] def __repr__(self): - return str(self.coordinates) + return str(self.output_points) - def discrete_coordinates(self, overlap: bool = False): + def segment(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: - overlap (bool, optional): If False, remove unnecessary coordinates connecting to other coordinates side by side, leaving only a diagonal connection. Defaults to False. + overlap (bool, optional): If False, remove unnecessary points connecting to other points 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()) + self.output_points.append(start.copy()) dx = abs(self.end.x - self.start.x) dy = abs(self.end.y - self.start.y) dz = abs(self.end.z - self.start.z) @@ -45,18 +45,18 @@ class Segment3D: p2 = 2 * dz - dx while start.x != end.x: start.x += xs - self.coordinates.append(start.copy()) + self.output_points.append(start.copy()) if p1 >= 0: start.y += ys if not overlap: - if self.coordinates[-1].y != start.y: - self.coordinates.append(start.copy()) + if self.output_points[-1].y != start.y: + self.output_points.append(start.copy()) p1 -= 2 * dx if p2 >= 0: start.z += zs if not overlap: - if self.coordinates[-1].z != start.z: - self.coordinates.append(start.copy()) + if self.output_points[-1].z != start.z: + self.output_points.append(start.copy()) p2 -= 2 * dx p1 += 2 * dy p2 += 2 * dz @@ -67,18 +67,18 @@ class Segment3D: p2 = 2 * dz - dy while start.y != end.y: start.y += ys - self.coordinates.append(start.copy()) + self.output_points.append(start.copy()) if p1 >= 0: start.x += xs if not overlap: - if self.coordinates[-1].x != start.x: - self.coordinates.append(start.copy()) + if self.output_points[-1].x != start.x: + self.output_points.append(start.copy()) p1 -= 2 * dy if p2 >= 0: start.z += zs if not overlap: - if self.coordinates[-1].z != start.z: - self.coordinates.append(start.copy()) + if self.output_points[-1].z != start.z: + self.output_points.append(start.copy()) p2 -= 2 * dy p1 += 2 * dx p2 += 2 * dz @@ -89,22 +89,22 @@ class Segment3D: p2 = 2 * dx - dz while start.z != end.z: start.z += zs - self.coordinates.append(start.copy()) + self.output_points.append(start.copy()) if p1 >= 0: start.y += ys if not overlap: - if self.coordinates[-1].y != start.y: - self.coordinates.append(start.copy()) + if self.output_points[-1].y != start.y: + self.output_points.append(start.copy()) p1 -= 2 * dz if p2 >= 0: start.x += xs if not overlap: - if self.coordinates[-1].x != start.x: - self.coordinates.append(start.copy()) + if self.output_points[-1].x != start.x: + self.output_points.append(start.copy()) p2 -= 2 * dz p1 += 2 * dy p2 += 2 * dx - return self.coordinates + return self.output_points def middle_point(self): return (np.round((self.start.x + self.end.x) / 2.0).astype(int), diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 1ca56bb..9f4f400 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -1,6 +1,5 @@ from math import sqrt, cos, pi, sin import numpy as np -from networks.geometry.segment_tools import discrete_segment, middle_point, parallel def segments_intersection(line0, line1, full_line=True): diff --git a/output_image.png b/output_image.png index df9422f8fe707286e718084b7324378f49052d62..8d29fc56b30b312307102127f8e6eb63a3fcbdea 100644 GIT binary patch literal 4384 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 V<;Noh!jlh?bN0})b!P&9|1V9IXSo0X literal 5757 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 Date: Thu, 13 Jun 2024 18:35:45 +0200 Subject: [PATCH 58/69] Remove debug print --- main.py | 9 --------- networks/geometry/Polyline.py | 3 --- output_image.png | Bin 4384 -> 3803 bytes 3 files changed, 12 deletions(-) diff --git a/main.py b/main.py index 6ea285c..c636111 100644 --- a/main.py +++ b/main.py @@ -305,9 +305,6 @@ p = Polyline(random_points) radius = p.get_radii() center = p.get_centers() -print(radius) -print(center) -print(p.lengths) y = 200 @@ -319,23 +316,19 @@ image = Image.new('RGB', (width, height), 'white') draw = ImageDraw.Draw(image) for i in range(len(p.output_points)-1): - print("iiii", i) if p.output_points[i] != None: s = Segment2D(Point2D(p.output_points[i].x, p.output_points[i].y), Point2D( p.output_points[i+1].x, p.output_points[i+1].y)) s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) for j in range(len(s.points_thick)-1): - print("j", j) # editor.placeBlock( # s.coordinates[j].coordinate, Block("cyan_concrete")) draw.point((s.points_thick[j].x+w, w-s.points_thick[j].y), fill='red') - print(s.points_thick[j]) for i in range(len(center)): - print("iiii", i) if center[i]: circle = Circle(center[i]) circle.circle_thick(radius[i]-ww/2+1, radius[i]+ww/2+1) @@ -344,8 +337,6 @@ for i in range(len(center)): # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) draw.point((circle.points_thick[j].x+w, w-circle.points_thick[j].y), fill='black') - print(circle.points_thick[j]) - image.save('output_image.png') diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 6988140..e3e8635 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -47,8 +47,6 @@ class Polyline: def get_radii(self): for i in range(1, self.length_polyline-1): - print("\nalpha_radii, tan", self.alpha_radii[i] * self.tangente[i], - self.alpha_radii[i], self.tangente[i]) self.radii[i] = round(self.alpha_radii[i] * self.tangente[i]) return self.radii @@ -59,7 +57,6 @@ class Polyline: for i in range(1, self.length_polyline-1): bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / ( np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1])) - print("bi", bisector) array = self.points[i] + sqrt((self.radii[i] ** 2) + (self.alpha_radii[i] ** 2)) * bisector diff --git a/output_image.png b/output_image.png index 8d29fc56b30b312307102127f8e6eb63a3fcbdea..4bb92b505c81f423113a8084e7f408483a3ee257 100644 GIT binary patch literal 3803 zcmeHK`#)4``=0Wac4|eEvXN?|gVWCLz{1FobQmgfC}fvL2W6#NMN6R?lcbXf=zP+6M-h>ZiQgsaOj(JWHp;H~$F zZW5xUqETBZC^M$cy)CF$00si^c)9sgjAKbkj=*I;M79RhcS*Ubhz?^zed}SlH_?5N zl*~iw3h6LYsIMxc;XRBdpd&7-4}&1i!N-nx@Vmb)8i%P(~aa z_-xj5@unF0Ib0|FakG;#iQQbh$EGJGn*{~_@ggZ5{$zCxEe`tTAaRK5=i<88cl>eH z*U^&9u*es82Re)Gr%Z{YJ||Q@%m! zd0cq+cZf9X8`HOsxh69R(3CqXfO>7YhXOdFE#IpEG_>U*3ZQVl{GM!YSDE@5)KiE(V9z$A?G3lx}waV(&~G>o$X3e3Pb z%OF=`>227EN@UN0@>U{zJ+ZJ=W-wb5K9)sWrJ~{U z5Pr5;*wH_wJlu^4(uDdBDuCs2pu7cUR%yVA$hL&?bP@hFv5=YT2rXFg7LM^!2;#xc z0T6e`7jOJWWk!sA#=jk%5C73Tr7`Ql#1?(#W=f2?qUTn6bB~t zgTE}J7Ua;(+U`okM8PE2O#!sUfnEdPUZzDZ9d-xZm4=B{lH9Wj;FSQhZh{9_e~ZNh|c&AvH-w%#w>blQ{cP2NNOBh<{Ll`u&`|-9ToZ(MmgKHLc%dZM0^tRb z+_DD>iQ}lnW0L#)lp>(ma?VebnIqwwh?dSoOy5e?E6~z82#+cFEiz$n92nmWw;EAJ zu7vLewDc#$lqua+h-oTXYK534qowN*)7xmN9b$R|jm5P4Gn>^e=w&o|{VqRq(f^I1 zYgAZcQDx1r53k^B`!a+unGEjrsFR1Z)b2=3Z;RE1zjF!cAw!wBMDBqHO;-ePp5>}e zZn!#1QZu~1XP^GtuOMP^q@AH!eLxVa&9_pdaf|(I-opjaF>Vw3ucP-@n5*&LX*Bq6 zg0uFuoR9FWzX?4vS1V572C#GFjx#4|Wy2Z?y9n>Tm7no8`DF2Ldn?D=RJ_uz!u+%( z@LYvt`osPAbE96Kxbj;(_$7-@P~92~#&f=#9j&iDR-XCByz|Z_a-$v3hZ9=w_@lJw zBIOo#p#52Bq0MTDzg#vkK74pxNkUAxHoRdo(WsxlZ$htd9S7p~$R=v1nV?VZC%HZl z$Ajtj=qjw7D^;vq!-MNunlrbY1YlVfy|?xB50+F@9JZ(8S<~R0D_~jaX(FjQ z$)s-wo}KTdeEcQUpPfbm>7(oW>!SGaeV%xB`Kg6;TAMdLN_~ooND+Yez6VTmVo>&4 z2S#7%A#J08M0fz=cN8_lwi}vZh!48RJD*nP8dFMqA}3e*;6WD;DUY=~ zXh8gpxkKPCO?VNnf=Qum7~*}-bm(D4$7sz8)-Tt{lT9~ufEOMmh-A|N<+sR|8 zA$vZ3#NxpPyVk6C`DRIlK!=$W2Y+a2aFKg$cuJFivGM26SE`htKfwlk@a%&26P%SK zaA`nU5n6rceT#V)p$$JYFWm6UlRjI9v#TFVtoF+7i?Ka4Y(ZJYX7%4*C|h4h0=Jsr ziqMV;=_OQT*D8%vAvejqGpZl7;FP)ugfH;&$${KNp6;Vqe{_BCrhkYYwd$}GEe{3M~xBUU(6En{* zxjt9Ri5;jMmImtB8_N9~f9=}*s?K1iPtC|Bl%t9CSweg>_}s;lBg+kkExNZWCVP#3 zj$D(5a*U*~*kDcY=me+c$5Hz)tCmuls|x8Q=~N{3dmz5l+k;@Q|L&JNqGw5IZYR?8 z&pMI7fS;`TPvd(Ku_V#|)t7r?JgxvVt5rn_O25k>%L{35D)F41VIr

    Dqc!@kngYs@*1MM`i9L zx5-QTfUUOW^Y+b?1M7zp;=!UiX=t<9VBzT(w8gB@PIzlPD*8}FaG2iceT4s9zsgbW zNl3kjP!oV9%$b&_1%E8=L|_AyqF{AR_@f3w-xDdZIPj}*;_fUG7%wTNEvEO$&Q@)L z)T8!KIM_hy0e>~a-WPJ7SJyvB)rtoqciFSq96b8jm&P}4c(l92sQ;wAfBXqd0?Z!K zCS$W1BZubAwXtT5T>4dlttkoUcaOdgf1bG>QU@YBRt~8Se{5&E-+&N7U(Pbd0_423 zWAUOV$;MQzuVT{aQe`|C7LBPq4z!ZM-jRIT<=3{2`LC~lVRw}T@dj#Y$RUBUlGB8S zgo{US+C3E6a8>{|Nv>gS+q!Ks^Q@_P5ibow4&QB*uV4yF-LIofPD1DQPwix3u}Fv3 zF3-OU_p2EyOpcHMz90T@a6oC+Gv34O(5w^onH@b(q#CNp{cYazwcahN;9dY9_}J>4 zpOCsU)@BPHon}Bp7zVtbskUe$-=re=f^w)UbL`vp^Qz|1;)7kM6z(K&5-Olo%2rF& zF+;`utn;I1u1$7M^ZBzKs{udVu%zBqP4_Q9p>F%UB{uM~<67V#cq>`d*se?7Wh8?qi>(%w~^KW_8)8W)CU(`s#T zazMh@MnCM+_eXfNUfpA&@9O;|(1X)(xmEu?KmxaSJi7n>kMN7ATGG3LL&6*B%P7a6 zbqbhEAafRbH2zf~3H-JlkG{7_XWhLoX{g$%QTphXc4ob{NPRo9o*pLw&$hfu-(W&) zkW$;(o3L7GLe=V1(&N8Q1?57nmpJv90JOSQVJ-^YhU(CM&-c?x> literal 4384 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 V<;Noh!jlh?bN0})b!P&9|1V9IXSo0X From 9b87874e13eb530b9641b8346d4c63be70ca2481 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Thu, 13 Jun 2024 19:49:38 +0200 Subject: [PATCH 59/69] Working polylines demo --- main.py | 66 ++++++++++++++++++++++++++++------ networks/geometry/Circle.py | 2 +- networks/geometry/Polyline.py | 32 +++++++++-------- output_image.png | Bin 3803 -> 5780 bytes 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/main.py b/main.py index c636111..a0d761e 100644 --- a/main.py +++ b/main.py @@ -286,9 +286,9 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), # Point2D(-1188, 451), Point2D(-1176, 409), Point2D(-1179, 399))) -w = 250 +w = 200 -n_points = 5 +n_points = 20 min_val, max_val = -w, w random_points = [Point2D(random.randint(min_val, max_val), random.randint( @@ -298,6 +298,8 @@ random_points = [Point2D(random.randint(min_val, max_val), random.randint( # random_points = (Point2D(-75, -75), Point2D(0, -75), Point2D(75, 75), # Point2D(75, -50), Point2D(-50, 50), Point2D(0, 0)) +random_points = random_points[0].optimized_path(random_points) + p = Polyline(random_points) # Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) @@ -307,14 +309,15 @@ radius = p.get_radii() center = p.get_centers() y = 200 - -ww = 40 +ww = 15 width, height = 2*w, 2*w -image = Image.new('RGB', (width, height), 'white') +image = Image.new('RGB', (width, height), 'black') draw = ImageDraw.Draw(image) +print(p.output_points) + for i in range(len(p.output_points)-1): if p.output_points[i] != None: s = Segment2D(Point2D(p.output_points[i].x, p.output_points[i].y), Point2D( @@ -325,18 +328,59 @@ for i in range(len(p.output_points)-1): # editor.placeBlock( # s.coordinates[j].coordinate, Block("cyan_concrete")) draw.point((s.points_thick[j].x+w, - w-s.points_thick[j].y), fill='red') + w-s.points_thick[j].y), fill='grey') + + +for i in range(2, len(p.get_arcs_intersections())-2): + + s = Segment2D(Point2D(p.acrs_intersections[i][0].x, p.acrs_intersections[i][0].y), Point2D( + p.acrs_intersections[i-1][-1].x, p.acrs_intersections[i-1][-1].y)) + s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) + + for j in range(len(s.points_thick)-1): + # editor.placeBlock( + # s.coordinates[j].coordinate, Block("cyan_concrete")) + draw.point((s.points_thick[j].x+w, + w-s.points_thick[j].y), fill='white') + draw.point((p.acrs_intersections[i][0].x+w, + w-p.acrs_intersections[i][0].y), fill='blue') + draw.point((p.acrs_intersections[i][-1].x+w, + w-p.acrs_intersections[i][-1].y), fill='red') for i in range(len(center)): if center[i]: circle = Circle(center[i]) - circle.circle_thick(radius[i]-ww/2+1, radius[i]+ww/2+1) + circle.circle_thick(round(radius[i]-ww/2), round(radius[i]+ww/2)) for j in range(len(circle.points_thick)-1): - # editor.placeBlock( - # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.points_thick[j].x+w, - w-circle.points_thick[j].y), fill='black') + if circle.points_thick[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): + # editor.placeBlock( + # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) + draw.point((circle.points_thick[j].x+w, + w-circle.points_thick[j].y), fill='white') + circle.circle(radius[i]) + for j in range(len(circle.points)-1): + if circle.points[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): + # editor.placeBlock( + # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) + draw.point((circle.points[j].x+w, + w-circle.points[j].y), fill='purple') + +s1 = Segment2D(Point2D(p.acrs_intersections[1][0].x, p.acrs_intersections[1][0].y), Point2D( + p.output_points[0].x, p.output_points[0].y)) +s1.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) + +for j in range(len(s1.points_thick)-1): + draw.point((s1.points_thick[j].x+w, + w-s1.points_thick[j].y), fill='white') + +s1 = Segment2D(Point2D(p.acrs_intersections[-2][2].x, p.acrs_intersections[-2][2].y), Point2D( + p.output_points[-1].x, p.output_points[-1].y)) +s1.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) + +for j in range(len(s1.points_thick)-1): + draw.point((s1.points_thick[j].x+w, + w-s1.points_thick[j].y), fill='white') image.save('output_image.png') diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index c6f78a2..caff87c 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -20,7 +20,7 @@ class Circle: def __repr__(self): return f"Circle(center: {self.center}, radius: {self.radius}, spaced_radius: {self.spaced_radius}, inner: {self.inner}, outer: {self.outer})" - def cirlce(self, radius: int) -> List[Point2D]: + def circle(self, radius: int) -> List[Point2D]: self.radius = radius center = self.center.copy() diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index e3e8635..da20544 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -18,8 +18,9 @@ class Polyline: >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ - self.points = Point2D.to_vectors(self._remove_collinear_points(points)) - self.length_polyline = len(points) + self.points_array = Point2D.to_vectors( + self._remove_collinear_points(points)) + self.length_polyline = len(self.points_array) if self.length_polyline < 4: raise ValueError("The list must contain at least 4 elements.") @@ -29,11 +30,12 @@ class Polyline: self.unit_vectors = [None] * self.length_polyline # n self.tangente = [0] * self.length_polyline # f - self.alpha_radii = [None] * self.length_polyline # alpha + # alpha, maximum radius factor + self.alpha_radii = [None] * self.length_polyline self.radii = [None] * self.length_polyline # r self.centers = [None] * self.length_polyline # c - self.connections = [None] * self.length_polyline + self.acrs_intersections = [None] * self.length_polyline self._compute_requirements() self._compute_alpha_radii() @@ -58,19 +60,20 @@ class Polyline: bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / ( np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1])) - array = self.points[i] + sqrt((self.radii[i] - ** 2) + (self.alpha_radii[i] ** 2)) * bisector + array = self.points_array[i] + sqrt((self.radii[i] + ** 2) + (self.alpha_radii[i] ** 2)) * bisector self.centers[i] = Point2D(array[0], array[1]).round() return self.centers - def get_arcs(self): + def get_arcs_intersections(self): for i in range(1, self.length_polyline-1): - point_1 = self.points[i] - \ + point_1 = self.points_array[i] - \ self.alpha_radii[i] * self.unit_vectors[i-1] - point_2 = self.points[i] + \ + point_2 = self.points_array[i] + \ self.alpha_radii[i] * self.unit_vectors[i] - self.connections[i] = (point_1, point_2) - return self.connections + self.acrs_intersections[i] = Point2D( + point_1[0], point_1[1]).round(), Point2D(self.points_array[i][0], self.points_array[i][1]), Point2D(point_2[0], point_2[1]).round() + return self.acrs_intersections def _alpha_assign(self, start_index: int, end_index: int): """ @@ -109,9 +112,8 @@ class Polyline: alpha_low, alpha_high = alpha_a, self.alpha_radii[end_index] # Assign alphas at ends of selected segment - self.alpha_radii[minimum_index] = alpha_low/1.5 - self.alpha_radii[minimum_index+1] = alpha_high/1.5 - + self.alpha_radii[minimum_index] = alpha_low + self.alpha_radii[minimum_index+1] = alpha_high # Recur on lower segments self._alpha_assign(start_index, minimum_index) # Recur on higher segments @@ -130,7 +132,7 @@ class Polyline: def _compute_requirements(self): # Between two points, there is only one segment for j in range(self.length_polyline-1): - self.vectors[j] = self.points[j+1] - self.points[j] + self.vectors[j] = self.points_array[j+1] - self.points_array[j] self.lengths[j] = np.linalg.norm(self.vectors[j]) self.unit_vectors[j] = self.vectors[j]/self.lengths[j] diff --git a/output_image.png b/output_image.png index 4bb92b505c81f423113a8084e7f408483a3ee257..d90c85411584c5a3365ac9283c7faac614bb1808 100644 GIT binary patch literal 5780 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

    Dqc!@kngYs@*1MM`i9L zx5-QTfUUOW^Y+b?1M7zp;=!UiX=t<9VBzT(w8gB@PIzlPD*8}FaG2iceT4s9zsgbW zNl3kjP!oV9%$b&_1%E8=L|_AyqF{AR_@f3w-xDdZIPj}*;_fUG7%wTNEvEO$&Q@)L z)T8!KIM_hy0e>~a-WPJ7SJyvB)rtoqciFSq96b8jm&P}4c(l92sQ;wAfBXqd0?Z!K zCS$W1BZubAwXtT5T>4dlttkoUcaOdgf1bG>QU@YBRt~8Se{5&E-+&N7U(Pbd0_423 zWAUOV$;MQzuVT{aQe`|C7LBPq4z!ZM-jRIT<=3{2`LC~lVRw}T@dj#Y$RUBUlGB8S zgo{US+C3E6a8>{|Nv>gS+q!Ks^Q@_P5ibow4&QB*uV4yF-LIofPD1DQPwix3u}Fv3 zF3-OU_p2EyOpcHMz90T@a6oC+Gv34O(5w^onH@b(q#CNp{cYazwcahN;9dY9_}J>4 zpOCsU)@BPHon}Bp7zVtbskUe$-=re=f^w)UbL`vp^Qz|1;)7kM6z(K&5-Olo%2rF& zF+;`utn;I1u1$7M^ZBzKs{udVu%zBqP4_Q9p>F%UB{uM~<67V#cq>`d*se?7Wh8?qi>(%w~^KW_8)8W)CU(`s#T zazMh@MnCM+_eXfNUfpA&@9O;|(1X)(xmEu?KmxaSJi7n>kMN7ATGG3LL&6*B%P7a6 zbqbhEAafRbH2zf~3H-JlkG{7_XWhLoX{g$%QTphXc4ob{NPRo9o*pLw&$hfu-(W&) zkW$;(o3L7GLe=V1(&N8Q1?57nmpJv90JOSQVJ-^YhU(CM&-c?x> From 4a611a4aa2685ca64b1c55bbe89b9188d73bd083 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sat, 15 Jun 2024 01:41:26 +0200 Subject: [PATCH 60/69] Finalize Polyline parsing but still arcs precision issue --- main.py | 148 ++++++++++++++++++++------------- networks/geometry/Circle.py | 21 +++-- networks/geometry/Point2D.py | 49 ++++++++--- networks/geometry/Point3D.py | 14 +++- networks/geometry/Polyline.py | 122 +++++++++++++++++++++------ networks/geometry/Segment2D.py | 19 +++-- output_image.png | Bin 5780 -> 1754 bytes 7 files changed, 260 insertions(+), 113 deletions(-) diff --git a/main.py b/main.py index a0d761e..28309d7 100644 --- a/main.py +++ b/main.py @@ -1,31 +1,33 @@ -from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE, ROTATION -from PIL import Image, ImageDraw -import matplotlib.pyplot as plt -from networks.geometry.Point3D import Point3D -from networks.geometry.Segment3D import Segment3D -from networks.geometry.Segment2D import Segment2D -from networks.geometry.Circle import Circle -from networks.geometry.Polyline import Polyline -from networks.geometry.Point2D import Point2D -import networks.roads.lines.Line as Line -import networks.roads.lanes.Lane as Lane -from gdpc import Editor, Block, geometry -import networks.geometry.curve_tools as curve_tools -import networks.geometry.Strip as Strip -import networks.geometry.segment_tools as segment_tools -import numpy as np import json -from buildings.Building import Building import random +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +from gdpc import Block, Editor, geometry +from PIL import Image, ImageDraw + +import networks.geometry.curve_tools as curve_tools +import networks.geometry.segment_tools as segment_tools +import networks.geometry.Strip as Strip +import networks.roads.lanes.Lane as Lane +import networks.roads.lines.Line as Line +from buildings.Building import Building +from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE, ROTATION +from networks.geometry.Circle import Circle +from networks.geometry.Point2D import Point2D +from networks.geometry.Point3D import Point3D +from networks.geometry.point_tools import ( + curved_corner_by_curvature, + curved_corner_by_distance, +) +from networks.geometry.Polyline import Polyline +from networks.geometry.Segment2D import Segment2D +from networks.geometry.Segment3D import Segment3D from networks.roads import Road as Road from networks.roads.intersections import Intersection as Intersection -from networks.geometry.point_tools import curved_corner_by_curvature, curved_corner_by_distance - - -import matplotlib matplotlib.use('Agg') @@ -286,18 +288,26 @@ block_list = ["blue_concrete", "red_concrete", "green_concrete", # p = Polyline((Point2D(-1225, 468), Point2D(-1138, 481), # Point2D(-1188, 451), Point2D(-1176, 409), Point2D(-1179, 399))) -w = 200 +w = 100 -n_points = 20 +n_points = 8 min_val, max_val = -w, w random_points = [Point2D(random.randint(min_val, max_val), random.randint( min_val, max_val)) for _ in range(n_points)] +print(random_points) +print("\n\n") + # random_points = (Point2D(-75, -75), Point2D(0, -75), Point2D(75, 75), # Point2D(75, -50), Point2D(-50, 50), Point2D(0, 0)) +# random_points = random_points[0].optimized_path(random_points) + +# random_points = [Point2D(-40, -56), Point2D(-94, 92), Point2D(19, -47), Point2D( +# 100, 59), Point2D(-85, -73), Point2D(-33, -9), Point2D(57, -25), Point2D(51, -34)] + random_points = random_points[0].optimized_path(random_points) p = Polyline(random_points) @@ -316,10 +326,8 @@ image = Image.new('RGB', (width, height), 'black') draw = ImageDraw.Draw(image) -print(p.output_points) - for i in range(len(p.output_points)-1): - if p.output_points[i] != None: + if p.output_points[i] != 0: s = Segment2D(Point2D(p.output_points[i].x, p.output_points[i].y), Point2D( p.output_points[i+1].x, p.output_points[i+1].y)) s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) @@ -331,40 +339,40 @@ for i in range(len(p.output_points)-1): w-s.points_thick[j].y), fill='grey') -for i in range(2, len(p.get_arcs_intersections())-2): +# for i in range(2, len(p.get_arcs_intersections())-2): - s = Segment2D(Point2D(p.acrs_intersections[i][0].x, p.acrs_intersections[i][0].y), Point2D( - p.acrs_intersections[i-1][-1].x, p.acrs_intersections[i-1][-1].y)) - s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) +# s = Segment2D(Point2D(p.acrs_intersections[i][0].x, p.acrs_intersections[i][0].y), Point2D( +# p.acrs_intersections[i-1][-1].x, p.acrs_intersections[i-1][-1].y)) +# s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) - for j in range(len(s.points_thick)-1): - # editor.placeBlock( - # s.coordinates[j].coordinate, Block("cyan_concrete")) - draw.point((s.points_thick[j].x+w, - w-s.points_thick[j].y), fill='white') - draw.point((p.acrs_intersections[i][0].x+w, - w-p.acrs_intersections[i][0].y), fill='blue') - draw.point((p.acrs_intersections[i][-1].x+w, - w-p.acrs_intersections[i][-1].y), fill='red') +# for j in range(len(s.points_thick)-1): +# # editor.placeBlock( +# # s.coordinates[j].coordinate, Block("cyan_concrete")) +# draw.point((s.points_thick[j].x+w, +# w-s.points_thick[j].y), fill='green') +# draw.point((p.acrs_intersections[i][0].x+w, +# w-p.acrs_intersections[i][0].y), fill='green') +# draw.point((p.acrs_intersections[i][-1].x+w, +# w-p.acrs_intersections[i][-1].y), fill='green') -for i in range(len(center)): - if center[i]: - circle = Circle(center[i]) - circle.circle_thick(round(radius[i]-ww/2), round(radius[i]+ww/2)) - for j in range(len(circle.points_thick)-1): - if circle.points_thick[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): - # editor.placeBlock( - # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.points_thick[j].x+w, - w-circle.points_thick[j].y), fill='white') - circle.circle(radius[i]) - for j in range(len(circle.points)-1): - if circle.points[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): - # editor.placeBlock( - # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.points[j].x+w, - w-circle.points[j].y), fill='purple') +# for i in range(len(center)): +# if center[i]: +# circle = Circle(center[i]) +# circle.circle_thick(round(radius[i]-ww/2), round(radius[i]+ww/2)) +# for j in range(len(circle.points_thick)-1): +# if circle.points_thick[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): +# # editor.placeBlock( +# # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) +# draw.point((circle.points_thick[j].x+w, +# w-circle.points_thick[j].y), fill='green') +# circle.circle(radius[i]) +# for j in range(len(circle.points)-1): +# if circle.points[j].is_in_triangle(p.acrs_intersections[i][0], p.acrs_intersections[i][1], p.acrs_intersections[i][2]): +# # editor.placeBlock( +# # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) +# draw.point( +# (circle.points[j].x+w, w-circle.points[j].y), fill='green') s1 = Segment2D(Point2D(p.acrs_intersections[1][0].x, p.acrs_intersections[1][0].y), Point2D( p.output_points[0].x, p.output_points[0].y)) @@ -372,7 +380,7 @@ s1.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) for j in range(len(s1.points_thick)-1): draw.point((s1.points_thick[j].x+w, - w-s1.points_thick[j].y), fill='white') + w-s1.points_thick[j].y), fill='grey') s1 = Segment2D(Point2D(p.acrs_intersections[-2][2].x, p.acrs_intersections[-2][2].y), Point2D( p.output_points[-1].x, p.output_points[-1].y)) @@ -380,10 +388,34 @@ s1.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) for j in range(len(s1.points_thick)-1): draw.point((s1.points_thick[j].x+w, - w-s1.points_thick[j].y), fill='white') + w-s1.points_thick[j].y), fill='grey') + +for i in range(0, len(p.arcs)): + for j in range(len(p.arcs[i])): + draw.point((p.arcs[i][j].x+w, w-p.arcs[i][j].y), fill='white') + + +for i in range(1, len(p.segments)-1): + for j in range(len(p.segments[i].segment())): + draw.point((p.segments[i].points[j].x+w, + w-p.segments[i].points[j].y), fill='white') + +for i in range(1, len(p.centers)-1): + draw.point((p.centers[i].x+w, w-p.centers[i].y), fill='red') + draw.point((p.acrs_intersections[i][0].x+w, + w-p.acrs_intersections[i][0].y), fill='blue') + draw.point((p.acrs_intersections[i][1].x+w, + w-p.acrs_intersections[i][1].y), fill='purple') + draw.point((p.acrs_intersections[i][2].x+w, + w-p.acrs_intersections[i][2].y), fill='blue') + image.save('output_image.png') +# s = Segment2D(Point2D(-88, -12), Point2D(9, 75)) +# s.segment_thick(3, LINE_THICKNESS_MODE.MIDDLE) +# print(s.points) + # s = Segment2D(Point2D(0, 0), Point2D(10, 10)).perpendicular(10) # print(s) diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index caff87c..cda5585 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -1,21 +1,24 @@ -from networks.geometry.Point2D import Point2D -from math import cos, sin, pi +from math import cos, pi, sin from typing import List +import numpy as np + +from networks.geometry.Point2D import Point2D + class Circle: def __init__(self, center: Point2D): self.center = center self.radius = None - self.points = [] + self.points: List[Point2D] = [] self.inner = None self.outer = None - self.points_thick = [] + self.points_thick: List[Point2D] = [] self.spaced_radius = None - self.spaced_points = [] + self.spaced_points: List[Point2D] = [] def __repr__(self): return f"Circle(center: {self.center}, radius: {self.radius}, spaced_radius: {self.spaced_radius}, inner: {self.inner}, outer: {self.outer})" @@ -43,6 +46,7 @@ class Circle: continue else: break + return self.points def circle_thick(self, inner: int, outer: int) -> List[Point2D]: """Compute discrete value of a 2d-circle with thickness. @@ -114,16 +118,17 @@ class Circle: center = self.center self.spaced_points = [ - Point2D(cos(2 * pi / number * i) * radius, - sin(2 * pi / number * i) * radius) + Point2D(round(cos(2 * pi / number * i) * radius), + round(sin(2 * pi / number * i) * radius)) for i in range(0, number + 1) ] for i in range(len(self.spaced_points)): - self.spaced_points[i] = Point2D( + current_point = Point2D( self.spaced_points[i].x + center.x, self.spaced_points[i].y + center.y ).round() + self.spaced_points[i] = current_point return self.spaced_points def _x_line(self, x1, x2, y): diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index b2bedff..ef8a804 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -1,6 +1,8 @@ -import numpy as np -from typing import List from math import atan2, sqrt +from typing import List, Union + +import numpy as np + from Enums import ROTATION @@ -21,10 +23,26 @@ class Point2D: return self.x == other.x and self.y == other.y return False + def __add__(self, other): + if isinstance(other, np.ndarray) and other.shape == (2,): + return Point2D(self.x + other[0], self.y + other[1]) + elif isinstance(other, Point2D): + return Point2D(self.x + other.x, self.y + other.y) + else: + raise TypeError(f"Unsupported type for addition: {type(other)}") + + def __sub__(self, other): + if isinstance(other, np.ndarray) and other.shape == (2,): + return Point2D(self.x - other[0], self.y - other[1]) + elif isinstance(other, Point2D): + return Point2D(self.x - other.x, self.y - other.y) + else: + raise TypeError(f"Unsupported type for subtraction: {type(other)}") + def is_in_triangle(self, xy0: "Point2D", xy1: "Point2D", xy2: "Point2D"): """Returns True is the point is in a triangle defined by 3 others points. - From: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle. + From: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle Args: xy0 (Type[Point2D]): Point of the triangle. @@ -190,12 +208,21 @@ class Point2D: return abs(x1 * y2 - x2 * y1) < 1e-12 @staticmethod - def to_vectors(points: List["Point3D"]) -> List[np.array]: - vectors = [] - for point in points: - vectors.append(np.array(point.coordinates)) - - if (len(vectors) == 1): - return vectors[0] - else: + def to_arrays(points: Union[List["Point2D"], "Point2D"]) -> Union[List[np.array], "Point2D"]: + if isinstance(points, list): + vectors = [] + for point in points: + vectors.append(np.array(point.coordinates)) return vectors + else: + return np.array(points.coordinates) + + @staticmethod + def from_arrays(vectors: Union[List[np.array], "Point2D"]) -> Union[List["Point2D"], "Point2D"]: + if isinstance(vectors, list): + points = [] + for vector in vectors: + points.append(Point2D(vector[0], vector[1])) + return points + else: + return Point2D(vectors[0], vectors[1]) diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index 7873612..75fb36c 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -1,5 +1,6 @@ +from math import sqrt from typing import List -from math import atan2, sqrt + import numpy as np @@ -80,3 +81,14 @@ class Point3D: return vectors[0] else: return vectors + + @staticmethod + def from_arrays(vectors: List[np.array]) -> List["Point3D"]: + points = [] + for vector in vectors: + points.append(Point3D(vector[0], vector[1], vector[2])) + + if (len(points) == 1): + return points[0] + else: + return points diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index da20544..5f320ec 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -1,11 +1,17 @@ -from networks.geometry.Point2D import Point2D +from math import inf, sqrt +from typing import List, Tuple, Union -from math import sqrt, inf import numpy as np +from networks.geometry.Circle import Circle +from networks.geometry.Point2D import Point2D +from networks.geometry.Segment2D import Segment2D + +# from Enums import LINE_THICKNESS_MODE, LINE_OVERLAP + class Polyline: - def __init__(self, points: list["Point2D"]): + def __init__(self, points: List[Point2D]): """A polyline with smooth corners, only composed of segments and circle arc. Mathematics and algorithms behind this can be found here: https://cdr.lib.unc.edu/concern/dissertations/pz50gw814?locale=en, E2 Construction of arc roads from polylines, page 210. @@ -18,7 +24,8 @@ class Polyline: >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ - self.points_array = Point2D.to_vectors( + self.output_points = points + self.points_array = Point2D.to_arrays( self._remove_collinear_points(points)) self.length_polyline = len(self.points_array) @@ -26,36 +33,46 @@ class Polyline: raise ValueError("The list must contain at least 4 elements.") self.vectors = [None] * self.length_polyline # v - self.lengths = [None] * (self.length_polyline - 1) # l + self.lengths = [0] * (self.length_polyline - 1) # l self.unit_vectors = [None] * self.length_polyline # n self.tangente = [0] * self.length_polyline # f # alpha, maximum radius factor - self.alpha_radii = [None] * self.length_polyline + self.alpha_radii = [0] * self.length_polyline - self.radii = [None] * self.length_polyline # r - self.centers = [None] * self.length_polyline # c + # Useful outputs. In order to not break indexation, each list has the same length, even if for n points, there is n-2 radius. + # Lists will start and end with None. + self.radii = [0] * self.length_polyline # r, list of points + self.centers = [None] * self.length_polyline # c, list of points + # list of tuple of points (first intersection, corresponding corner, last intersection) self.acrs_intersections = [None] * self.length_polyline + self.arcs = [[]] * self.length_polyline # list of points + # self.not_arcs = [[]] * self.length_polyline + # For n points, there is n-1 segments. Last element should stays None. + self.segments = [None] * \ + self.length_polyline # list of segments + + # Run procedure self._compute_requirements() self._compute_alpha_radii() self._alpha_assign(0, self.length_polyline-1) - - self.output_points = points + self.get_radii() + self.get_centers() + self.get_arcs_intersections() + self.get_arcs() + self.get_segments() def __repr__(self): return str(self.alpha_radii) - def get_radii(self): + def get_radii(self) -> List[Union[int]]: for i in range(1, self.length_polyline-1): self.radii[i] = round(self.alpha_radii[i] * self.tangente[i]) return self.radii - def get_centers(self): - if self.radii == [None] * self.length_polyline: - raise ValueError("No radii found. Run get_radii before.") - + def get_centers(self) -> List[Union[Point2D, None]]: for i in range(1, self.length_polyline-1): bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / ( np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1])) @@ -65,16 +82,68 @@ class Polyline: self.centers[i] = Point2D(array[0], array[1]).round() return self.centers - def get_arcs_intersections(self): + def get_arcs_intersections(self) -> List[Tuple[Point2D]]: + """Get arcs intersections points. + + First and last elements elements of the list should be None. For n points, there are n-1 segments, and n-2 angle. + + Returns: + list[tuple(Point2D)]: List of tuples composed - in order - of the first arc points, the corner points, the last arc points. The corresponding arc circle is inside this triangle. + """ for i in range(1, self.length_polyline-1): - point_1 = self.points_array[i] - \ - self.alpha_radii[i] * self.unit_vectors[i-1] - point_2 = self.points_array[i] + \ - self.alpha_radii[i] * self.unit_vectors[i] - self.acrs_intersections[i] = Point2D( - point_1[0], point_1[1]).round(), Point2D(self.points_array[i][0], self.points_array[i][1]), Point2D(point_2[0], point_2[1]).round() + point_1 = Point2D.from_arrays(self.points_array[i] - + self.alpha_radii[i] * self.unit_vectors[i-1]) + point_2 = Point2D.from_arrays(self.points_array[i] + + self.alpha_radii[i] * self.unit_vectors[i]) + self.acrs_intersections[i] = point_1.round(), Point2D.from_arrays( + self.points_array[i]), point_2.round() return self.acrs_intersections + def get_arcs(self) -> List[Point2D]: + for i in range(1, self.length_polyline-1): + circle = Circle(self.centers[i]) + circle.circle(self.radii[i]) + for j in range(len(circle.points)): + if circle.points[j].is_in_triangle(self.acrs_intersections[i][0], self.acrs_intersections[i][1], self.acrs_intersections[i][2]): + self.arcs[i].append(circle.points[j]) + # for j in range(len(circle.points)): + # if (circle.points[j] in Segment2D(self.acrs_intersections[i][0], self.acrs_intersections[i][1]).segment(LINE_OVERLAP.MINOR)): + # self.not_arcs[i].append(circle.points[j]) + # print("hzeh") + # if (circle.points[j] in Segment2D(self.acrs_intersections[i][1], self.acrs_intersections[i][2]).segment(LINE_OVERLAP.MINOR)): + # self.not_arcs[i].append(circle.points[j]) + # print("hzeh") + # if (circle.points[j] in Segment2D(self.acrs_intersections[i][2], self.acrs_intersections[i][0]).segment(LINE_OVERLAP.MINOR)): + # self.not_arcs[i].append(circle.points[j]) + # print("hzeh") + return self.arcs + + def get_segments(self) -> List[Segment2D]: + """Get the segments between the circle arcs and at the start and end. + + Last list element should be None, and last usable index is -2 or self.length_polyline - 2. For n points, there are n-1 segments. + + Returns: + list[Segment2D]: List of segments in order. + """ + # Get first segment. + # segments index is 0, corresponding to the first points_array to the first point ([0]) of the first arc (acrs_intersections[1]). + # First arc index is 1 because index 0 is None due to fix list lenght. Is it a good choice? + self.segments[1] = Segment2D(Point2D.from_arrays( + self.points_array[0]), self.acrs_intersections[1][0]) + + # Get segments between arcs + for i in range(2, self.length_polyline - 2): + self.segments[i] = Segment2D(Point2D(self.acrs_intersections[i][0].x, self.acrs_intersections[i][0].y), Point2D( + self.acrs_intersections[i-1][-1].x, self.acrs_intersections[i-1][-1].y)) + + # Get last segment. Index is -2 because last index -1 should be None due to the same list lenght. + # For n points, there are n-1 segments. + self.segments[-2] = Segment2D(Point2D.from_arrays( + self.points_array[-1]), self.acrs_intersections[-2][2]) + + return self.segments + def _alpha_assign(self, start_index: int, end_index: int): """ The alpha-assign procedure assigning radii based on a polyline. @@ -101,8 +170,8 @@ class Polyline: minimum_radius, minimum_index = current_radius, i alpha_low, alpha_high = alpha_a, alpha_b - alpha_a = min(self.lengths[end_index-2], - self.lengths[end_index-1]-self.alpha_radii[end_index]) + alpha_a = min( + self.lengths[end_index-2], self.lengths[end_index-1]-self.alpha_radii[end_index]) current_radius = max(self.tangente[end_index-1]*alpha_a, self.tangente[end_index] * self.alpha_radii[end_index]) # Radius at final segment @@ -123,9 +192,8 @@ class Polyline: """ Returns the radius that balances the radii on either end segement i. """ - - alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / - (self.tangente[i] + self.tangente[i+1])) + alpha_a = min(self.lengths[i-1], (self.lengths[i] * + self.tangente[i+1])/(self.tangente[i] + self.tangente[i+1])) alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) return alpha_a, alpha_b, min(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 46cc70c..2acf5e8 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -1,21 +1,23 @@ -from typing import List +from typing import List, Union + +import numpy as np + from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE from networks.geometry.Point2D import Point2D -from math import sqrt class Segment2D: def __init__(self, start: Point2D, end: Point2D): self.start = start self.end = end - self.points = [] - self.points_thick = [] + self.points: List[Point2D] = [] + self.points_thick: List[Point2D] = [] self.thickness = None def __repr__(self): - return str(self.points) + return str(f"Segment2D(start: {self.start}, end: {self.end}, points: {self.points})") - def segment(self, start: Point2D = None, end: Point2D = None, overlap: LINE_OVERLAP = LINE_OVERLAP.NONE, _is_computing_thickness: bool = False) -> List[Point2D]: + def segment(self, start: Point2D = None, end: Point2D = None, overlap: LINE_OVERLAP = LINE_OVERLAP.NONE, _is_computing_thickness: bool = False) -> Union[List[Point2D], None]: """Modified Bresenham draw (line) with optional overlap. From: https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp @@ -29,7 +31,7 @@ class Segment2D: >>> Segment2D(Point2D(0, 0), Point2D(10, 15)) """ - if start == None or end == None: + if start is None or end is None: start = self.start.copy() end = self.end.copy() else: @@ -90,6 +92,7 @@ class Segment2D: if not _is_computing_thickness: return self.points + return None def segment_thick(self, thickness: int, thickness_mode: LINE_THICKNESS_MODE) -> List[Point2D]: """Bresenham with thickness. @@ -208,7 +211,7 @@ class Segment2D: self.segment( start, end, overlap=overlap, _is_computing_thickness=True) - return self.points + return self.points_thick def perpendicular(self, distance: int) -> List[Point2D]: """Compute perpendicular points from both side of the segment placed at start level. diff --git a/output_image.png b/output_image.png index d90c85411584c5a3365ac9283c7faac614bb1808..b091c4164b965d6678afc3f5decdfdb2757ebf5d 100644 GIT binary patch literal 1754 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 Date: Sat, 15 Jun 2024 19:58:58 +0200 Subject: [PATCH 61/69] Working debug roads --- main.py | 34 +++++++++++---- networks/geometry/Point2D.py | 26 ++++++++++- networks/geometry/Point3D.py | 64 +++++++++++++++++++-------- networks/geometry/Polyline.py | 31 ++++++------- networks/roads_2/Roads.py | 79 ++++++++++++++++++++++++++++++++++ output_image.png | Bin 1754 -> 1912 bytes 6 files changed, 191 insertions(+), 43 deletions(-) create mode 100644 networks/roads_2/Roads.py diff --git a/main.py b/main.py index 28309d7..b1bfa74 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ +from networks.roads_2.Roads import Road import json import random @@ -25,7 +26,7 @@ from networks.geometry.point_tools import ( from networks.geometry.Polyline import Polyline from networks.geometry.Segment2D import Segment2D from networks.geometry.Segment3D import Segment3D -from networks.roads import Road as Road +# from networks.roads import Road as Road from networks.roads.intersections import Intersection as Intersection matplotlib.use('Agg') @@ -296,9 +297,6 @@ min_val, max_val = -w, w random_points = [Point2D(random.randint(min_val, max_val), random.randint( min_val, max_val)) for _ in range(n_points)] -print(random_points) -print("\n\n") - # random_points = (Point2D(-75, -75), Point2D(0, -75), Point2D(75, 75), # Point2D(75, -50), Point2D(-50, 50), Point2D(0, 0)) @@ -396,9 +394,9 @@ for i in range(0, len(p.arcs)): for i in range(1, len(p.segments)-1): - for j in range(len(p.segments[i].segment())): - draw.point((p.segments[i].points[j].x+w, - w-p.segments[i].points[j].y), fill='white') + for j in range(len(p.segments[i].segment_thick(5, LINE_THICKNESS_MODE.MIDDLE))): + draw.point((p.segments[i].points_thick[j].x+w, + w-p.segments[i].points_thick[j].y), fill='white') for i in range(1, len(p.centers)-1): draw.point((p.centers[i].x+w, w-p.centers[i].y), fill='red') @@ -409,9 +407,29 @@ for i in range(1, len(p.centers)-1): draw.point((p.acrs_intersections[i][2].x+w, w-p.acrs_intersections[i][2].y), fill='blue') - image.save('output_image.png') + +# road = Road([Point3D(-1201, 75, 705), Point3D(-1162, 69, 687), +# Point3D(-1149, 71, 647), Point3D(-1191, 68, 611)], 5) + +# road = Road([Point3D(-1359, 75, 696), Point3D(-1389, 126, 697), +# Point3D(-1401, 126, 714), Point3D(-1426, 126, 707), Point3D(-1452, 126, 714), Point3D(-1430, 126, 765)], 5) + +# road = Road([Point3D(-1203, 73, 718), Point3D(-1157, 76, 719), +# Point3D(-1119, 76, 763), Point3D(-1101, 76, 827), Point3D(-1088, 76, 879), Point3D(-1095, 76, 944)], 10) + +# road = Road([Point3D(-986, 83, 602), Point3D(-1000, 83, 647), +# Point3D(-993, 83, 680), Point3D(-965, 83, 712)], 10) + +# road = Road([Point3D(-984, 97, 811), Point3D(-984, 97, 847), +# Point3D(-962, 97, 860), Point3D(-970, 97, 900), Point3D(-953, 97, 920)], 10) + +road = Road([Point3D(-1024, 106, 1000), Point3D(-1024, 101, 972), + Point3D(-1001, 100, 966), Point3D(-977, 98, 984), Point3D(-966, 102, 1011), Point3D(-905, 97, 1013), Point3D(-774, 99, 998), Point3D(-694, 99, 1047)], 9) + +road.place() + # s = Segment2D(Point2D(-88, -12), Point2D(9, 75)) # s.segment_thick(3, LINE_THICKNESS_MODE.MIDDLE) # print(s.points) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index ef8a804..9848d5b 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -197,9 +197,33 @@ class Point2D: self.coordinates = (self.x, self.y) return self - def distance(self, point: "Point2D") -> int: + def distance(self, point: "Point2D") -> float: return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2) + # def slope(self, point: "Point2D") -> int: + # try: + # slope = (point.y - self.y) / (point.x - self.x) + # return slope + # except ZeroDivisionError: + # return float('inf') + + # def is_between_slopes(self, lower_slope: int, upper_slope: int) -> bool: + # slope = self.slope(Point2D(0, 0)) + # print("sole", slope, (slope <= upper_slope), (slope >= lower_slope), + # ((slope <= upper_slope) and (slope >= lower_slope))) + # return ((slope <= upper_slope) and (slope >= lower_slope)) + + # def is_between_lines(self, point_1: "Point2D", point_2: "Point2D", point_a: "Point2D", point_b: "Point2D") -> bool: + # slope_1, slope_a = point_1.slope(point_2), point_a.slope(point_b) + # lower_slope, upper_slope = min(slope_1, slope_a), max(slope_1, slope_a) + # print(self.is_between_slopes(lower_slope, upper_slope), "slope", + # lower_slope, upper_slope, self.slope(Point2D(0, 0))) + # print(self.x <= max(point_1.x, point_2.x, point_a.x, point_b.x), "x max") + # print(self.x >= min(point_1.x, point_2.x, point_a.x, point_b.x), "x min") + # print(self.y <= max(point_1.y, point_2.y, point_a.y, point_b.y), "y max") + # print(self.y >= min(point_1.y, point_2.y, point_a.y, point_b.y), "y min") + # return self.is_between_slopes(lower_slope, upper_slope) and self.x <= max(point_1.x, point_2.x, point_a.x, point_b.x) and self.x >= min(point_1.x, point_2.x, point_a.x, point_b.x) and self.y <= max(point_1.y, point_2.y, point_a.y, point_b.y) and self.y >= min(point_1.y, point_2.y, point_a.y, point_b.y) + @staticmethod def collinear(p0: "Point2D", p1: "Point2D", p2: "Point2D") -> bool: # https://stackoverflow.com/questions/9608148/python-script-to-determine-if-x-y-coordinates-are-colinear-getting-some-e diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index 75fb36c..dcbca41 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -1,5 +1,6 @@ from math import sqrt -from typing import List +from typing import List, Union +from networks.geometry.Point2D import Point2D import numpy as np @@ -72,23 +73,52 @@ class 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 = [] - for point in points: - vectors.append(np.array(point.coordinates)) - - if (len(vectors) == 1): - return vectors[0] - else: + def to_arrays(points: Union[List["Point3D"], "Point3D"]) -> Union[List[np.array], "Point3D"]: + if isinstance(points, list): + vectors = [] + for point in points: + vectors.append(np.array(point.coordinates)) return vectors + else: + return np.array(points.coordinates) @staticmethod - def from_arrays(vectors: List[np.array]) -> List["Point3D"]: - points = [] - for vector in vectors: - points.append(Point3D(vector[0], vector[1], vector[2])) - - if (len(points) == 1): - return points[0] - else: + def from_arrays(vectors: Union[List[np.array], "Point3D"]) -> Union[List["Point3D"], "Point3D"]: + if isinstance(vectors, list): + points = [] + for vector in vectors: + points.append(Point3D(vector[0], vector[1], vector[2])) return points + else: + return Point3D(vectors[0], vectors[1], vectors[2]) + + @staticmethod + def to_2d(points: List["Point3D"], removed_axis: str) -> List[Point2D]: + points_2d = [] + if removed_axis == 'x': + for i in range(len(points)): + points_2d.append(Point2D(points[i].y, points[i].z)) + if removed_axis == 'y': + for i in range(len(points)): + points_2d.append(Point2D(points[i].x, points[i].z)) + if removed_axis == 'z': + for i in range(len(points)): + points_2d.append(Point2D(points[i].x, points[i].y)) + return points_2d + + @staticmethod + def insert_3d(points: List[Point2D], position: str, to_insert: List[int]) -> List["Point3D"]: + points_3d = [] + if position == 'x': + for i in range(len(points)): + points_3d.append( + Point3D(to_insert[i], points[i].x, points[i].y)) + if position == 'y': + for i in range(len(points)): + points_3d.append( + Point3D(points[i].x, to_insert[i], points[i].y)) + if position == 'z': + for i in range(len(points)): + points_3d.append( + Point3D(points[i].x, points[i].y, to_insert[i])) + return points_3d diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 5f320ec..d5892a9 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -47,7 +47,7 @@ class Polyline: # list of tuple of points (first intersection, corresponding corner, last intersection) self.acrs_intersections = [None] * self.length_polyline self.arcs = [[]] * self.length_polyline # list of points - # self.not_arcs = [[]] * self.length_polyline + self.bisectors = [None] * self.length_polyline # For n points, there is n-1 segments. Last element should stays None. self.segments = [None] * \ @@ -106,16 +106,11 @@ class Polyline: for j in range(len(circle.points)): if circle.points[j].is_in_triangle(self.acrs_intersections[i][0], self.acrs_intersections[i][1], self.acrs_intersections[i][2]): self.arcs[i].append(circle.points[j]) - # for j in range(len(circle.points)): - # if (circle.points[j] in Segment2D(self.acrs_intersections[i][0], self.acrs_intersections[i][1]).segment(LINE_OVERLAP.MINOR)): - # self.not_arcs[i].append(circle.points[j]) - # print("hzeh") - # if (circle.points[j] in Segment2D(self.acrs_intersections[i][1], self.acrs_intersections[i][2]).segment(LINE_OVERLAP.MINOR)): - # self.not_arcs[i].append(circle.points[j]) - # print("hzeh") - # if (circle.points[j] in Segment2D(self.acrs_intersections[i][2], self.acrs_intersections[i][0]).segment(LINE_OVERLAP.MINOR)): - # self.not_arcs[i].append(circle.points[j]) - # print("hzeh") + self.bisectors[i] = Point2D.to_arrays( + self.centers[i]) + (self.unit_vectors[i-1] - self.points_array[i]) + + (self.unit_vectors[i]+self.unit_vectors[i-1]) / \ + np.linalg.norm(self.unit_vectors[i]-self.unit_vectors[i-1]) return self.arcs def get_segments(self) -> List[Segment2D]: @@ -133,14 +128,14 @@ class Polyline: self.points_array[0]), self.acrs_intersections[1][0]) # Get segments between arcs - for i in range(2, self.length_polyline - 2): + for i in range(2, self.length_polyline - 1): self.segments[i] = Segment2D(Point2D(self.acrs_intersections[i][0].x, self.acrs_intersections[i][0].y), Point2D( self.acrs_intersections[i-1][-1].x, self.acrs_intersections[i-1][-1].y)) # Get last segment. Index is -2 because last index -1 should be None due to the same list lenght. # For n points, there are n-1 segments. - self.segments[-2] = Segment2D(Point2D.from_arrays( - self.points_array[-1]), self.acrs_intersections[-2][2]) + self.segments[-2] = Segment2D(self.acrs_intersections[-2][2], Point2D.from_arrays( + self.points_array[-1])) return self.segments @@ -205,9 +200,11 @@ class Polyline: self.unit_vectors[j] = self.vectors[j]/self.lengths[j] # Between two segments, there is only one angle - for k in range(1, self.length_polyline-1): - dot = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) - self.tangente[k] = sqrt((1+dot)/(1-dot)) + for i in range(1, self.length_polyline-1): + dot = np.dot(self.unit_vectors[i], self.unit_vectors[i-1]) + self.tangente[i] = sqrt((1+dot)/(1-dot)) + # self.bisectors[i] = (self.unit_vectors[i]+self.unit_vectors[i-1]) / \ + # np.linalg.norm(self.unit_vectors[i]-self.unit_vectors[i-1]) def _compute_alpha_radii(self): self.alpha_radii[0] = 0 diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py new file mode 100644 index 0000000..1d7b16a --- /dev/null +++ b/networks/roads_2/Roads.py @@ -0,0 +1,79 @@ +import json +from typing import List +from networks.geometry.Polyline import Polyline +from networks.geometry.Segment2D import Segment2D +from networks.geometry.Segment3D import Segment3D +from networks.geometry.Point3D import Point3D +from networks.geometry.Point2D import Point2D +from networks.geometry.Circle import Circle +from Enums import LINE_THICKNESS_MODE +from gdpc import Block, Editor, geometry + + +class Road: + def __init__(self, coordinates: List[Point3D], width: int): + self.coordinates = coordinates + self.output_block = [] + # with open(road_configuration) as f: + # self.road_configuration = json.load(f) + # self.width = self.road_configuration["width"] + self.width = width + + self.polyline = Polyline(Point3D.to_2d(coordinates, 'y')) + self.surface() + + # for i in range(1, len(self.polyline.segments)-1): + # print(self._y_interpolation(self.polyline.segments[i].segment_thick( + # self.width, LINE_THICKNESS_MODE.MIDDLE))) + # self._y_interpolation(self.polyline.segments[i].segment()) + + def surface(self): + # Segments + for i in range(1, len(self.polyline.segments)-1): + print() + if len(self.polyline.segments[i].segment()) > 1: + for j in range(len(self.polyline.segments[i].segment_thick(self.width, LINE_THICKNESS_MODE.MIDDLE))): + self.output_block.append( + (Point3D.insert_3d([self.polyline.segments[i].points_thick[j]], 'y', [170])[0].coordinates, Block("stone"))) + + for i in range(1, len(self.polyline.centers)-1): + # Circle + + circle = Circle(self.polyline.centers[i]) + circle.circle_thick(int( + (self.polyline.radii[i]-self.width/2)), int((self.polyline.radii[i]+self.width/2)-1)) + + # Better to do here than drawing circle arc inside big triangle! + double_point_a = Point2D.from_arrays(Point2D.to_arrays(self.polyline.acrs_intersections[i][0]) + 5 * (Point2D.to_arrays( + self.polyline.acrs_intersections[i][0]) - Point2D.to_arrays(self.polyline.centers[i]))) + double_point_b = Point2D.from_arrays(Point2D.to_arrays(self.polyline.acrs_intersections[i][2]) + 5 * (Point2D.to_arrays( + self.polyline.acrs_intersections[i][2]) - Point2D.to_arrays(self.polyline.centers[i]))) + + for j in range(len(circle.points_thick)): + if circle.points_thick[j].is_in_triangle(double_point_a, self.polyline.centers[i], double_point_b): + self.output_block.append( + (Point3D.insert_3d([circle.points_thick[j]], 'y', [ + 170+i])[0].coordinates, Block("white_concrete"))) + + # v = Point2D.to_arrays( + # self.polyline.centers[i]) - self.polyline.bisectors[i] + + # print(self.polyline.centers[i], Point2D.from_arrays(v).round()) + # # s = Segment2D( + # # self.polyline.centers[i], Point2D.from_arrays(v).round()) + # # s.segment() + # arc = Point2D.to_arrays(self.polyline.acrs_intersections[i][0]) + # s = Segment2D( + # self.polyline.centers[i], Point2D.from_arrays(arc)) + # s.segment() + + # for j in range(len(s.points)): + # self.output_block.append( + # (Point3D.insert_3d([s.points[j]], 'y', [ + # 162])[0].coordinates, Block("purple_concrete"))) + + def place(self): + editor = Editor(buffering=True) + for i in range(len(self.output_block)): + editor.placeBlock(self.output_block[i][0], + self.output_block[i][1]) diff --git a/output_image.png b/output_image.png index b091c4164b965d6678afc3f5decdfdb2757ebf5d..ed7d9f205ffda42f732f0c472ede6285fbc11734 100644 GIT binary patch delta 1898 zcmV-w2bK8R4fqa_BYy`!NklTpdg#IUNx7=Ksq0sw|ZQ!p%=f??4V z42z~K#CGw#CS@ z7}oz37Q@1_xPKHD3#)ek3d8zOX8hd!Pfn~S3@ao3Q$mLIgkjMX4C@chp0OBKJGPj> zuu}LktY!>Lq-pkWT%|EAnu1}`6b$QeQA<3);yRecuxJYJVR80M;ytVeY_SBxy2mS6 zom_{-*)xypuoC!uuoJ_gDg1_&m_1J)kj!ya&Tm)=e1F(3-S;AfMK_MBdfIXt7GKVg za6QuIqjnk=J^3xEtu2pX`I$Yno^F|jTg+ov4Lr1%KE}`IQ?)gQMIyPXj_WT~ZMh38 zaSOWrCG{m$9m?Bs7gjVowtR)f%U4%zRf$N9F?+N43X4uX zRh4XkVSjnlUsZNHj>6(PcoV5Y?-RSCngZBVfzZB@dsOj=%*m=DY0 zI+)%6r+NlfCFaBOrttK~7{7i2H^` zs`C-d^}bUL7#2K*<4SPDvbPRe8driF79G^yTOh+yduui`Hm(FTtOlSh0)Jh& z`r;)l%{2GX3@l z4`)L8JDY1^gw;UpX;8akW0?&L!hgQ+=N~MMD}e~h(&ekK9g8s@ZJBw@egX)~wisgw zd-~s~y`Lm7toJe_9Kf1y~ zyvvTd7K(W63A(~+p!s~bt$#`kgtb=swN`Gc5>;WrUA`Jpl~szu!ZNBVljBMlRvT88 zwXNQPKOok@>~~~UC%#K3udo1Cb)_OKwYPG4Qeyg@5e-y?)nICiRU2=30;u+mG!SZ7 zGc=rUR`q0kSQ>8Vwxr~``~^wBIimR+fm$C{gHflac{?|Qs&PwZL4StTuO}&KeVp!R zT*>n5STA3`>iOEvr+%)g5+@U@^Q4w16+*cEUf!w_^I?^r(c5~C$5K_7J&*6`u%s_5 zm%@4LVC{vd>t8#sbDrP&_Yba^npizO9kxwIWf1-~W^-tkO{}(C{OPM|m%KDS>Fb9y zv3}27|A-obN&P|!>wjwOh&olxD)yAHCamUDGH*oj6)$70d5ksn+;Bu`fVvd^Vo$0p zm{`Nl6PG-NfF~A5lxC1&aYX6&_EOmY`hr3nQEOpLtn#GTlM=5^tlAUFB@dUziNz() z09|2mM49p4QkXcq?8zP#6KmV#^U41+CIv*rgZF*kW!pCS++*g&o)D>+STV-^i0U%C z_^2G3t_!5KZ?R%eYC--Gnmy?U_IfNPg<|!aDFQ!B!zS%-v1d}C7Js-c!?J4qrzWS< kVOXHnhb1H4hDzG}16!N8*=lDAO8@`>07*qoM6N<$g6%D-ga7~l delta 1739 zcmV;+1~mEj4%!WnBYy^=Nkl-#?>ghE?5^=td5v_zVOb+yqD0-<-}@5IVHAJtCh z_l9;Gy{WC-{@}_>j1GN;^LJB{e(Q8r$II|Qmpf;V(z*L`MO{N7gwJjz5dRbmOD0=! z9e2-5C>xecyb$2*?Zh%$$GV|;efOuq{Ttk|S$ThS^ZK(L@s;~`T`8=qO%&bk`~8mn zBg?Xs*po;8I)D5BGfm~TY&Un>P(mfg*Ll6<^GSTQWDYv38rHTIlWl!RP~ogs-rTnl zJIlQvtG?|7xBA$U!(gMcqy6$;w>$mVQ^Rj#=@KXROQ_*)qgdbF>m?yLFMsU(E7T@!DM!?*x34&pz2EOT zW6X0{?+})=SB^?bsvo1$o2$@P{Yi0o(?`uuhi;RqwhyJ->L@Hp-5dSSM4b;RC9AzE z+E)ETXMAh&@#uu`KD|&KZctsZT0dKsrP}pY1rPT>MjfWBwjL~$9HwSma6}jD{nDkVm06y^shh1=-eUBV2XdD9ChTcvYs>Kp)~eKU!zseL8d~O6 zwH=U>_N)|!R;JxqH>|3k!<+aJ9@@%eUR7^LwavvlLI@(C5SGlUdh2PE{@5^vR=(up z(0~5{ta|aj>HoNNYn1im^pC>Fcj)Yv$hL-v>08-}rCf`jmgXRgGNN{}F5U-y&qEmf zz#2UBm#g?pF26i&bMd}$a~$(I8~-*>DQ9S5r}4xR!g9(ctj9Q(WeFkrQL`AAb z$4Z5zjDrh#uL@4AoWaA;8eRK~cZQZrWy8w(KxAkQt^LGWUA0ACgR;E4;fg*EZhvs2 zmx;By|0a>P22x{UN#wkF{M(2W*1t+&iFI4CFNn(e`Y7CJ)*$$B>{-2uC7Kx%et-O) z0sY_5r?B317S=&OzjhT-s)i-0=bq6uoZ4YY`L>cb0Yk%tC0>3b>m(Y4C3(N8A@-SD zEwzSNL=X$D>iDfD0V#`xiLRjX~ z+HzKv5LOAXZTYK8xW+0+nTyM+go#y-rWmhP31KzlxGLeJu(Z>9d?&SHVwH7lXojkU zuqya2X_Bf?4@m(407RfypVm770000000000000000000000000000000000000000 h000000001(`vX2A^D(GLZkhl9002ovPDHLkV1i3Fb~XS2 From 10083e6d86fed0301f0bfcd1d7f8d4d4d0504a9f Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sat, 15 Jun 2024 21:18:32 +0200 Subject: [PATCH 62/69] Fix lenght bug --- main.py | 23 +++++++++++++++----- networks/geometry/Polyline.py | 38 ++++++++++++++++++++-------------- networks/roads_2/Roads.py | 32 ++++++---------------------- output_image.png | Bin 1912 -> 2041 bytes 4 files changed, 47 insertions(+), 46 deletions(-) diff --git a/main.py b/main.py index b1bfa74..2d4197f 100644 --- a/main.py +++ b/main.py @@ -308,10 +308,13 @@ random_points = [Point2D(random.randint(min_val, max_val), random.randint( random_points = random_points[0].optimized_path(random_points) +print(random_points) + +# random_points = [Point2D(94, 71), Point2D(-12, 54), Point2D(-28, 10), Point2D( +# 0, -33), Point2D(80, -50), Point2D(73, -89), Point2D(-86, -3), Point2D(-82, 92)] + p = Polyline(random_points) -# Point2D(-1156, 378), Point2D(-1220, 359), Point2D(-1265, 290) -# print(p.alpha_radii) radius = p.get_radii() center = p.get_centers() @@ -388,9 +391,9 @@ for j in range(len(s1.points_thick)-1): draw.point((s1.points_thick[j].x+w, w-s1.points_thick[j].y), fill='grey') -for i in range(0, len(p.arcs)): - for j in range(len(p.arcs[i])): - draw.point((p.arcs[i][j].x+w, w-p.arcs[i][j].y), fill='white') +# for i in range(0, len(p.arcs)): +# for j in range(len(p.arcs[i])): +# draw.point((p.arcs[i][j].x+w, w-p.arcs[i][j].y), fill='green') for i in range(1, len(p.segments)-1): @@ -398,6 +401,12 @@ for i in range(1, len(p.segments)-1): draw.point((p.segments[i].points_thick[j].x+w, w-p.segments[i].points_thick[j].y), fill='white') + +def get_color(i): + color = (50+round(i), 50+round(i/5), 50+round(i/3)) + return color + + for i in range(1, len(p.centers)-1): draw.point((p.centers[i].x+w, w-p.centers[i].y), fill='red') draw.point((p.acrs_intersections[i][0].x+w, @@ -407,6 +416,10 @@ for i in range(1, len(p.centers)-1): draw.point((p.acrs_intersections[i][2].x+w, w-p.acrs_intersections[i][2].y), fill='blue') +for i in range(len(p.total_line_output)): + draw.point((p.total_line_output[i].x+w, + w-p.total_line_output[i].y), fill=get_color(i)) + image.save('output_image.png') diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index d5892a9..8a47d64 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -7,8 +7,6 @@ from networks.geometry.Circle import Circle from networks.geometry.Point2D import Point2D from networks.geometry.Segment2D import Segment2D -# from Enums import LINE_THICKNESS_MODE, LINE_OVERLAP - class Polyline: def __init__(self, points: List[Point2D]): @@ -46,8 +44,8 @@ class Polyline: self.centers = [None] * self.length_polyline # c, list of points # list of tuple of points (first intersection, corresponding corner, last intersection) self.acrs_intersections = [None] * self.length_polyline - self.arcs = [[]] * self.length_polyline # list of points - self.bisectors = [None] * self.length_polyline + self.arcs = [[] for _ in range(self.length_polyline)] # list of points + # self.bisectors = [None] * self.length_polyline # For n points, there is n-1 segments. Last element should stays None. self.segments = [None] * \ @@ -64,6 +62,13 @@ class Polyline: self.get_arcs() self.get_segments() + self.total_line_output = [] + # for i in range(1, self.length_polyline-1): + # self.total_line_output.extend(self.segments[i].segment()) + # self.total_line_output.extend(self.arcs[i]) + self.total_line_output.extend(self.segments[1].segment()) + self.total_line_output.extend(self.arcs[2]) + def __repr__(self): return str(self.alpha_radii) @@ -101,16 +106,19 @@ class Polyline: def get_arcs(self) -> List[Point2D]: for i in range(1, self.length_polyline-1): - circle = Circle(self.centers[i]) - circle.circle(self.radii[i]) - for j in range(len(circle.points)): - if circle.points[j].is_in_triangle(self.acrs_intersections[i][0], self.acrs_intersections[i][1], self.acrs_intersections[i][2]): - self.arcs[i].append(circle.points[j]) - self.bisectors[i] = Point2D.to_arrays( - self.centers[i]) + (self.unit_vectors[i-1] - self.points_array[i]) + points = Circle(self.centers[i]).circle(self.radii[i]) - (self.unit_vectors[i]+self.unit_vectors[i-1]) / \ - np.linalg.norm(self.unit_vectors[i]-self.unit_vectors[i-1]) + # Better to do here than drawing circle arc inside big triangle! + double_point_a = Point2D.from_arrays(Point2D.to_arrays(self.acrs_intersections[i][0]) + 5 * (Point2D.to_arrays( + self.acrs_intersections[i][0]) - Point2D.to_arrays(self.centers[i]))) + double_point_b = Point2D.from_arrays(Point2D.to_arrays(self.acrs_intersections[i][2]) + 5 * (Point2D.to_arrays( + self.acrs_intersections[i][2]) - Point2D.to_arrays(self.centers[i]))) + input("---") + for j in range(len(points)): + print(points[j], i, j, len(points)) + print(len(self.arcs[i]), len(self.arcs[i-1])) + if points[j].is_in_triangle(double_point_a, self.centers[i], double_point_b): + self.arcs[i].append(points[j]) return self.arcs def get_segments(self) -> List[Segment2D]: @@ -134,8 +142,8 @@ class Polyline: # Get last segment. Index is -2 because last index -1 should be None due to the same list lenght. # For n points, there are n-1 segments. - self.segments[-2] = Segment2D(self.acrs_intersections[-2][2], Point2D.from_arrays( - self.points_array[-1])) + # self.segments[-2] = Segment2D(self.acrs_intersections[-2][2], Point2D.from_arrays( + # self.points_array[-1])) return self.segments diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py index 1d7b16a..7d5eacd 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Roads.py @@ -20,21 +20,15 @@ class Road: self.width = width self.polyline = Polyline(Point3D.to_2d(coordinates, 'y')) - self.surface() + self._surface() - # for i in range(1, len(self.polyline.segments)-1): - # print(self._y_interpolation(self.polyline.segments[i].segment_thick( - # self.width, LINE_THICKNESS_MODE.MIDDLE))) - # self._y_interpolation(self.polyline.segments[i].segment()) - - def surface(self): + def _surface(self): # Segments for i in range(1, len(self.polyline.segments)-1): - print() if len(self.polyline.segments[i].segment()) > 1: for j in range(len(self.polyline.segments[i].segment_thick(self.width, LINE_THICKNESS_MODE.MIDDLE))): self.output_block.append( - (Point3D.insert_3d([self.polyline.segments[i].points_thick[j]], 'y', [170])[0].coordinates, Block("stone"))) + (Point3D.insert_3d([self.polyline.segments[i].points_thick[j]], 'y', [180])[0].coordinates, Block("stone"))) for i in range(1, len(self.polyline.centers)-1): # Circle @@ -53,24 +47,10 @@ class Road: if circle.points_thick[j].is_in_triangle(double_point_a, self.polyline.centers[i], double_point_b): self.output_block.append( (Point3D.insert_3d([circle.points_thick[j]], 'y', [ - 170+i])[0].coordinates, Block("white_concrete"))) + 180+i])[0].coordinates, Block("black_concrete"))) - # v = Point2D.to_arrays( - # self.polyline.centers[i]) - self.polyline.bisectors[i] - - # print(self.polyline.centers[i], Point2D.from_arrays(v).round()) - # # s = Segment2D( - # # self.polyline.centers[i], Point2D.from_arrays(v).round()) - # # s.segment() - # arc = Point2D.to_arrays(self.polyline.acrs_intersections[i][0]) - # s = Segment2D( - # self.polyline.centers[i], Point2D.from_arrays(arc)) - # s.segment() - - # for j in range(len(s.points)): - # self.output_block.append( - # (Point3D.insert_3d([s.points[j]], 'y', [ - # 162])[0].coordinates, Block("purple_concrete"))) + def _projection(self): + pass def place(self): editor = Editor(buffering=True) diff --git a/output_image.png b/output_image.png index ed7d9f205ffda42f732f0c472ede6285fbc11734..9ce53c84b34f0ab369c66d758e2f23708698ef5d 100644 GIT binary patch delta 2027 zcmVY+rB#)SZ4s8e+JAd;t}ViHhLqBwya>xu zJ2m7=TZCn!?0TGNYq#kGK&R|(qA1GqWyaM^9{}!?v#Oz?q51jwzF%(;OQAE;s)mP# zwzjrf9}*V&%Tq~xX!!2dR_n6OZ4s6&axQ|kw#`c%$9cZj2*R>L&Zm#?ft8xorStqUPx%%j>wyGQ& zSAwvN$kV}r`smHg_4GrxsuF~yUztbP2fa3f2urJ+Rev=`#%^riSChc@M^wAS?~CYnUCK_}aUw z1Yw!t^nXiY@*pg=vKPU*vAvhq?zAr3|Dw==@SD&I?KEau4Q*bzc4@n+V%K9cCYCCF zYpBm2N-z7g2(}QG5;<#V_2gewg0PJ9vMNDXDmWcPRS2tMRaNDBorF~-s!9=7WvHqh zVcErxssv$G#hW0iLRb}G5$r}-Wl>d6gk=TKs(%uMRZ=BE_*a!6tTMa|qAG+{5L>Hk zgjEDp^~GPN*pcdc-Q%hRVVSyj{jN%c@X}=O??O9yGO^sM$E~9+#-*_QxIg^(U-V^N zSbm>(zyEjb%s*yLSXfS< zoqzAHE;bYK?2%~|S1zT@I5|$Aw_<#L9P7u0Z<8Nge!I3V&O?oe1&7^gbiE-f?Eq4{bhUN;x0a z?~8X5Arg^1ZYZf#cUu;oE^P+>snDL2^?%3GN+Oa+r}mmU0DDe_g`C`D{kgP~2=U0& zgofLqo?-?MOPYL?`LE^GBoPl+ofP#{u25Kp{Jp&TP-DE<_mYlgZY+iWEUzU(JlL-) z>am9dKNbGFy!Jq2%P`Sd=e_y7z_}b*`#T!>N-#W_c6PD7XvakG&$=O7R_kXC$ zO<7@;ud47hoSZ!R&W+0@Zyg3zym_x=!kwBtdV#7Om-P#)Mz+asHmJ%6cKs&w>Bijo zWvcQ)#bK2ha&I-}-d?^`-Lb%=K4EEF4bL>@&QX;|tXK-8#<8Wl<5cA(C3~!#EuNb?erD-Q6$b$0Om5!US$Qm1 zSSnV-^V9RE8CU)?vw6QTJ%4KP3nMoTJyb1)*+Y&+(0h5qs&F-YclyLh#+C03Y~C4H zAy61r&mnhl=EUoZpR2o3SVH&2sxh#_FhE%4w^{GcJpbzT&q`hioS=4Mb$_(TunLFP zrI2+n9D{{bW;Oia;0v!@|FrP8!!61uR@x%NDkz$k!VeF=`0@hdDj)-gRctk6T!lu{ z#3HQF=m<+nS>t8xfhl-@540dYw({*u*FGtBLvWOiiN&xAke;w=yjxmmm{?GvU|0i9 zAU5s+Qa-VsT>bvn@jZg~4u1`d^01gzVJaS0&Iv?VVbU!aICKP}ws{yeLp$8>}h$1$s6XsQe=Ynvr%QbrZt z7A&Xf-DaMe*)L@rMV)Q28v07>dyvL)kG9|?-G9YCj^k=?5q7fTkB!4W=EIUwveWX9 z6@SIP@OQyl<|h`sq<=jux%0m;v^-)_Sc0}Zq9iQ(fOUH;qV$7xVF}vuf|!2-fNGZ)frIW4CMqX+<3=GPmuzC)sK5wlh)f@^+vnVXl ztF0vA(FZ5l6_)5Zw`w)oa!!x1?D^TXRugd?izHzS?;PtUme?LzaU4sTUSMK5W{0q( z)`RGFZ$wEUWn|RzooZk+OT)THiga$#mP2e@3U|Uz%ONg>1&0t8lpri9QTAmsaDx8F zCJF04_B=QY000000000000000000000000000000000oE{{YMJlWEApqs#yR002ov JPDHLkV1nu->Z7C3ux>Fdnu1{!+4p_* zE`VWJMPiI>s|v#^6=Mv6ZIxnJ6++6>$rTAp93d6d=pDXucSiM-`Ervx?Ff5vaVbK%}i>6>$GzG(=DHs+_!LW+#`_5O( zRAE>&1;e5#7#2;zuxJW~MN{xYLB9RIT4CS!ZQ~79hZCzv2!St;sq**g9YAun#mKT4 z*8db1!@{z-6n_>At9Jkj!}?EV{M`LdPOK*kDkrPJu^3i6wwS=M zQus2gW(-TDY4&hjr7`}q$17N! zT!+QkGmq=A68Ln~MpxeF_C z3%dO!^(9pu%G+`mRze6Ke~eRJ)uFI0hUHpqRfm$ce1*l!S66LSiAanwd$afoi%vaN zm281wd4JSjRdzd$!s0r37*$nzYmUMq8C_Lkn_j{)IeTi&sOo96Uczc%ti?C05{Bi( z%Tzk9Xg9ktp(KO&ng8aAJMIZLzAV zqonJ9`#1ZAVOh{n)nQUqVmK_^*;A{zs!^N0Wj3q^h%M@@N*ES2wN;7LusmG8(p;5T z4a+>`RSCmFG-PV)oQRtV)c9)j)ZR5LXs%c@N8y!g2M^eaTK(wl800sj5mWg=JD<2imF< zhNa)A8eSgHM#8ea4rbrZl^s{_AFPqE%zG+BXH{Y&ETGv_C4_K2wBN&2Tb0-ds{v4p z*Staq66(Ho{0^0|5Y~=UNx82k&b-dDvVY@h?VShy4Xfel7nNA_r>zh^pO5CM#Ky#` zwfsiUO7Efms>EklwrQwJ(8Dr$`Ksrtm7b%+4$B*qs)S)_(;SDYssuVLlj~qYVq7Ua zF5s~2N%T?mzEcet7CeRHN^rxnw+>nwSArWB9n{`iAj497Yc?}Bt^_o!2B0khU4OXx z;w3E2H22XAFD#R%;TG<=62!0?Xg&v;_pOJEun<9yD}f8k-sP)Xpw9%az``=7EqaK% z3d2&zz_=2uu#ByPhR2m)h2?>baV0=uq1;mo*&bH{6jlSE({L~|uEcg&YQYuX03$39 zXF~Zqn`>c&)j;iOP`hJenGFlVzJKrMA1sY4fe6de<*Tn9i!mN;nR(290tm~t7-I-~ z`roL%pCmA>_dN9xLOj<*Gqi_gX&tR1|>3QLckk9)L1X;`)|U#b0d5(6m>3)*iM_AabvF|5IEhfU(bdf^t}XbcN?9ZX+g z&;GF45{+Sj%EoQ-hs{sag{5TnOp;v_%^beEuo`GM8Rh!`{ZSv~EQGaO{#vL%y23)d z%Z|Dhig@e^y25Ip`FyyoN`DN5wO0DIR&J{jRbjzhz8X@MRf@vGGO8+*<4PD-8&;LI zt=@q@AlAX`cVty3zDp;sumDzdr6Me~w{m$>V)~sC4OE2HU}}q18*g|5sP>LD5NcR6 zG@Nf%^<;fm8gA&eq~yB%1xdd-qWK$vS|3(}QKzSQJ2!)>aZ6@FhJV$sCn;%tobG2_ z$@1%1FJHdu`P$B>ey*w#Cljmlq?RWYLb&~2-l`JwVU?fJ+j@@2QdO5dkMHQPq%SL% z!g=dp?S-i8UpudJp5OZS53ZP+SUo)*woOK55dJl0b7+=LthQVH>8om&yfi-P>xVS4 ze$QP0h#G=P{Xz=sYJcpAI#tao_LQ(DtmacPZ$$AGFJrBFj5YP#a71Z!=3>R35UH40F~N30d zs2rNE3#7Ggv0_hZLH-b$J?RJbdMqY|V)dIT0zXT`Chc#rXHuXRf4D8fvTFUOCa2S3 jSfJL2B_rO3O4|GbTbsDqYG(;c00000NkvXXu0mjfvUjPT From 5b86bdc4a061f1fea06bca7db29cea13e23b90ad Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sat, 15 Jun 2024 21:20:33 +0200 Subject: [PATCH 63/69] Total and ordered full block placement --- networks/geometry/Polyline.py | 12 ++++-------- networks/roads_2/Roads.py | 5 ++--- output_image.png | Bin 2041 -> 3532 bytes 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 8a47d64..e77f3e2 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -63,11 +63,9 @@ class Polyline: self.get_segments() self.total_line_output = [] - # for i in range(1, self.length_polyline-1): - # self.total_line_output.extend(self.segments[i].segment()) - # self.total_line_output.extend(self.arcs[i]) - self.total_line_output.extend(self.segments[1].segment()) - self.total_line_output.extend(self.arcs[2]) + for i in range(1, self.length_polyline-1): + self.total_line_output.extend(self.segments[i].segment()) + self.total_line_output.extend(self.arcs[i]) def __repr__(self): return str(self.alpha_radii) @@ -113,10 +111,8 @@ class Polyline: self.acrs_intersections[i][0]) - Point2D.to_arrays(self.centers[i]))) double_point_b = Point2D.from_arrays(Point2D.to_arrays(self.acrs_intersections[i][2]) + 5 * (Point2D.to_arrays( self.acrs_intersections[i][2]) - Point2D.to_arrays(self.centers[i]))) - input("---") + for j in range(len(points)): - print(points[j], i, j, len(points)) - print(len(self.arcs[i]), len(self.arcs[i-1])) if points[j].is_in_triangle(double_point_a, self.centers[i], double_point_b): self.arcs[i].append(points[j]) return self.arcs diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py index 7d5eacd..da70e72 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Roads.py @@ -1,13 +1,12 @@ import json from typing import List from networks.geometry.Polyline import Polyline -from networks.geometry.Segment2D import Segment2D -from networks.geometry.Segment3D import Segment3D + from networks.geometry.Point3D import Point3D from networks.geometry.Point2D import Point2D from networks.geometry.Circle import Circle from Enums import LINE_THICKNESS_MODE -from gdpc import Block, Editor, geometry +from gdpc import Block, Editor class Road: diff --git a/output_image.png b/output_image.png index 9ce53c84b34f0ab369c66d758e2f23708698ef5d..596dfb9efe0667d3405ff7396c1f4eda58d229c4 100644 GIT binary patch literal 3532 zcma)<^pM1>v{oR}nw{Z&gJT9D|_8z293wDKc&!LW0L}MY+AkxP^I?KOc2?+OfXj+0h zL!M*hp~{8yD2+Cr)&J@fMI)2=xrIdUNCN;h@u-(BgeJ8s|UCwDwd!M zK++#AWx8@;fLseGyT+xBm1mxObb<;< z4QGU%iGu(GMx;USMlu^k92KASGO4?Tp&$^|Q(xTE0yNBP$ozVL9zP>h_qG{H&lB-d z1z=_5kw-uvNxa4W6uyyc&&7l^%`AFr^tBG~+=QL5{IKY!AZ7#C*4L(?Wa0T)nv}+c z{Bp<0^CncNb3%laCbYaDH#6@TX#0KW5>`k<^nLYm9b5Dx|*=klR?xr@>3Nctp)$n-|#TC^Vs_fvRbkEZ5jz4XK*1(UDiIeYz3|6P`N zdQ*YvV0et%;U3>*@h~UK90iSK zi(y9gajWlJYM7D@!SH#;rQos7`A9)N#jHkp{+VEMj0Iyd(@UsgCIXV@iN5{(p$Veh z5U<(0W<{XkQnb(VDXulr?~s1*9SD=s^aHvDRbxZC$_$jKqWP}`kTNXq-2A#sd5|(} z@7!vOIxaTuf#C^mJTFpP58u-`=9Jh1SzOt$ohtwmWD;49-kN7WIJxDI?k`YlJQIneN$Ku4hxZFjjU= z5d4V112jOik0fA84MKNDeORqMFhVBtY3Es2xR6*64`LT>3a zI3yyv&NTU}Vc9Hugh%=A`0My`Bjbe|`!4H-@F1CEo%S@ZE9LMw>fA9e-X`*2CK$v~ zAy^f(I5@}hrBs!jtb@Mz+ng(;f)Bq-g92@b^pfe%%=x*RiBbhxZoX$GATM-Cqx2DY z;f~egfAUA00@DB`&ee>MNu1R-M}tZnsC$D@;h}XLTKa$lLOVIF5Tu3qt0#OVSv@56 zC%ML1xiI0lBAt{dO=@|4F+$5eO;s4@_)R`^b!K3^B?4_)+{s_ePE|~+V4Z*^QN_+) zbS?EFG*&%b&RAgtjZ;-cRYwl1Oug$%2(n_8;$vXDroM;lk1f|cj)f_d3|j;p~TAZ z@BpRZ&eJoAU@4aU&*d>LgWBrTwbjs+v5k%oTeNh=`a#D}%uh$7cwcLXJ(j(Vy{sbN zY-2aD;{o&Pb1RF6_S~{0 zZewj%9Bj)9cu}wgf=_q&W;*YDD75ag{w?Y+S}p=`cGt z^!p^H)r|Hznlk67r+IDp{x|^L@TKT&VheDru~{FO;35&zA>qs#Cz%wbQ?sL871La2FA0XXIDyarC*w$*_6SR`_4Kt1>`_t`#O5PT;1iChBljnX!Zh_AZ$XJyk8y^@jzG#YLDTXLklZ8j7W^Y(57;^=tm0^8UWd-M=sf3hY9 z-ZdKd$#FcgA5sCc74qMLXX0zY{)x4=e#?zxTQ>_GGHpW4V7Hv!B0>D^h^tE_Ej0Im z<%c?No|pJL-23bPdN-GWZNoEbYA-=?m6OB0DzM|rkLz8_?x=C;(Sj`~2c5PS@742A zv;ot88xYKvP`@YB?}MNfwLJ^{$@9)gKW* zEw78i_58`Bx&bbDp${T;OST?ZpWcsgRoIUT4U_W0A*Rm~CfHKtzW!0v=wLl97>j?dCR=<09v6PaO}cs7fb%zs~5w~K!HF+Px zxE$yEMb78qrDD}L+v`5atak%Y3I&IU||7BtUtNrSv z_^y13H9WD0?kmr#6tt7Bb~XsiQh2*m4Mv)#fLI}2J&Ezo-ehHW;}GCNVL`T%(n9?2 z!F*!I1HbbOt1wowNj~`N9dv&&8vSoD#$J{KD`dNB>QEXMsL7{Ty91R7#<`3iT%c6_ z3S5aVZDS)qGIG4+{)(iQYVjk7vWAGJc8aZJrKLW6v^l3Az04CU#lY}Ycap&ab^Jo)c~Tm?ELrwNRJY2HCs!oltLVrs zEq(OA+XLs4LsE&aQ;&TrW5tkt%))DBT_-OYZs4rWW$I3d<6i;K?5(@6cdmWHG)*Zp z5g>_lqUjT20>TRv6n-6FWIgw9hs*JZW2W-JU26Z;vwF_O@l;`S{^v}`Bk1-&Lk^)` zi0u3*Mz~1uAp+KuCLz$g<666pqePnX3G8{3@Yu-gU_#K$7cxx4+c6>TV7yD|enysSTL7hbi(M|Rwt4q4n^(1 zZ*|=ZaLf3!mSiiOdrv_SsHtKIs5jYeJ2LuSY!q(%=WE6WVfiSn0?}FfE4_?2W23p9 zM~npAl`1wD4H7o5|Cg-#b50-twkWZI`w%n`l~Lrd6V+}NCR(Kg;$iKo?DMXPyV&}q zkYbEgg`BK?ix(7&3znCPF5kdB{qZP6$aH?cYOX{;>|usbpy?8AhsaD0xjCRWJX;82N|kZ0uflwm8dg-*W@0m%6L`x_t8~G2ABYy|LNkldRUlDUE3LtXXf0$ z-^xi7dpiE~KPR^12mt^90E9+LY5EX~*k$?vkfl|Xi)|5>y?@$!a;`1Ha)y-Bp}Yvo zQad%|N?U|wqwIQ|Xlu9W13;(jZlWm4^JT`>OdkO5le4O!p`rQt`MzIo5KEym(yE4s zhPJl0S|1V?`pZ*EeQ5aZ)>iAX&215uEpje`wYJSm9LIUS*9gM0Le8p2hle-r-fdk@ ztIBf?`Vp3SIe)7f8?LW!S7krKvRYcz_;7vg?q>S=j&Wrn!ZIw+5;iehU(HdKRjVOk znUs6Oes9N~F{=m*LgcP$Uw!1x=0@wXD^&@?G9_nK`|BgOx2tjsVOb}w>bd&pt+uKh z8drj_jL6f$f%@pp&Gqy{x2h6^rC*sx*ay8fg9uBjoPSj{M#gSz{6DT-tV$4;PC2WZ z8W~&OSkrMpDiM|y(yFFM#+KTua&%k?!qO&B2Qwq%i(XVE2uqJZHtg*PONWdU(y9)P zjxVgQwl2F~l^`s$(y9)RPF&lr$`6F4L(egjR&``_;_CWJ`gsq=l^`q)vTK+fo%q_j zssv$~;(zo@V)7s?wXzq%xv{;M*Y30~+yA1_f$*Eq3hgvzS`BSpxpry0s$$n;GbWZQ zeQT)C9!f9!viLPEyks={J1~-_+Rv8 zU08mfcfbF4?ab`4d^^8V2xRZk(iBNkh-Q-PIXo>?&aIu=3a~2S zUs%6fztt3?DUxQAJbmy$_ZP%*th6l=YDpdZSqgt!yqyT~#PmKRx88AP(hqGuV@f$6 z*6)jV5+M?iJZ>ndRCikzo-S<${;ANOlYjNc(n=zdN2m6hIski4g@v5lWBs|bk_hq0 z)P#oHqMl*~4@;VSl=-ja)g%!QSDh5~RIX51hWx#}`cPxM*!Pl-W^OEn|17U1LOj^7 zD(bO^13wl1yS(;5W6bO^U{JZj(tls3xA@xR?8O^bG~YVP>l2pJq_VI4jmgVvKe0Tc9Nye4$3~b&R zS0PXsR?i`KapuJ9i=V5zQCLFv#Hul{!Z1Ks<+oYy&piL?_0LLP37nvIVt;kC$gm2B z)}@ejFdT!0Rc1B(;NT0dT>rH2x5F*UCsx`b!zw76mckDYzWDM2<0>EnhgEDfWL$+t z)5IdI(C7$DN?GG&?SUzHe-E@EKDP4hOV>Urc0+KKj)}#v3Xq<#YP?%oXqZ?~qF`79 zO&~Vz0a8A(o?QL@*YQ1q_kRuzjPkIUR$(e0R?Z1TSYgsIu}CXK>ceX5ifvXnN`;jt z6y{Z!SPG$p@~}Ss^XK*tZCec!(+A+I&>wEF{P5gWHmtrAh=<+7WMGeF+iD0ELSG7@ zNCjc>jz#FEd}8hDwaH>N^q%^#m{~!pCajDJ^vvO@AFeJ{aw~9%j(=B>_FN6&H!WfH zS`FD|`K_|BGD6wKzzUDCi3Pvu2D^|Yn%OU997UaNu^ReH>wA#KagVm(CEb6;K91vRZxME~;*X8PKjy=dQnJ(X zj}?E#zVLU!TIMGfynm!UEV=W)Ftj{kQCNbuJfb8l`hazNETZ&-bzuqG@`9PaV&8%1 z5dC9*VzsK`IYiHx3@eJFjMvgIw4A2)1$Y^8^c+#$Pf?SXql`_go-cD?LG*_5mkbX& zv_w~P?bpz8ZOk)k>3A)A`314SHNz~!KNYed`by!%%0EsD&wni@mbG@5{C@6Q8(4KO zv<{~K#L{!nB{y5Jij>l#x9gxSZ%8T4Kc$nc&qiKqIt&cTqOf`nr#^43CDj}XO0y^| z(W|W_;n4>t*%g-PIk##x+Hy{huPRk)Kg$0KY7L*_?C{gxhGjM|b o$0iBuKK48~43VK47!auc0L$@{X~@E(%m4rY07*qoM6N<$g7z2d#Q*>R From 2619aeee3892e02b2be19bc99a7a5ad3fd93c6f8 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sat, 15 Jun 2024 21:36:52 +0200 Subject: [PATCH 64/69] Fix length issue on last segment --- networks/geometry/Polyline.py | 7 +++---- networks/roads_2/Roads.py | 7 ++++++- output_image.png | Bin 3532 -> 3419 bytes 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index e77f3e2..ea801de 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -126,7 +126,6 @@ class Polyline: list[Segment2D]: List of segments in order. """ # Get first segment. - # segments index is 0, corresponding to the first points_array to the first point ([0]) of the first arc (acrs_intersections[1]). # First arc index is 1 because index 0 is None due to fix list lenght. Is it a good choice? self.segments[1] = Segment2D(Point2D.from_arrays( self.points_array[0]), self.acrs_intersections[1][0]) @@ -136,10 +135,10 @@ class Polyline: self.segments[i] = Segment2D(Point2D(self.acrs_intersections[i][0].x, self.acrs_intersections[i][0].y), Point2D( self.acrs_intersections[i-1][-1].x, self.acrs_intersections[i-1][-1].y)) - # Get last segment. Index is -2 because last index -1 should be None due to the same list lenght. + # Why -3? # For n points, there are n-1 segments. - # self.segments[-2] = Segment2D(self.acrs_intersections[-2][2], Point2D.from_arrays( - # self.points_array[-1])) + self.segments[-3] = Segment2D(self.acrs_intersections[-2][2], Point2D.from_arrays( + self.points_array[-1])) return self.segments diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py index da70e72..2faa7c1 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Roads.py @@ -20,6 +20,7 @@ class Road: self.polyline = Polyline(Point3D.to_2d(coordinates, 'y')) self._surface() + self._projection() def _surface(self): # Segments @@ -49,7 +50,11 @@ class Road: 180+i])[0].coordinates, Block("black_concrete"))) def _projection(self): - pass + nearest_points_to_reference = [] + for i in range(len(self.coordinates)): + nearest_points_to_reference.append(Point3D.insert_3d([Point3D.to_2d([self.coordinates[i]], 'y')[0].nearest( + self.polyline.total_line_output)], 'y', [self.coordinates[i].y])) + print(nearest_points_to_reference) def place(self): editor = Editor(buffering=True) diff --git a/output_image.png b/output_image.png index 596dfb9efe0667d3405ff7396c1f4eda58d229c4..ce0197cef5ba2cde93773113656cdf5e7b322b31 100644 GIT binary patch delta 3416 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 Date: Sat, 15 Jun 2024 21:48:20 +0200 Subject: [PATCH 65/69] Truly fix length error (probably) --- main.py | 7 +++++-- networks/geometry/Polyline.py | 5 ++++- networks/roads_2/Roads.py | 2 +- output_image.png | Bin 3419 -> 2674 bytes 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 2d4197f..85e7ef2 100644 --- a/main.py +++ b/main.py @@ -306,13 +306,16 @@ random_points = [Point2D(random.randint(min_val, max_val), random.randint( # random_points = [Point2D(-40, -56), Point2D(-94, 92), Point2D(19, -47), Point2D( # 100, 59), Point2D(-85, -73), Point2D(-33, -9), Point2D(57, -25), Point2D(51, -34)] -random_points = random_points[0].optimized_path(random_points) +# random_points = random_points[0].optimized_path(random_points) -print(random_points) +# print(random_points) # random_points = [Point2D(94, 71), Point2D(-12, 54), Point2D(-28, 10), Point2D( # 0, -33), Point2D(80, -50), Point2D(73, -89), Point2D(-86, -3), Point2D(-82, 92)] +random_points = [Point2D(-59, -21), Point2D(-43, -19), Point2D(-61, 19), Point2D( + 45, 19), Point2D(80, -4), Point2D(99, 2), Point2D(47, 63), Point2D(100, -91)] + p = Polyline(random_points) diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index ea801de..e495466 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -61,11 +61,14 @@ class Polyline: self.get_arcs_intersections() self.get_arcs() self.get_segments() + print("\nlekj\n", self.segments, "\nklj\n") self.total_line_output = [] for i in range(1, self.length_polyline-1): self.total_line_output.extend(self.segments[i].segment()) self.total_line_output.extend(self.arcs[i]) + self.total_line_output.extend( + self.segments[self.length_polyline-1].segment()) def __repr__(self): return str(self.alpha_radii) @@ -137,7 +140,7 @@ class Polyline: # Why -3? # For n points, there are n-1 segments. - self.segments[-3] = Segment2D(self.acrs_intersections[-2][2], Point2D.from_arrays( + self.segments[-1] = Segment2D(self.acrs_intersections[-2][2], Point2D.from_arrays( self.points_array[-1])) return self.segments diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py index 2faa7c1..d9d8603 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Roads.py @@ -53,7 +53,7 @@ class Road: nearest_points_to_reference = [] for i in range(len(self.coordinates)): nearest_points_to_reference.append(Point3D.insert_3d([Point3D.to_2d([self.coordinates[i]], 'y')[0].nearest( - self.polyline.total_line_output)], 'y', [self.coordinates[i].y])) + self.polyline.total_line_output)], 'y', [self.coordinates[i].y])[0]) print(nearest_points_to_reference) def place(self): diff --git a/output_image.png b/output_image.png index ce0197cef5ba2cde93773113656cdf5e7b322b31..9564ae9fb5cadf6a68f5a079a0a5d07e1068a561 100644 GIT binary patch delta 2666 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 delta 3417 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 From 0dc29bc7c77d4d0462be5b7e23d5c960e0fb4f58 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Sat, 15 Jun 2024 22:44:52 +0200 Subject: [PATCH 66/69] Working first step projection --- main.py | 8 ++++---- networks/geometry/Point2D.py | 5 ++++- networks/geometry/Polyline.py | 1 - networks/roads_2/Roads.py | 9 ++++++--- output_image.png | Bin 2674 -> 3905 bytes 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index 85e7ef2..a229945 100644 --- a/main.py +++ b/main.py @@ -306,15 +306,15 @@ random_points = [Point2D(random.randint(min_val, max_val), random.randint( # random_points = [Point2D(-40, -56), Point2D(-94, 92), Point2D(19, -47), Point2D( # 100, 59), Point2D(-85, -73), Point2D(-33, -9), Point2D(57, -25), Point2D(51, -34)] -# random_points = random_points[0].optimized_path(random_points) +random_points = random_points[0].optimized_path(random_points) -# print(random_points) +print(random_points) # random_points = [Point2D(94, 71), Point2D(-12, 54), Point2D(-28, 10), Point2D( # 0, -33), Point2D(80, -50), Point2D(73, -89), Point2D(-86, -3), Point2D(-82, 92)] -random_points = [Point2D(-59, -21), Point2D(-43, -19), Point2D(-61, 19), Point2D( - 45, 19), Point2D(80, -4), Point2D(99, 2), Point2D(47, 63), Point2D(100, -91)] +# random_points = [Point2D(-59, -21), Point2D(-43, -19), Point2D(-61, 19), Point2D( +# 45, 19), Point2D(80, -4), Point2D(99, 2), Point2D(47, 63), Point2D(100, -91)] p = Polyline(random_points) diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 9848d5b..d748303 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -72,7 +72,7 @@ class Point2D: else: return (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= d - def nearest(self, points: List["Point2D"]) -> "Point2D": + def nearest(self, points: List["Point2D"], return_index: bool = False) -> Union["Point2D", List[Union["Point2D", int]]]: """Return the nearest point. If multiple nearest point, returns the first in the list. Args: @@ -81,6 +81,9 @@ class Point2D: Returns: Point2D: The nearest point, and if multiple, the first in the list. """ + if return_index: + return min( + enumerate(points), key=lambda pair: self.distance(pair[1])) return min(points, key=lambda point: self.distance(point)) def optimized_path(self, points: List["Point2D"]) -> List["Point2D"]: diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index e495466..9d067cb 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -61,7 +61,6 @@ class Polyline: self.get_arcs_intersections() self.get_arcs() self.get_segments() - print("\nlekj\n", self.segments, "\nklj\n") self.total_line_output = [] for i in range(1, self.length_polyline-1): diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py index d9d8603..23cc062 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Roads.py @@ -52,9 +52,12 @@ class Road: def _projection(self): nearest_points_to_reference = [] for i in range(len(self.coordinates)): - nearest_points_to_reference.append(Point3D.insert_3d([Point3D.to_2d([self.coordinates[i]], 'y')[0].nearest( - self.polyline.total_line_output)], 'y', [self.coordinates[i].y])[0]) - print(nearest_points_to_reference) + # nearest_points_to_reference.append(Point3D.insert_3d([Point3D.to_2d([self.coordinates[i]], 'y')[0].nearest( + # self.polyline.total_line_output, return_index=True)], 'y', [self.coordinates[i].y])[0]) + index, point = Point3D.to_2d([self.coordinates[i]], 'y')[0].nearest( + self.polyline.total_line_output, return_index=True) + nearest_points_to_reference.append( + Point2D(index, self.coordinates[i].y)) def place(self): editor = Editor(buffering=True) diff --git a/output_image.png b/output_image.png index 9564ae9fb5cadf6a68f5a079a0a5d07e1068a561..a07f8a0bbcfec22cd522b25342350d2424262a99 100644 GIT binary patch literal 3905 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 Date: Sun, 16 Jun 2024 00:00:43 +0200 Subject: [PATCH 67/69] Working road projection (with bug) --- main.py | 12 +++++++++--- networks/roads_2/Roads.py | 32 ++++++++++++++++++++++++++++---- output_image.png | Bin 3905 -> 3730 bytes 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/main.py b/main.py index a229945..b05bebd 100644 --- a/main.py +++ b/main.py @@ -441,10 +441,16 @@ image.save('output_image.png') # road = Road([Point3D(-984, 97, 811), Point3D(-984, 97, 847), # Point3D(-962, 97, 860), Point3D(-970, 97, 900), Point3D(-953, 97, 920)], 10) -road = Road([Point3D(-1024, 106, 1000), Point3D(-1024, 101, 972), - Point3D(-1001, 100, 966), Point3D(-977, 98, 984), Point3D(-966, 102, 1011), Point3D(-905, 97, 1013), Point3D(-774, 99, 998), Point3D(-694, 99, 1047)], 9) +# road = Road([Point3D(-1024, 106, 1000), Point3D(-1024, 101, 972), +# Point3D(-1001, 100, 966), Point3D(-977, 98, 984), Point3D(-966, 102, 1011), Point3D(-905, 97, 1013), Point3D(-774, 99, 998), Point3D(-694, 99, 1047)], 9) -road.place() +# road = Road([Point3D(-745, 125, 899), Point3D(-744, 117, 944), +# Point3D(-696, 112, 941), Point3D(-645, 112, 979)], 9) + +# road = Road([Point3D(-454, 130, 1046), Point3D(-497, 127, 1070), +# Point3D(-545, 85, 1019), Point3D(-545, 85, 970), Point3D(-457, 87, 865)], 9) + +# road.place() # s = Segment2D(Point2D(-88, -12), Point2D(9, 75)) # s.segment_thick(3, LINE_THICKNESS_MODE.MIDDLE) diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py index 23cc062..2a0a274 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Roads.py @@ -17,18 +17,28 @@ class Road: # self.road_configuration = json.load(f) # self.width = self.road_configuration["width"] self.width = width + self.polyline_height = None self.polyline = Polyline(Point3D.to_2d(coordinates, 'y')) - self._surface() + self.polyline_total_line_output = [ + [] for _ in range(len(self.polyline.total_line_output))] + self.index_factor = 0 + self._projection() + self._surface() + + print(self.polyline_total_line_output) def _surface(self): # Segments - for i in range(1, len(self.polyline.segments)-1): + for i in range(1, len(self.polyline.segments)): if len(self.polyline.segments[i].segment()) > 1: for j in range(len(self.polyline.segments[i].segment_thick(self.width, LINE_THICKNESS_MODE.MIDDLE))): + # Get nearest in x,z projection + nearest = self.polyline.segments[i].points_thick[j].nearest( + Point3D.to_2d(self.polyline_total_line_output, removed_axis='y'), True) self.output_block.append( - (Point3D.insert_3d([self.polyline.segments[i].points_thick[j]], 'y', [180])[0].coordinates, Block("stone"))) + (Point3D.insert_3d([self.polyline.segments[i].points_thick[j]], 'y', [self.polyline_total_line_output[nearest[0]].y])[0].coordinates, Block("stone"))) for i in range(1, len(self.polyline.centers)-1): # Circle @@ -45,9 +55,11 @@ class Road: for j in range(len(circle.points_thick)): if circle.points_thick[j].is_in_triangle(double_point_a, self.polyline.centers[i], double_point_b): + nearest = circle.points_thick[j].nearest( + Point3D.to_2d(self.polyline_total_line_output, removed_axis='y'), True) self.output_block.append( (Point3D.insert_3d([circle.points_thick[j]], 'y', [ - 180+i])[0].coordinates, Block("black_concrete"))) + self.polyline_total_line_output[nearest[0]].y])[0].coordinates, Block("white_concrete"))) def _projection(self): nearest_points_to_reference = [] @@ -59,6 +71,18 @@ class Road: nearest_points_to_reference.append( Point2D(index, self.coordinates[i].y)) + self.polyline_height = Polyline(nearest_points_to_reference) + + self.index_factor = len( + self.polyline_height.total_line_output)/len(self.polyline.total_line_output) + + for i in range(len(self.polyline.total_line_output)): + self.polyline_total_line_output[i] = Point3D( + self.polyline.total_line_output[i].x, self.polyline_height.total_line_output[round(i*self.index_factor)].y, self.polyline.total_line_output[i].y) + + self.polyline_total_line_output = self.polyline_total_line_output[0].optimized_path( + self.polyline_total_line_output) + def place(self): editor = Editor(buffering=True) for i in range(len(self.output_block)): diff --git a/output_image.png b/output_image.png index a07f8a0bbcfec22cd522b25342350d2424262a99..bf1473ca53f5f11b02d99f42074c0751b1d45c4c 100644 GIT binary patch literal 3730 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 wM`=6(`m8YRRNb^Li697qAP9mW2!dqc|FZm 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 Date: Sun, 16 Jun 2024 00:24:39 +0200 Subject: [PATCH 68/69] Fix breaking height partially --- main.py | 5 ++++- networks/geometry/Polyline.py | 3 +++ networks/roads_2/Roads.py | 3 +-- output_image.png | Bin 3730 -> 3511 bytes 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index b05bebd..20b6a2a 100644 --- a/main.py +++ b/main.py @@ -450,7 +450,10 @@ image.save('output_image.png') # road = Road([Point3D(-454, 130, 1046), Point3D(-497, 127, 1070), # Point3D(-545, 85, 1019), Point3D(-545, 85, 970), Point3D(-457, 87, 865)], 9) -# road.place() +road = Road(Point3D.insert_3d(random_points, 'y', [random.randint( + 200, 250) for _ in range(n_points)]), 15) + +road.place() # s = Segment2D(Point2D(-88, -12), Point2D(9, 75)) # s.segment_thick(3, LINE_THICKNESS_MODE.MIDDLE) diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 9d067cb..d7cbac5 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -69,6 +69,9 @@ class Polyline: self.total_line_output.extend( self.segments[self.length_polyline-1].segment()) + self.total_line_output = self.total_line_output[0].optimized_path( + self.total_line_output) + def __repr__(self): return str(self.alpha_radii) diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py index 2a0a274..af467fb 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Roads.py @@ -27,10 +27,9 @@ class Road: self._projection() self._surface() - print(self.polyline_total_line_output) - def _surface(self): # Segments + for i in range(1, len(self.polyline.segments)): if len(self.polyline.segments[i].segment()) > 1: for j in range(len(self.polyline.segments[i].segment_thick(self.width, LINE_THICKNESS_MODE.MIDDLE))): diff --git a/output_image.png b/output_image.png index bf1473ca53f5f11b02d99f42074c0751b1d45c4c..05a9418ef22e14597be6a17b123e29d65ea3f960 100644 GIT binary patch delta 3510 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 wM`=6(`m8YRRNb^Li697qAP9mW2!dqc|FZ Date: Sun, 16 Jun 2024 00:51:57 +0200 Subject: [PATCH 69/69] Working! --- networks/roads_2/Roads.py | 2 +- output_image.png | Bin 3511 -> 2610 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/networks/roads_2/Roads.py b/networks/roads_2/Roads.py index af467fb..99b9b0d 100644 --- a/networks/roads_2/Roads.py +++ b/networks/roads_2/Roads.py @@ -31,7 +31,7 @@ class Road: # Segments for i in range(1, len(self.polyline.segments)): - if len(self.polyline.segments[i].segment()) > 1: + if len(self.polyline.segments[i].segment()) > 2: for j in range(len(self.polyline.segments[i].segment_thick(self.width, LINE_THICKNESS_MODE.MIDDLE))): # Get nearest in x,z projection nearest = self.polyline.segments[i].points_thick[j].nearest( diff --git a/output_image.png b/output_image.png index 05a9418ef22e14597be6a17b123e29d65ea3f960..73166064be9a34f2c8a44a449dc63d597d4d85ca 100644 GIT binary patch delta 2562 zcmV+d3jOuB8?qFTBYz3`Nkl?UquC`WqiEpv6stoZ|>f`yL--a_U!lb!ihJzyU*SI-shZq?hD=v5dZ)H0DxwayOE(H zk+2MZh|P7~;LpC~n-$470T~q%pyb&aoACaS6{+;0=+Lq3lsrZWw}6gi1I^ti3UsXO z(H5g5J3_Eb$KH+N6@Xxwg*`?EE|Gir4K)^qU~w`fM$yz%Ehg{!WGhAqAy}&9Dn{iH ztaPZ3k>fbUpFPi0y026qSaIl#QMrFVdq@(0Nhu|fB$6a0(Nzm?Ra#Q54_Q^Pay}45 z7oMS@Ee&@|Ns>qqed5krN`8C(z#sgCUEOe7+BL?Zt1Ou;Qy4 zrpK4Gm(Q}lGq@+p#*@GF8{aseh|UfFeqw@b#h%q5SQR;r6Ml9*&kJU&r+HPe3Hd*) zAJ`RTN9vXjT-WVf(4=VN;V^j*6Oyfe>3=-#_n`}gB+1qNJELq4VA8BMmg*L-%M&+@W#-= z&&SGEY|4UVyjx#9Su%1RzG=p&e_3@*^s&b%gw(#r+zo}dA35cZT+h#)7X4m-5wn$+ zR1RRtM$QiXZgSHd+6u_UG^kc*|fpIHT^ge$gGL27fzs(bx3UkTh5Z zrfTuAvr^LF?8*9fZQ#)eiqf-x(s~G1&9Rz~{Y?@L+^9BE#Sfvg$4HL$;wz*oozpDc z!BTWGUbttCBzeoe9wMTBD)w+0_DIDet3@lt$Cdexb$G=ZNp$nxZtg-D)YPu1+WbA; zXUmv`_vd}?zPO^}roG+yOjQgv4}~c@vZ7a9qS*up}Fv33i~@&^n-Y!ZQZOKq)LS&{e!cc zn{I5b_z@k|vlnwG@2|G4pSk;S?kbE^GNP6Dp~L$J1<~B4i$XSkqT!7=Lxc5N`}!~K zLaK-&f5!UI{-*_zAi8V(6cWb3jRUGDS2RP zESDu37p#%?4KsFs{ers+tqP~yExo%1g}GIvUz>IDMR7umG{=?sa=KeJTG{KiI<}%<;zFCBq1xGJToF#0^KU07#-JjrIaF@M~8p%{>G~N zzOXZ2?aEi)d&=9G5F#rrssvlGipMLH&&QMmLsKb0yl?r%#@w;qTy@{1osaR>z$?o8 zEm_PKbFlD#Vvr(#n`pMOmGXJ5>A|l%{_ccd{WRSIz!01r3(d-6Y|9NBj-RhXyRsKs z#}e6D>w_3Lg9R4j3syE4lp&&&gB5Xf`}T?V9=rCcbaxF#;ptdtR@UPR7NTW2hG2d7 z#QTq4d!_#Uf=&#<3eMWG;$SO|U|}500hVBa{n8JAR@KoB!O8;+g`kA)U~PKsgRL{i zY2GR5r#o0^RymEX=p;rZ8Axqni*J{g*r-A|qDb z!9F~}@}_^>dHsJZy9PZNI~LX*Y-Z3geW86=+)aF{5MQ3mvRuM6kSia!zv>0jV+~SYRJhg5}kdb6OBq9Bg4qu)s0~1K`en1#~d9WBJ66Eyz|z867OhF4Kc0c5IQ~ z{4sYGP%}e=6`Yfao*y7vnPX_MN+4UAlZ{|eAzKe~R{>G!3s&+AtwhgeWGgE&)3JP_ zXR}=Muw3&K?n0o7j0KB`ME7RVeLu2)Ww`7G%ZF?k&fH*Cos)^~`{kNPWXB^~%7NOL z8>}cq_a@P`3E47Gwu9vpU7KXbR@t$YyAZfUYp~RPv`cht6kQvUEpyn=v5G|3M%lSd zc5dS?1ZXUG0VxyPzfVN6a~qKm+rO9MmQDU7t%0@az64n3A#mROBnk2+eyDAKkBm&q zmTes?$ot%fMC6C9+!f^l!>3_*o3``Tr~F6l zO?aQNjMQN$WLE50&RbU&zwpf){~ljOcFJ9_ih>=>dHd5uej~dWwYqm1YO+XvgB9eB zKVNc-^Ul>5Gj?&dP-t)B;(v;1vj|b)FSY)$Tj{YeF)3KCL5Ihy`zLn^*=EwpyUSew zq{pOSfkhaCW!ej4PyrnalrSP#uIr+WS%&RZA(CV_gTCO*@TeJH9smFU00000007kP Y|C?qmuY8(?`2YX_07*qoM6N<$f>zrFU;qFB delta 3480 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