Calculating light intensity in a closed world - algorithm

I am building a simulation in which there is world made of many squares. There are also objects designated as "suns", which illuminate the squares and update their "received intensity" each step.
For example:
In this image, the distance between the centres of squares is 32 units, and the received intensity (or R.I.) of each square is calculated with this formula:
R.I. = 100 / (distance between light source and square)^2
Written in a generic(and terrible) programming language, a function that calculates R.I. may look like this:
(define (calc-RI sq-x sq-y sn-x sn-y)
(return (/ 100
(+ (square (- sq-x sn-x))
(square (- sq-y sn-y))
)
)
)
)
...and, to accommodate multiple suns, the R.I. for each sun will be calculated separately and added together for each square. This is all well and good, until I felt the need to introduce a warping mechanic to this simulation: objects that move "outside" the edge of the world will "re-enter" the world from the other side, like in the game Asteroid.
This not only need to apply to other objects in the simulation, but also light.
So, what should the new function be for calculating the R.I. of each square? (Given that the function takes in the x and y coordinate of one square and one sun, and the width and height of the world, what would be the return of the given square's R.I. under the sun's influence?)

You can solve this by imagining that the grid is surrounded by copies of the original grid, one layer for each wrap. For each square in the original grid, calculate the light intensity that falls on each corresponding square, and sum the results.
In the above diagram, the black grid represents the world, and the blue grid represents the copies of the world grid. To find the light intensity for one wrap at the green square, add the simple intensities calculated at each of the blue squares to the simple intensity calculated for the green square. To calculate another wrap, add another layer of copies.
Here is some Lua code that implements this. Stars are represented in tables of the form:
stars = { { x=x1, y=y1 }, { x=x2, y=y2 },... }
In this implementation, a square containing a star is given an intensity of -1. This could be changed so that a square containing a star accumulates intensity like any other square. To use the functions, define a stars table, and call light_universe() with the grid_size (grids are taken to be square here), square_size, stars table, and number of wraps desired. Set wraps to zero, nil, or just omit this parameter, to obtain simple intensity calculations with no wrapping. The function light_universe() returns a table containing intensities for each square of the world grid. You can view the results by calling show_intensities() with the table returned from light_universe(), and the grid_size of the table. Note that the upper-left corner of the world grid has coordinate (1, 1).
The calculate_intensity() function first calculates the field_size of the field of grid copies. This is the size of the blue grid in the diagram, and is an odd multiple of grid_size, e.g., for 1 wrap the field_size is 3. Next, the world coordinates of the current star are transformed to the field coordinates star_x and star_y (the coordinates with respect to the blue grid in the diagram). Then the locations within the field corresponding to x and y are iterated over. These are the locations of the blue squares of the diagram. The first location is in the upper-left grid of the field, and has field coordinates that are equal to the world coordinates of the square of interest. For each location, the intensity is calculated and added to the running total, which is returned when the iteration is complete.
-- Intensity calculation with wraps
-- wraps = nil or wraps = 0 for simple calculation
function calculate_intensity(star, x, y, grid_size, square_size, wraps)
wraps = wraps or 0
local field_size = grid_size * (2 * wraps + 1) -- odd number of grids
local star_x = star.x + wraps * grid_size -- cdts of star in field
local star_y = star.y + wraps * grid_size
local intensity = 0
-- x and y are cdts wrt world grid, but also wrt first grid in field
for field_y = y, field_size, grid_size do
for field_x = x, field_size, grid_size do
local dx = square_size * (star_x - field_x)
local dy = square_size * (star_y - field_y)
local dist_sq = dx * dx + dy * dy
intensity = intensity + 100 / dist_sq
end
end
return intensity
end
function light_universe(grid_size, square_size, stars, wraps)
wraps = wraps or 0
local grid_intensities = {}
for i, star in ipairs(stars) do
for y = 1, grid_size do
grid_intensities[y] = grid_intensities[y] or {}
for x = 1, grid_size do
if x == star.x and y == star.y then
grid_intensities[y][x] = -1
elseif grid_intensities[y][x] ~= -1 then
grid_intensities[y][x] = (grid_intensities[y][x] or 0) +
calculate_intensity(star, x, y, grid_size, square_size, wraps)
end
end
end
end
return grid_intensities
end
function show_intensities(grid, grid_size)
for y = 1, grid_size do
for x = 1, grid_size do
local intensity = grid[y][x]
local fmt
if intensity ~= -1 then
fmt = (string.format("%10.5f", intensity))
else
fmt = string.format("%-10s", " Star")
end
io.write(fmt)
end
print()
end
end
Here is an interaction showing intensity with no wraps, corresponding to the example from your question.
> stars = { { x=1, y=1 } }
> light_grid = light_universe(3, 32, stars, 0)
> show_intensities(light_grid, 3)
Star 0.09766 0.02441
0.09766 0.04883 0.01953
0.02441 0.01953 0.01221
Here is the same situation with one wrap:
> light_grid = light_universe(3, 32, stars, 1)
> show_intensities(light_grid, 3)
Star 0.17054 0.16628
0.17054 0.12440 0.12023
0.16628 0.12023 0.11630
And with two wraps:
> light_grid = light_universe(3, 32, stars, 2)
> show_intensities(light_grid, 3)
Star 0.20497 0.20347
0.20497 0.15960 0.15811
0.20347 0.15811 0.15664
Here is a 7X7 grid with two stars, and one wrap:
> stars = { { x=1, y=1 }, { x=7, y=4 } }
> light_grid = light_universe(7, 32, stars, 1)
> show_intensities(light_grid, 7)
Star 0.13085 0.05729 0.04587 0.04728 0.06073 0.13366
0.14064 0.08582 0.05424 0.04640 0.04971 0.06411 0.09676
0.09676 0.06411 0.04971 0.04640 0.05424 0.08582 0.14064
0.13366 0.06073 0.04728 0.04587 0.05729 0.13085 Star
0.08469 0.05562 0.04574 0.04433 0.05218 0.08190 0.13222
0.06715 0.05619 0.04631 0.04302 0.04635 0.05627 0.06728
0.13073 0.08043 0.05075 0.04294 0.04439 0.05432 0.08347

