In order to draw, for instance, roads or walls, we know how to calculate a miter join on 2 connecting lines, resulting in something like this
But is there an algorithm to efficiently calculate points for a nice join when 3 or more lines connect to the same point, like this
The input is a list of segments
NB: The ultimate goal is to triangulate this
Let's say your segments (in red) join in the origin of your cartesian coordinates system. Identify your segments by their angle from an axis of your choice, let's say the x axis.
Draw the walls (in black) around the segment, let's say they have both different width from the red segment, t₁ and t₂.
Now the idea is to find the coordinates of your intersection vector. As you can see, a rhombus immediatly emerges from this geometry. Doing a bit of vector calculation gets you easily to the answer.
Here is a little Python script for illustration, using MatPlotLib:
import matplotlib.pyplot as plt
import math
angles = [1.0, 2.0, 2.5, 3.0, 5.0] # list of angles corresponding to your red segments (middle of your walls)
wall_len = [0.05, 0.1, 0.02, 0.08, 0.1] # list of half-width of your walls
nb_walls = len(angles)
for a in angles:
plt.plot((0, math.cos(a)), (0, math.sin(a)), "r", linewidth=3) # plotting the red segments
for i in range(0, len(angles)):
j = (i + 1)%nb_walls
angle_n = angles[i] # get angle Θ_n
angle_np = angles[j] # get the angle Θ_n+1 (do not forget to loop to the first angle when you get to the last angle in the list)
wall_n = wall_len[i] # get the thickness of the n-th wall t_n
wall_np = wall_len[j] # get the thickness of the n+1-th wall t_n+1
dif_angle = angle_np - angle_n # ΔΘ
t1 = wall_n/math.sin(dif_angle) # width of the rhombus
t2 = wall_np/math.sin(dif_angle) # height of the rhombus
x = t2*math.cos(angle_n) + t1*math.cos(angle_np) # x coordinates of the intersection point
y = t2*math.sin(angle_n) + t1*math.sin(angle_np) # y coordinates of the intersection point
wall_n = [math.cos(angle_n), math.sin(angle_n)] # for plotting n-th wall
wall_np = [math.cos(angle_np), math.sin(angle_np)] # for plotting n+1-th wall
plt.plot((x, wall_n[0] + x), (y, wall_n[1] + y), "k") # plotting the n wall
plt.plot((x, wall_np[0] + x), (y, wall_np[1] + y), "k") # plotting the n+1 wall
plt.show()
Related
There are several questions on this site about distributing points on the surface of a sphere, but all of these are based on actually generating all of the points on that sphere. My favorite thus far is the golden spiral discussed in Evenly distributing n points on a sphere.
I need to cover a sphere in trillions of points, but only ever need to actually generate a tiny region of the surface (earth down to ~10 meters, looking at a roughly 1 km^2 area). The points generated for that region must match the points that would be generated for the entire sphere (i.e., stitching small regions together must yield the same result as generating a larger region), and generation should be pretty fast.
My attempts to use the golden spiral with such a large number of points have been thwarted by floating point precision issues.
The best I've managed to come up with is generating points at equally spaced latitudes and calculating longitudinal spacing based on the circumference at that latitude. The result is far from satisfactory however (especially the resulting horizontal rings of points).
Does anyone have a suggestion for generating a small region of distributed points on the surface of a sphere?
The vertices of a geodesic sphere would work well in this application.
You start with an icosahedron, divide each face into a triangular mesh of whatever resolution you like, and project the points onto the surface of the sphere.
The Fibonacci sphere approximation is quite easy to generalize efficiently to a subset of points computation, as the analytic formulas are very straight-forward.
The below code computes the subset of points shown below for a trillion points in a few seconds of runtime on my weak laptop and a relatively under optimised python implementation.
Code to compute the above is below, and includes a means to verify the subset computation is exactly the same as a brute-force computation (however don't try it for trillion points, it will never finish unless you have a super-computer!)
Please note, the use of 128-bit doubles is an absolute requirement when you do the computation over more than about a billion points as there are major quantisation artefacts otherwise!
Runtime scales with r' * N where r' is the ratio of the subset to that of the full sphere. Thus, a very small r' can be computed very efficiently.
#!/usr/bin/env python3
import argparse
import mpl_toolkits.mplot3d.axes3d as ax3d
import matplotlib.pyplot as plt
import numpy as np
def fibonacci_sphere_pts(num_pts):
ga = (3 - np.sqrt(5)) * np.pi # golden angle
# Create a list of golden angle increments along tha range of number of points
theta = ga * np.arange(num_pts)
# Z is a split into a range of -1 to 1 in order to create a unit circle
z = np.linspace(1 / num_pts - 1, 1 - 1 / num_pts, num_pts)
# a list of the radii at each height step of the unit circle
radius = np.sqrt(1 - z * z)
# Determine where xy fall on the sphere, given the azimuthal and polar angles
y = radius * np.sin(theta)
x = radius * np.cos(theta)
return np.asarray(list(zip(x,y,z)))
def fibonacci_sphere(num_pts):
x,y,z = zip(*fibonacci_sphere_subset(num_pts))
# Display points in a scatter plot
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.scatter(x, y, z)
plt.show()
def fibonacci_sphere_subset_pts(num_pts, p0, r0 ):
"""
Get a subset of a full fibonacci_sphere
"""
ga = (3 - np.sqrt(5)) * np.pi # golden angle
x0, y0, z0 = p0
z_s = 1 / num_pts - 1
z_e = 1 - 1 / num_pts
# linspace formula for range [z_s,z_e] for N points is
# z_k = z_s + (z_e - z_s) / (N-1) * k , for k [0,N)
# therefore k = (z_k - z_s)*(N-1) / (z_e - z_s)
# would be the closest value of k
k = int(np.round((z0 - z_s) * (num_pts - 1) / (z_e - z_s)))
# here a sufficient number of "layers" of the fibonacci sphere must be
# selected to obtain enough points to be a superset of the subset given the
# radius, we use a heuristic to determine the number but it can be obtained
# exactly by the correct formula instead (by choosing an upperbound)
dz = (z_e - z_s) / (num_pts-1)
n_dk = int(np.ceil( r0 / dz ))
dk = np.arange(k - n_dk, k + n_dk+1)
dk = dk[np.where((dk>=0)&(dk<num_pts))[0]]
# NOTE: *must* use long double over regular doubles below, otherwise there
# are major quantization errors in the output for large number of points
theta = ga * dk.astype(np.longdouble)
z = z_s + (z_e - z_s ) / (num_pts-1) *dk
radius = np.sqrt(1 - z * z)
y = radius * np.sin(theta)
x = radius * np.cos(theta)
idx = np.where(np.square(x - x0) + np.square(y-y0) + np.square(z-z0) <= r0*r0)[0]
return x[idx],y[idx],z[idx]
def fibonacci_sphere_subset(num_pts, p0, r0, do_compare=False ):
"""
Display fib sphere subset points and optionally compare against bruteforce computation
"""
x,y,z = fibonacci_sphere_subset_pts(num_pts,p0,r0)
if do_compare:
subset = zip(x,y,z)
subset_bf = fibonacci_sphere_pts(num_pts)
x0,y0,z0 = p0
subset_bf = [ (x,y,z) for (x,y,z) in subset_bf if np.square(x - x0) + np.square(y-y0) + np.square(z-z0) <= r0*r0 ]
subset_bf = np.asarray(subset_bf)
if np.allclose(subset,subset_bf):
print('PASS: subset and bruteforce computation agree completely')
else:
print('FAIL: subset and bruteforce computation DO NOT agree completely')
# Display points in a scatter plot
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.scatter(x, y, z)
plt.show()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="fibonacci sphere")
parser.add_argument(
"numpts", type=int, help="number of points to distribute along sphere"
)
args = parser.parse_args()
# hard-coded point to query with a tiny fixed radius
p0 = (.5,.5,np.sqrt(1. - .5*.5 - .5*.5)) # coordinate of query point representing center of subset, note all coordinates fall between -1 and 1
r0 = .00001 # the radius of the subset, a very small number is chosen as radius of full sphere is 1.0
fibonacci_sphere_subset(int(args.numpts),p0,r0,do_compare=False)
I have a list of point2D that makes a closed polygon. Now I want to create another set of 2D points by offsetting the polygon given an option inside or outside and an offset value. How can I do it?
For every polygon vertex calculate outer bisector vector as sum of normalized normals na and nb of two neighbor edges), then normalize it
bis = na + nb
bis = bis / Length(bis)
Then find needed length of bisector to provide offset distance as
l = d / Sqrt((1 + dotproduct(na,nb))/2)
(derived from l=d/cos(fi/2) and half-angle cosine formula)
And get offset polygon vertex (use minus for inner offset!):
P' = P + l * bis
Added: python implementation here
You need to work with dircetion to be able to define what is outside/inside. Better is to work with to the left/right of the arrow (vector).
In my example the offset is to the right of the vector, now you need to calculate all intersections of the red lines to define the new start-end points of the lines.
Example: P0 = (5,2) & P1 = (2, 1.7)
V1 = -3, -0.3. Rotating clock wise 90deg gives us vector -0.3, 3 (a,b) -> (b, -a)
Divide the vector by 3 (thats about the distance in the drawing) gives us (-0.1, 1)
ofsetting point P0 by the vector gives P0' (5,2) - v(-0.1,1) = (4.9, 3)
If I have the coordinates of the points on the outline of an arbitrary 2D shape, how can I find the coordinates of points composing the outline of a stair step curve, which best represents the original outline, but only use a set of known coordinates (xi, i=1,...,n and yi, i=1,...,m). For example the original triangle is represented by the thick solid blue line. it's different from the matlab stairs function, if my understanding is correct.
matlab code will be nice, but in other language is also ok, algorithm is most important.Thanks.
I'll start by defining a set of sample data based on your plot. Assuming that the pixel centers are aligned at integer values (the convention MATLAB follows) and that the lower left corner is at (0.5, 0.5), here's the data we get:
vx = [1.5; 9.7; 3.7; 1.5]; % X values of triangle vertices
vy = [8.3; 6.0; 1.7; 8.3]; % Y values of triangle vertices
x = 1:10; % X pixel center coordinates
y = 1:9; % Y pixel center coordinates
Note that the vertex coordinates are ordered starting at the top left corner of the triangle and proceeding clockwise, repeating the first vertex at the end to close the polygon.
Getting the mask (the easy part):
There is an easy way to compute the dark gray mask if you have the Image Processing Toolbox: use poly2mask:
mask = poly2mask(vx, vy, numel(y), numel(x));
The algorithm this function uses is discussed here. However, if you'd like to use a pure MATLAB approach that requires no special toolboxes, you can use inpolygon instead:
[cx, cy] = meshgrid(x, y); % Generate a grid of x and y values
mask = inpolygon(cx, cy, vx, vy);
In this case, a pixel is included in the mask as long as its center point lies within the polygon. In this particular example these two approaches yield the same resulting mask, but they won't always due to the differences in their criteria for deciding if a pixel is included or not.
Getting the outline coordinates:
It's a little more involved to get the coordinates of the mask outline, ordered appropriately around the perimeter. To accomplish this, we can represent the mask as a series of vertices and triangular facets (using the triangulation function), then compute the free boundary (i.e. edges that are only present on one triangular facet):
% Create raw triangulation data:
[cx, cy] = meshgrid(x, y);
xTri = bsxfun(#plus, [0; 1; 1; 0], cx(mask).');
yTri = bsxfun(#plus, [0; 0; 1; 1], cy(mask).');
V = [xTri(:) yTri(:)];
F = reshape(bsxfun(#plus, [1; 2; 3; 1; 3; 4], 0:4:(4*nnz(mask)-4)), 3, []).';
% Trim triangulation data:
[V, ~, Vindex] = unique(V, 'rows');
V = V-0.5;
F = Vindex(F);
% Create triangulation and find free edge coordinates:
TR = triangulation(F, V);
freeEdges = freeBoundary(TR).';
xOutline = V(freeEdges(1, [1:end 1]), 1); % Ordered edge x coordinates
yOutline = V(freeEdges(1, [1:end 1]), 2); % Ordered edge y coordinates
And we can plot this like so:
imagesc(x, y, mask);
axis equal
set(gca, 'XLim', [min(x)-0.5 max(x)+0.5], ...
'YLim', [min(y)-0.5 max(y)+0.5], ...
'XTick', x, 'YTick', y, 'YDir', 'normal');
colormap([0.9 0.9 0.9; 0.6 0.6 0.6]);
hold on;
plot(xOutline, yOutline, 'b', 'LineWidth', 2);
plot(xOutline(1), yOutline(1), 'go', 'LineWidth', 2);
plot(vx, vy, 'r', 'LineWidth', 2);
The outline coordinates in xOutline and yOutline are ordered starting from the green circle going counter-clockwise around the mask region.
Seems you need any line rasterization algorithm (that gives coordinate of integer grid points approximating line segment).
Consider Bresenham algortihm or DDA one.
If I have the coordinates of the points on the outline of an arbitrary 2D shape, how can I find the coordinates of points composing the outline of a stair step curve, which best represents the original outline, but only use a set of known coordinates (xi, i=1,...,n and yi, i=1,...,m). For example the original triangle is represented by the thick solid blue line. it's different from the matlab stairs function, if my understanding is correct.
matlab code will be nice, but in other language is also ok, algorithm is most important.Thanks.
I'll start by defining a set of sample data based on your plot. Assuming that the pixel centers are aligned at integer values (the convention MATLAB follows) and that the lower left corner is at (0.5, 0.5), here's the data we get:
vx = [1.5; 9.7; 3.7; 1.5]; % X values of triangle vertices
vy = [8.3; 6.0; 1.7; 8.3]; % Y values of triangle vertices
x = 1:10; % X pixel center coordinates
y = 1:9; % Y pixel center coordinates
Note that the vertex coordinates are ordered starting at the top left corner of the triangle and proceeding clockwise, repeating the first vertex at the end to close the polygon.
Getting the mask (the easy part):
There is an easy way to compute the dark gray mask if you have the Image Processing Toolbox: use poly2mask:
mask = poly2mask(vx, vy, numel(y), numel(x));
The algorithm this function uses is discussed here. However, if you'd like to use a pure MATLAB approach that requires no special toolboxes, you can use inpolygon instead:
[cx, cy] = meshgrid(x, y); % Generate a grid of x and y values
mask = inpolygon(cx, cy, vx, vy);
In this case, a pixel is included in the mask as long as its center point lies within the polygon. In this particular example these two approaches yield the same resulting mask, but they won't always due to the differences in their criteria for deciding if a pixel is included or not.
Getting the outline coordinates:
It's a little more involved to get the coordinates of the mask outline, ordered appropriately around the perimeter. To accomplish this, we can represent the mask as a series of vertices and triangular facets (using the triangulation function), then compute the free boundary (i.e. edges that are only present on one triangular facet):
% Create raw triangulation data:
[cx, cy] = meshgrid(x, y);
xTri = bsxfun(#plus, [0; 1; 1; 0], cx(mask).');
yTri = bsxfun(#plus, [0; 0; 1; 1], cy(mask).');
V = [xTri(:) yTri(:)];
F = reshape(bsxfun(#plus, [1; 2; 3; 1; 3; 4], 0:4:(4*nnz(mask)-4)), 3, []).';
% Trim triangulation data:
[V, ~, Vindex] = unique(V, 'rows');
V = V-0.5;
F = Vindex(F);
% Create triangulation and find free edge coordinates:
TR = triangulation(F, V);
freeEdges = freeBoundary(TR).';
xOutline = V(freeEdges(1, [1:end 1]), 1); % Ordered edge x coordinates
yOutline = V(freeEdges(1, [1:end 1]), 2); % Ordered edge y coordinates
And we can plot this like so:
imagesc(x, y, mask);
axis equal
set(gca, 'XLim', [min(x)-0.5 max(x)+0.5], ...
'YLim', [min(y)-0.5 max(y)+0.5], ...
'XTick', x, 'YTick', y, 'YDir', 'normal');
colormap([0.9 0.9 0.9; 0.6 0.6 0.6]);
hold on;
plot(xOutline, yOutline, 'b', 'LineWidth', 2);
plot(xOutline(1), yOutline(1), 'go', 'LineWidth', 2);
plot(vx, vy, 'r', 'LineWidth', 2);
The outline coordinates in xOutline and yOutline are ordered starting from the green circle going counter-clockwise around the mask region.
Seems you need any line rasterization algorithm (that gives coordinate of integer grid points approximating line segment).
Consider Bresenham algortihm or DDA one.
I'm not sure how to approach this problem. I'm not sure how complex a task it is. My aim is to have an algorithm that generates any polygon. My only requirement is that the polygon is not complex (i.e. sides do not intersect). I'm using Matlab for doing the maths but anything abstract is welcome.
Any aid/direction?
EDIT:
I was thinking more of code that could generate any polygon even things like this:
I took #MitchWheat and #templatetypedef's idea of sampling points on a circle and took it a bit farther.
In my application I need to be able to control how weird the polygons are, ie start with regular polygons and as I crank up the parameters they get increasingly chaotic. The basic idea is as stated by #templatetypedef; walk around the circle taking a random angular step each time, and at each step put a point at a random radius. In equations I'm generating the angular steps as
where theta_i and r_i give the angle and radius of each point relative to the centre, U(min, max) pulls a random number from a uniform distribution, and N(mu, sigma) pulls a random number from a Gaussian distribution, and clip(x, min, max) thresholds a value into a range. This gives us two really nice parameters to control how wild the polygons are - epsilon which I'll call irregularity controls whether or not the points are uniformly space angularly around the circle, and sigma which I'll call spikeyness which controls how much the points can vary from the circle of radius r_ave. If you set both of these to 0 then you get perfectly regular polygons, if you crank them up then the polygons get crazier.
I whipped this up quickly in python and got stuff like this:
Here's the full python code:
import math, random
from typing import List, Tuple
def generate_polygon(center: Tuple[float, float], avg_radius: float,
irregularity: float, spikiness: float,
num_vertices: int) -> List[Tuple[float, float]]:
"""
Start with the center of the polygon at center, then creates the
polygon by sampling points on a circle around the center.
Random noise is added by varying the angular spacing between
sequential points, and by varying the radial distance of each
point from the centre.
Args:
center (Tuple[float, float]):
a pair representing the center of the circumference used
to generate the polygon.
avg_radius (float):
the average radius (distance of each generated vertex to
the center of the circumference) used to generate points
with a normal distribution.
irregularity (float):
variance of the spacing of the angles between consecutive
vertices.
spikiness (float):
variance of the distance of each vertex to the center of
the circumference.
num_vertices (int):
the number of vertices of the polygon.
Returns:
List[Tuple[float, float]]: list of vertices, in CCW order.
"""
# Parameter check
if irregularity < 0 or irregularity > 1:
raise ValueError("Irregularity must be between 0 and 1.")
if spikiness < 0 or spikiness > 1:
raise ValueError("Spikiness must be between 0 and 1.")
irregularity *= 2 * math.pi / num_vertices
spikiness *= avg_radius
angle_steps = random_angle_steps(num_vertices, irregularity)
# now generate the points
points = []
angle = random.uniform(0, 2 * math.pi)
for i in range(num_vertices):
radius = clip(random.gauss(avg_radius, spikiness), 0, 2 * avg_radius)
point = (center[0] + radius * math.cos(angle),
center[1] + radius * math.sin(angle))
points.append(point)
angle += angle_steps[i]
return points
def random_angle_steps(steps: int, irregularity: float) -> List[float]:
"""Generates the division of a circumference in random angles.
Args:
steps (int):
the number of angles to generate.
irregularity (float):
variance of the spacing of the angles between consecutive vertices.
Returns:
List[float]: the list of the random angles.
"""
# generate n angle steps
angles = []
lower = (2 * math.pi / steps) - irregularity
upper = (2 * math.pi / steps) + irregularity
cumsum = 0
for i in range(steps):
angle = random.uniform(lower, upper)
angles.append(angle)
cumsum += angle
# normalize the steps so that point 0 and point n+1 are the same
cumsum /= (2 * math.pi)
for i in range(steps):
angles[i] /= cumsum
return angles
def clip(value, lower, upper):
"""
Given an interval, values outside the interval are clipped to the interval
edges.
"""
return min(upper, max(value, lower))
#MateuszKonieczny here is code to create an image of a polygon from a list of vertices.
vertices = generate_polygon(center=(250, 250),
avg_radius=100,
irregularity=0.35,
spikiness=0.2,
num_vertices=16)
black = (0, 0, 0)
white = (255, 255, 255)
img = Image.new('RGB', (500, 500), white)
im_px_access = img.load()
draw = ImageDraw.Draw(img)
# either use .polygon(), if you want to fill the area with a solid colour
draw.polygon(vertices, outline=black, fill=white)
# or .line() if you want to control the line thickness, or use both methods together!
draw.line(vertices + [vertices[0]], width=2, fill=black)
img.show()
# now you can save the image (img), or do whatever else you want with it.
There's a neat way to do what you want by taking advantage of the MATLAB classes DelaunayTri and TriRep and the various methods they employ for handling triangular meshes. The code below follows these steps to create an arbitrary simple polygon:
Generate a number of random points equal to the desired number of sides plus a fudge factor. The fudge factor ensures that, regardless of the result of the triangulation, we should have enough facets to be able to trim the triangular mesh down to a polygon with the desired number of sides.
Create a Delaunay triangulation of the points, resulting in a convex polygon that is constructed from a series of triangular facets.
If the boundary of the triangulation has more edges than desired, pick a random triangular facet on the edge that has a unique vertex (i.e. the triangle only shares one edge with the rest of the triangulation). Removing this triangular facet will reduce the number of boundary edges.
If the boundary of the triangulation has fewer edges than desired, or the previous step was unable to find a triangle to remove, pick a random triangular facet on the edge that has only one of its edges on the triangulation boundary. Removing this triangular facet will increase the number of boundary edges.
If no triangular facets can be found matching the above criteria, post a warning that a polygon with the desired number of sides couldn't be found and return the x and y coordinates of the current triangulation boundary. Otherwise, keep removing triangular facets until the desired number of edges is met, then return the x and y coordinates of triangulation boundary.
Here's the resulting function:
function [x, y, dt] = simple_polygon(numSides)
if numSides < 3
x = [];
y = [];
dt = DelaunayTri();
return
end
oldState = warning('off', 'MATLAB:TriRep:PtsNotInTriWarnId');
fudge = ceil(numSides/10);
x = rand(numSides+fudge, 1);
y = rand(numSides+fudge, 1);
dt = DelaunayTri(x, y);
boundaryEdges = freeBoundary(dt);
numEdges = size(boundaryEdges, 1);
while numEdges ~= numSides
if numEdges > numSides
triIndex = vertexAttachments(dt, boundaryEdges(:,1));
triIndex = triIndex(randperm(numel(triIndex)));
keep = (cellfun('size', triIndex, 2) ~= 1);
end
if (numEdges < numSides) || all(keep)
triIndex = edgeAttachments(dt, boundaryEdges);
triIndex = triIndex(randperm(numel(triIndex)));
triPoints = dt([triIndex{:}], :);
keep = all(ismember(triPoints, boundaryEdges(:,1)), 2);
end
if all(keep)
warning('Couldn''t achieve desired number of sides!');
break
end
triPoints = dt.Triangulation;
triPoints(triIndex{find(~keep, 1)}, :) = [];
dt = TriRep(triPoints, x, y);
boundaryEdges = freeBoundary(dt);
numEdges = size(boundaryEdges, 1);
end
boundaryEdges = [boundaryEdges(:,1); boundaryEdges(1,1)];
x = dt.X(boundaryEdges, 1);
y = dt.X(boundaryEdges, 2);
warning(oldState);
end
And here are some sample results:
The generated polygons could be either convex or concave, but for larger numbers of desired sides they will almost certainly be concave. The polygons are also generated from points randomly generated within a unit square, so polygons with larger numbers of sides will generally look like they have a "squarish" boundary (such as the lower right example above with the 50-sided polygon). To modify this general bounding shape, you can change the way the initial x and y points are randomly chosen (i.e. from a Gaussian distribution, etc.).
For a convex 2D polygon (totally off the top of my head):
Generate a random radius, R
Generate N random points on the circumference of a circle of Radius R
Move around the circle and draw straight lines between adjacent points on the circle.
As #templatetypedef and #MitchWheat said, it is easy to do so by generating N random angles and radii. It is important to sort the angles, otherwise it will not be a simple polygon. Note that I am using a neat trick to draw closed curves - I described it in here. By the way, the polygons might be concave.
Note that all of these polygons will be star shaped. Generating a more general polygon is not a simple problem at all.
Just to give you a taste of the problem - check out
http://www.cosy.sbg.ac.at/~held/projects/rpg/rpg.html
and http://compgeom.cs.uiuc.edu/~jeffe/open/randompoly.html.
function CreateRandomPoly()
figure();
colors = {'r','g','b','k'};
for i=1:5
[x,y]=CreatePoly();
c = colors{ mod(i-1,numel(colors))+1};
plotc(x,y,c);
hold on;
end
end
function [x,y]=CreatePoly()
numOfPoints = randi(30);
theta = randi(360,[1 numOfPoints]);
theta = theta * pi / 180;
theta = sort(theta);
rho = randi(200,size(theta));
[x,y] = pol2cart(theta,rho);
xCenter = randi([-1000 1000]);
yCenter = randi([-1000 1000]);
x = x + xCenter;
y = y + yCenter;
end
function plotc(x,y,varargin)
x = [x(:) ; x(1)];
y = [y(:) ; y(1)];
plot(x,y,varargin{:})
end
Here is a working port for Matlab of Mike Ounsworth solution. I did not optimized it for matlab. I might update the solution later for that.
function [points] = generatePolygon(ctrX, ctrY, aveRadius, irregularity, spikeyness, numVerts)
%{
Start with the centre of the polygon at ctrX, ctrY,
then creates the polygon by sampling points on a circle around the centre.
Randon noise is added by varying the angular spacing between sequential points,
and by varying the radial distance of each point from the centre.
Params:
ctrX, ctrY - coordinates of the "centre" of the polygon
aveRadius - in px, the average radius of this polygon, this roughly controls how large the polygon is, really only useful for order of magnitude.
irregularity - [0,1] indicating how much variance there is in the angular spacing of vertices. [0,1] will map to [0, 2pi/numberOfVerts]
spikeyness - [0,1] indicating how much variance there is in each vertex from the circle of radius aveRadius. [0,1] will map to [0, aveRadius]
numVerts - self-explanatory
Returns a list of vertices, in CCW order.
Website: https://stackoverflow.com/questions/8997099/algorithm-to-generate-random-2d-polygon
%}
irregularity = clip( irregularity, 0,1 ) * 2*pi/ numVerts;
spikeyness = clip( spikeyness, 0,1 ) * aveRadius;
% generate n angle steps
angleSteps = [];
lower = (2*pi / numVerts) - irregularity;
upper = (2*pi / numVerts) + irregularity;
sum = 0;
for i =1:numVerts
tmp = unifrnd(lower, upper);
angleSteps(i) = tmp;
sum = sum + tmp;
end
% normalize the steps so that point 0 and point n+1 are the same
k = sum / (2*pi);
for i =1:numVerts
angleSteps(i) = angleSteps(i) / k;
end
% now generate the points
points = [];
angle = unifrnd(0, 2*pi);
for i =1:numVerts
r_i = clip( normrnd(aveRadius, spikeyness), 0, 2*aveRadius);
x = ctrX + r_i* cos(angle);
y = ctrY + r_i* sin(angle);
points(i,:)= [(x),(y)];
angle = angle + angleSteps(i);
end
end
function value = clip(x, min, max)
if( min > max ); value = x; return; end
if( x < min ) ; value = min; return; end
if( x > max ) ; value = max; return; end
value = x;
end