Related

Algorithm to get all zeniths crossing a box in three dimensions, MATLAB implementation

I have a 3D geometry problem and I am not certain of the best approach to solve it. I have a model with two boxes, one above the others. They have the same dimension, L (length) * p (depth) * e (thickness), and are separated by a height of h. They are perfectly superposed, with no offset between them.
For each point of my bottom box, I want to get the zenith of all lines that can cross the top box and arrive to this point. It doesn't matter if the line crosses the top box by the top or the side.
The zenith is the angle of "looking up". In our case, a zenith of 0 represents the point directly above the point P, and an angle of 90 is directly looking in front. A zenith of 180 would be looking below the point, but for our use, it's useless. The zeniths we look for are between 0 and 90°.
For a more intuitive visualization, let's say that I have a hole in the ceiling, and that I want to map the zenith of all light that crosses this hole and reaches the floor.
This is what it looks like:
For any point P of the bottom box, I want an array containing the zeniths of all "rays" that cross the top box before arriving on P. The red lines are the "edges", the last zeniths I would get for each corner.
I am working on a way to code it in MATLAB and I was wondering if there was a better algorithm that I am not seeing. My approach, in pseudocode, would be this:
bottomBox = [1:L, 1:p, 1:e];
topBox = [1:L, 1:p, 1+h:e+h];
results = zeros(L:p) * NaN; % Array of results, one per "case" on the bottom box
zeniths = zeros(L:p) * NaN; % Array of zeniths for each result case
for i = 1:L
for j = 1:p % Browsing the bottom box case by case
for k = 1:L
for l = 1:p
for m = 1:e % For each bottom box case, browsing the top box case by case
p1 = topBox(k,l,m); % p1 is each case on the top box
p2 = bottomBox(i,j,1); % p2 is the current bottom box case, z doesn't mattter
p3 = topBox(i,j,m); % p3 is the projection of p2 on the top box (zenith = 0)
v1 = p1 - p2;
v2 = p3 - p2;
zeniths(k,l) = rad2deg(atan2(norm(cross(p1, p2)), dot(p1, p2)));
end
end
end
results(i,j) = zeniths;
end
end
I tried to implement this and I couldn't get it to work. More specifically, the angle calculation doesn't seem to work, I have an error stating:
Error using cross;
A and B must be of length 3 in the dimension in which the cross product is taken.
I am looking for advice on how to build the algorithm.
Please tell me if the question is better suited for another StackExchange community, such as Math.
I'll get you started showing you one way to do it for 1 point and I'll let you build the final loop to do the calc for all your points.
As expressed in the comment, for the purpose of these calculations, you do not need to consider the thickness of your plates, you can model them simply with two parallel planes separated by a distance H.
I don't know the size of your plates nor the grid size you want so I'll keep it simple for this example:
H = 5 ; % distance between the planes
[X,Y] = meshgrid(-3:3,-2:2) ;
GridSize = size(X) ;
Zb = zeros(GridSize) ;
Zt = zeros(GridSize) + H ;
This gives you 4 matrices, defining 2 planes. The bottom plane is composed of [X,Y,Zb] and the top plane is formed by [X,Y,Zt].
If you want to visualise them, you can run the following code (optional):
%% Display planes
figure ;
ht = surf(X,Y,Zt, 'FaceColor',[.8 .8 .8],'DisplayName','Top plate') ;
hold on
hb = surf(X,Y,Zb, 'FaceColor',[.6 .6 .6],'DisplayName','Bottom plate') ;
xlabel('X') ; ylabel('Y') ; zlabel('Z') ;
axis equal ; legend show
Now for the rest of the example, I selected a point P, at coordinate [-2,1,0]. This choice is completely arbitrary, just for the example. In your final algorythm you will still have to loop over several points Pi (although remember that your problem is symetric so if your domain is too large you can reduce your computations by using the symetries of your model).
%% This will have to be embedded into a loop over the points Pi
% Assuming points P=(-2,1,0)
p = [-2;1;0] ;
zn = [0;0;1] ; % unitary vector, oriented Oz
dx = X - p(1) ; % `x` distance between all points of the plane and P
dy = Y - p(2) ; % `y` distance between all points of the plane and P
dz = zeros(size(X)) + H ; % `z` distance (all the same)
V = [dx(:) dy(:) dz(:)].' ; % to obtain list of vector V = [dx;dy;dz] ;
nv = size(V,2) ; % number of points/angle to calculate
zenith = zeros(nv,1) ; % preallocate result matrix (always good!)
for k=1:nv
% [u] is the vector going from `P` to the current point considered on the top plane
u = V(:,k) ;
% determine the angle between [u] and [zn]
zenith(k) = atan2( norm(cross(u,zn)) , dot(u,zn) ) ;
end
% Reshape "zenith" from vector to matrix so it matches the base grid system
zenith = reshape( zenith , GridSize ) ;
You now have, for this point P, a matrix of angle with every other point of the top plane:
>> rad2deg(zenith)
ans =
32.31 30.96 32.31 35.80 40.32 45.00 49.39
24.09 21.80 24.09 29.50 35.80 41.81 47.12
15.79 11.31 15.79 24.09 32.31 39.51 45.56
11.31 0 11.31 21.80 30.96 38.66 45.00
15.79 11.31 15.79 24.09 32.31 39.51 45.56
Once again, completely optionally, if you want to visualise the vectors which were used for the calculations:
for k=1:nv
hp(k) = plot3([p(1) X(k)],[p(2) Y(k)],[0 H],'Marker','o','MarkerFaceColor','k') ;
end
will yield:
Now for your final result, remember you have a 2D matrix for each point P of your bottom plane, so your final result will either be a collection of 2D matrices or a large 3D matrix.
Zenith angle is just
atan2(h, sqrt(dx^2+dy^2))
where dx, dy are coordinate differences along L and p axes (i-k and j-l in your loops)
Perhaps h+m (m as your variable for m = 1:e) instead of h if you need points inside top box

Generate random position with uniform distribution inside rounded rect

Can't invent something acceptable.
My first (and sole) approach is pretty awkward:
Calculate area = non_rounded_area + area_of_rounded corner * 4. Let's consider this area as pixel count in the rect.
Get random number from range [0..area), so to say a pixel index.
Somehow get x and y coordinates from that index.
The main embarrassment is how to perform step 3?
I reckon it's even enough to consider 1/4 part of rect (and one corner) and just rotate result for other quarters.
Ok, suppose I know what number of pixels belongs' to the given corner.
And it's easy to get x and y coordinates from index that belongs to non-rounded area.
But how to do this for pixels that belongs to corners?
My thoughts are flying about "determine whether pixel belongs to circle" but can't formulate them plainly.
Here's a way to do it for one quadrant that you can generalize to a full rectangle:
First compute the total number of pixels in the quadrant (red + orange + green):
int totalPixels = w * h;
Then compute the red area (the pixels in the corner that are outside the rounded rect):
int invalidCornerPixels = (int)((float)(r * r) * ((4.0f - PI) / 4.0f));
The orange area is equal to the red area. You can sample pixels in the red + green area, and if they are in the red area, sample a random pixel in the orange area instead.
int redGreenArea = totalPixels - invalidCornerPixels;
Assume randomValue(n) returns a random int from 0 to n - 1:
int pixelIndex = randomValue(redGreenArea);
int pixelX = pixelIndex % w;
int pixelY = pixelIndex / w;
Test if the sampled pixel is in the red area and resample if necessary:
if((pixelX < r) && (pixelY < r))
{
int circleX = r - pixelX;
int circleY = r - pixelY;
if(((circleX * circleX) + (circleY * circleY)) > (r * r))
{
pixelIndex = randomValue(invalidCornerPixels) + redGreenArea;
pixelX = pixelIndex % w;
pixelY = pixelIndex / w;
}
}
This requires a maximum of 2 random number generations (usually only 1), and isn't any more complicated than rejection sampling, because you have to implement the same test for that too. The calculation of totalPixels, invalidCornerPixels and redGreenArea can be done once and stored for a given rectangle.
One weakness is that due to rounding errors the number of pixels that will fail the test in practice may not be exactly equal to invalidCornerPixels, which will give a very slightly non-uniform distribution. You could address this by calculating invalidCornerPixels by brute force offline (counting the pixels that fail the test in an r x r square) and creating a lookup table for each value of r. I doubt it will be noticeable if used for a particle generator however. Another weakness is that it will fail if the red area overlaps the orange area.

Ordering coordinates from top left to bottom right

How can I go about trying to order the points of an irregular array from top left to bottom right, such as in the image below?
Methods I've considered are:
calculate the distance of each point from the top left of the image (Pythagoras's theorem) but apply some kind of weighting to the Y coordinate in an attempt to prioritise points on the same 'row' e.g. distance = SQRT((x * x) + (weighting * (y * y)))
sort the points into logical rows, then sort each row.
Part of the difficulty is that I do not know how many rows and columns will be present in the image coupled with the irregularity of the array of points. Any advice would be greatly appreciated.
Even though the question is a bit older, I recently had a similar problem when calibrating a camera.
The algorithm is quite simple and based on this paper:
Find the top left point: min(x+y)
Find the top right point: max(x-y)
Create a straight line from the points.
Calculate the distance of all points to the line
If it is smaller than the radius of the circle (or a threshold): point is in the top line.
Otherwise: point is in the rest of the block.
Sort points of the top line by x value and save.
Repeat until there are no points left.
My python implementation looks like this:
#detect the keypoints
detector = cv2.SimpleBlobDetector_create(params)
keypoints = detector.detect(img)
img_with_keypoints = cv2.drawKeypoints(img, keypoints, np.array([]), (0, 0, 255),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
points = []
keypoints_to_search = keypoints[:]
while len(keypoints_to_search) > 0:
a = sorted(keypoints_to_search, key=lambda p: (p.pt[0]) + (p.pt[1]))[0] # find upper left point
b = sorted(keypoints_to_search, key=lambda p: (p.pt[0]) - (p.pt[1]))[-1] # find upper right point
cv2.line(img_with_keypoints, (int(a.pt[0]), int(a.pt[1])), (int(b.pt[0]), int(b.pt[1])), (255, 0, 0), 1)
# convert opencv keypoint to numpy 3d point
a = np.array([a.pt[0], a.pt[1], 0])
b = np.array([b.pt[0], b.pt[1], 0])
row_points = []
remaining_points = []
for k in keypoints_to_search:
p = np.array([k.pt[0], k.pt[1], 0])
d = k.size # diameter of the keypoint (might be a theshold)
dist = np.linalg.norm(np.cross(np.subtract(p, a), np.subtract(b, a))) / np.linalg.norm(b) # distance between keypoint and line a->b
if d/2 > dist:
row_points.append(k)
else:
remaining_points.append(k)
points.extend(sorted(row_points, key=lambda h: h.pt[0]))
keypoints_to_search = remaining_points
Jumping on this old thread because I just dealt with the same thing: sorting a sloppily aligned grid of placed objects by left-to-right, top to bottom location. The drawing at the top in the original post sums it up perfectly, except that this solution supports rows with varying numbers of nodes.
S. Vogt's script above was super helpful (and the script below is entirely based on his/hers), but my conditions are narrower. Vogt's solution accommodates a grid that may be tilted from the horizontal axis. I assume no tilting, so I don't need to compare distances from a potentially tilted top line, but rather from a single point's y value.
Javascript below:
interface Node {x: number; y: number; width:number; height:number;}
const sortedNodes = (nodeArray:Node[]) => {
let sortedNodes:Node[] = []; // this is the return value
let availableNodes = [...nodeArray]; // make copy of input array
while(availableNodes.length > 0){
// find y value of topmost node in availableNodes. (Change this to a reduce if you want.)
let minY = Number.MAX_SAFE_INTEGER;
for (const node of availableNodes){
minY = Math.min(minY, node.y)
}
// find nodes in top row: assume a node is in the top row when its distance from minY
// is less than its height
const topRow:Node[] = [];
const otherRows:Node[] = [];
for (const node of availableNodes){
if (Math.abs(minY - node.y) <= node.height){
topRow.push(node);
} else {
otherRows.push(node);
}
}
topRow.sort((a,b) => a.x - b.x); // we have the top row: sort it by x
sortedNodes = [...sortedNodes,...topRow] // append nodes in row to sorted nodes
availableNodes = [...otherRows] // update available nodes to exclude handled rows
}
return sortedNodes;
};
The above assumes that all node heights are the same. If you have some nodes that are much taller than others, get the value of the minimum node height of all nodes and use it instead of the iterated "node.height" value. I.e., you would change this line of the script above to use the minimum height of all nodes rather that the iterated one.
if (Math.abs(minY - node.y) <= node.height)
I propose the following idea:
1. count the points (p)
2. for each point, round it's x and y coordinates down to some number, like
x = int(x/n)*n, y = int(y/m)*m for some n,m
3. If m,n are too big, the number of counts will drop. Determine m, n iteratively so that the number of points p will just be preserved.
Starting values could be in alignment with max(x) - min(x). For searching employ a binary search. X and Y scaling would be independent of each other.
In natural words this would pin the individual points to grid points by stretching or shrinking the grid distances, until all points have at most one common coordinate (X or Y) but no 2 points overlap. You could call that classifying as well.

Algorithm for fitting points to a grid

I have a list of points in 2D space that form an (imperfect) grid:
x x x x
x x x x
x
x x x
x x x x
What's the best way to fit these to a rigid grid (i.e. create a two-dimendional array and work out where each point fits in that array)?
There are no holes in the grid, but I don't know in advance what its dimensions are.
EDIT: The grid is not necessarily regular (not even spacing between rows/cols)
A little bit of an image processing approach:
If you think of what you have as a binary image where the X is 1 and the rest is 0, you can sum up rows and columns, and use a peak finding algorithm to identify peaks which would correspond to x and y lines of the grid:
Your points as a binary image:
Sums of row/columns
Now apply some smoothing technique to the signal (e.g. lowess):
I'm sure you get the idea :-)
Good luck
The best I could come up with is a brute-force solution that calculates the grid dimensions that minimize the error in the square of the Euclidean distance between the point and its nearest grid intersection.
This assumes that the number of points p is exactly equal to the number of columns times the number of rows, and that each grid intersection has exactly one point on it. It also assumes that the minimum x/y value for any point is zero. If the minimum is greater than zero, just subtract the minimum x value from each point's x coordinate and the minimum y value from each point's y coordinate.
The idea is to create all of the possible grid dimensions given the number of points. In the example above with 16 points, we would make grids with dimensions 1x16, 2x8, 4x4, 8x2 and 16x1. For each of these grids we calculate where the grid intersections would lie by dividing the maximum width of the points by the number of columns minus 1, and the maximum height of the points by the number of rows minus 1. Then we fit each point to its closest grid intersection and find the error (square of the distance) between the point and the intersection. (Note that this only works if each point is closer to its intended grid intersection than to any other intersection.)
After summing the errors for each grid configuration individually (e.g. getting one error value for the 1x16 configuration, another for the 2x8 configuration and so on), we select the configuration with the lowest error.
Initialization:
P is the set of points such that P[i][0] is the x-coordinate and
P[i][1] is the y-coordinate
Let p = |P| or the number of points in P
Let max_x = the maximum x-coordinate in P
Let max_y = the maximum y-coordinate in P
(minimum values are assumed to be zero)
Initialize min_error_dist = +infinity
Initialize min_error_cols = -1
Algorithm:
for (col_count = 1; col_count <= n; col_count++) {
// only compute for integer # of rows and cols
if ((p % col_count) == 0) {
row_count = n/col_count;
// Compute the width of the columns and height of the rows
// If the number of columns is 1, let the column width be max_x
// (and similarly for rows)
if (col_count > 1) col_width = max_x/(col_count-1);
else col_width=max_x;
if (row_count > 1) row_height = max_y/(row_count-1);
else row_height=max_y;
// reset the error for the new configuration
error_dist = 0.0;
for (i = 0; i < n; i++) {
// For the current point, normalize the x- and y-coordinates
// so that it's in the range 0..(col_count-1)
// and 0..(row_count-1)
normalized_x = P[i][0]/col_width;
normalized_y = P[i][1]/row_height;
// Error is the sum of the squares of the distances between
// the current point and the nearest grid point
// (in both the x and y direction)
error_dist += (normalized_x - round(normalized_x))^2 +
(normalized_y - round(normalized_y))^2;
}
if (error_dist < min_error_dist) {
min_error_dist = error_dist;
min_error_cols = col_count;
}
}
}
return min_error_cols;
Once you've got the number of columns (and thus the number of rows) you can recompute the normalized values for each point and round them to get the grid intersection they belong to.
In the end I used this algorithm, inspired by beaker's:
Calculate all the possible dimensions of the grid, given the total number of points
For each possible dimension, fit the points to that dimension and calculate the variance in alignment:
Order the points by x-value
Group the points into columns: the first r points form the first column, where r is the number of rows
Within each column, order the points by y-value to determine which row they're in
For each row/column, calcuate the range of y-values/x-values
The variance in alignment is the maximum range found
Choose the dimension with the least variance in alignment
I wrote this algorithm that accounts for missing coordinates as well as coordinates with errors.
Python Code
# Input [x, y] coordinates of a 'sparse' grid with errors
xys = [[103,101],
[198,103],
[300, 99],
[ 97,205],
[304,202],
[102,295],
[200,303],
[104,405],
[205,394],
[298,401]]
def row_col_avgs(num_list, ratio):
# Finds the average of each row and column. Coordinates are
# assigned to a row and column by specifying an error ratio.
last_num = 0
sum_nums = 0
count_nums = 0
avgs = []
num_list.sort()
for num in num_list:
if num > (1 + ratio) * last_num and count_nums != 0:
avgs.append(int(round(sum_nums/count_nums,0)))
sum_nums = num
count_nums = 1
else:
sum_nums = sum_nums + num
count_nums = count_nums + 1
last_num = num
avgs.append(int(round(sum_nums/count_nums,0)))
return avgs
# Split coordinates into two lists of x's and y's
xs, ys = map(list, zip(*xys))
# Find averages of each row and column within a specified error.
x_avgs = row_col_avgs(xs, 0.1)
y_avgs = row_col_avgs(ys, 0.1)
# Return Completed Averaged Grid
avg_grid = []
for y_avg in y_avgs:
avg_row = []
for x_avg in x_avgs:
avg_row.append([int(x_avg), int(y_avg)])
avg_grid.append(avg_row)
print(avg_grid)
Code Output
[[[102, 101], [201, 101], [301, 101]],
[[102, 204], [201, 204], [301, 204]],
[[102, 299], [201, 299], [301, 299]],
[[102, 400], [201, 400], [301, 400]]]
I am also looking for another solution using linear algebra. See my question here.

Algorithm to generate random 2D polygon

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

Resources