Equidistant points in a line segment - algorithm

Let assume you have two points (a , b) in a two dimensional plane. Given the two points, what is the best way to find the maximum points on the line segment that are equidistant from each point closest to it with a minimal distant apart.
I use C#, but examples in any language would be helpful.
List<'points> FindAllPointsInLine(Point start, Point end, int minDistantApart)
{
// find all points
}

Interpreting the question as:
Between point start
And point end
What is the maximum number of points inbetween spaced evenly that are at least minDistanceApart
Then, that is fairly simply: the length between start and end divided by minDistanceApart, rounded down minus 1. (without the minus 1 you end up with the number of distances between the end points rather than the number of extra points inbetween)
Implementation:
List<Point> FindAllPoints(Point start, Point end, int minDistance)
{
double dx = end.x - start.x;
double dy = end.y - start.y;
int numPoints =
Math.Floor(Math.Sqrt(dx * dx + dy * dy) / (double) minDistance) - 1;
List<Point> result = new List<Point>;
double stepx = dx / numPoints;
double stepy = dy / numPoints;
double px = start.x + stepx;
double py = start.y + stepy;
for (int ix = 0; ix < numPoints; ix++)
{
result.Add(new Point(px, py));
px += stepx;
py += stepy;
}
return result;
}
If you want all the points, including the start and end point, then you'll have to adjust the for loop, and start 'px' and 'py' at 'start.x' and 'start.y' instead. Note that if accuracy of the end-points is vital you may want to perform a calculation of 'px' and 'py' directly based on the ratio 'ix / numPoints' instead.

I'm not sure if I understand your question, but are you trying to divide a line segment like this?
Before:
A +--------------------+ B
After:
A +--|--|--|--|--|--|--+ B
Where "two dashes" is your minimum distance? If so, then there'll be infinitely many sets of points that satisfy that, unless your minimum distance can exactly divide the length of the segment. However, one such set can be obtained as follows:
Find the vectorial parametric equation of the line
Find the total number of points (floor(length / minDistance) + 1)
Loop i from 0 to n, finding each point along the line (if your parametric equation takes a t from 0 to 1, t = ((float)i)/n)
[EDIT]
After seeing jerryjvl's reply, I think that the code you want is something like this: (doing this in Java-ish)
List<Point> FindAllPointsInLine(Point start, Point end, float distance)
{
float length = Math.hypot(start.x - end.x, start.y - end.y);
int n = (int)Math.floor(length / distance);
List<Point> result = new ArrayList<Point>(n);
for (int i=0; i<=n; i++) { // Note that I use <=, not <
float t = ((float)i)/n;
result.add(interpolate(start, end, t));
}
return result;
}
Point interpolate(Point a, Point b, float t)
{
float u = 1-t;
float x = a.x*u + b.x*t;
float y = a.y*u + b.y*t;
return new Point(x,y);
}
[Warning: code has not been tested]

Find the number of points that will fit on the line. Calculate the steps for X and Y coordinates and generate the points. Like so:
lineLength = sqrt(pow(end.X - start.X,2) + pow(end.Y - start.Y, 2))
numberOfPoints = floor(lineLength/minDistantApart)
stepX = (end.X - start.X)/numberOfPoints
stepY = (end.Y - start.Y)/numberOfPoints
for (i = 1; i < numberOfPoints; i++) {
yield Point(start.X + stepX*i, start.Y + stepY*i)
}

Related

Grid - Obstacle / How to Calculate "Field of view"

My english is not my native language, I have no idea how title it, how to explain it clearly and not sure if it's the right term. I've tried to search on google first but for the reason quoted above, I couldn't find anything related.
Could you guys first please check the imgur album : https://imgur.com/a/4mMuCil
So...
Black square is an "obstacle"
Red Square is a "player"
Grey square are "area where player is not able to see"
Depending on the distance of the player from the obstacle, the player can see more or less "things"
Is there a general formula to determine the area that he can see or can't see ?
Or I have to write a unique formula depending on the player position relative to the obstacle
I'm sorry If what I wrote doesn't make sense
Thanks for your help
EDIT :
point player(5, 0);
point obstacle(4, 2);
.....o...
.........
....#....
.........
.....#...
.....##..
.....###.
.....####
......###
This needs a little work. I'll say the steps to make it work:
(1) Let's define the player position p and the obstacle position o.
We know we want to paint a "triangle" after the obstacle.
(2) Let's define the angle according to proximity.
The nearer, the bigger the angle is, so I set the angle to 45 if the distance is 1 and it decreases as the player gets further from the obstacle. The angle is 5 + (max(0, 50 - distance * 10)). You can tune this angle.
(3) Let's build a big triangle. The first vertex is the obstacle. Then, throw a big line from the player through the obstacle. Rotate this line around the obstacle half angle clockwise (to get the second vertex) and half angle anti-clockwise (to get the third vertex), as shown in the image:
(4) Lastly, iterate to the matrix and for each position, ask if that coordinates are inside the triangle.
#include <bits/stdc++.h>
using namespace std;
struct point{
float x, y;
point(){}
point(float x, float y){this->x = x; this->y = y;}
//point(int x, int y){this->x = x; this->y = y;}
};
point rotate(point pivot, float angle, point p, bool clockwise){
float s = sin(angle);
float c = cos(angle);
p.x -= pivot.x;
p.y -= pivot.y;
if(clockwise){
return point(p.x * c + p.y * s + pivot.x, -p.x * s + p.y * c + pivot.y);
}
else{
return point(p.x * c - p.y * s + pivot.x, p.x * s + p.y * c + pivot.y);
}
}
float triangleArea(point p1, point p2, point p3) { //find area of triangle formed by p1, p2 and p3
return abs((p1.x*(p2.y-p3.y) + p2.x*(p3.y-p1.y)+ p3.x*(p1.y-p2.y))/2.0);
}
bool inside(point p1, point p2, point p3, point p) { //check whether p is inside or outside
float area = triangleArea (p1, p2, p3); //area of triangle ABC
float area1 = triangleArea (p, p2, p3); //area of PBC
float area2 = triangleArea (p1, p, p3); //area of APC
float area3 = triangleArea (p1, p2, p); //area of ABP
return abs(area - area1 + area2 + area3) < 1; //when three triangles are forming the whole triangle
}
char m[9][9];
point player(4, 0);
point obstacle(4, 2);
float angle(){
float dist = sqrt(pow(player.x - obstacle.x, 2) + pow(player.y - obstacle.y, 2));
cout<<"dist: "<<(5.0 + max(0.0, 50.0 - 10.0 * dist))<<endl;
return (5.0 + max(0.0, 50.0 - 10.0 * dist)) * 0.0174533;
}
void print(){
for(int i = 0; i < 9; i++){
for(int j = 0; j < 9; j++){
cout<<m[i][j];
}
cout<<endl;
}
}
int main(){
for(int i = 0; i < 9; i++){
for(int j = 0; j < 9; j++){
m[i][j] = '.';
}
}
m[(int)player.y][(int)player.x] = 'o';
m[(int)obstacle.y][(int)obstacle.x] = '#';
float rad = angle();
point end(20.0 * (obstacle.x - player.x) + obstacle.x, 20.0 * (player.y - obstacle.y) + obstacle.y);
point p2 = rotate(obstacle, rad / 2.0, end, true);
point p3 = rotate(obstacle, rad / 2.0, end, false);
for(int i = 0; i < 9; i++){
for(int j = 0; j < 9; j++){
if(i == (int) player.y && j == (int) player.x) continue;
if(i == (int) obstacle.y && j == (int) obstacle.x) continue;
if(inside(obstacle, p2, p3, point(j, i))) m[i][j] = '#';
}
}
print();
return 0;
}
OUTPUT:
....o....
.........
....#....
....#....
....#....
....#....
...###...
...###...
...###...
Suppose the player is at (0,0), and the obstacle is at (j,k), with j>0 and k>=0.
Then a square at (x, y) will be visible if either (2j-1)y >= (2k+1)x or (2k-1)x >= (2j+1)y.
Applying this rule to the other three quadrants is straightforward.

Drawing ellipse with Bresenham's algorithm

Hello,
I'm trying to draw an ellipse, which is parallel to the orthogonal system, using Bresenham's algorithm. I want to draw the top-left (W,SW,S) quarter of the ellipse, and then deduce others.
To do this, i'm using an incremental algorithm with the second-order logic. I did it from another algorithm that draw the top-right quarter first, but what i'm doing isn't working.
The problem appears when the 2nd region is drawing, and I don't know where it comes from.
You can see what I have (black), and what I expect (green):
(center of the ellipse (xc, yc) and the upper right button (x2,y2) that is ~(xc+30,yc+20) in this example)
(a is abs(x2-xc), and b is abs(y2-yc))
The first parameter is the middle of the ellipse (xc, yc), the second is the upper right point established the x and y radius. You can see the ellipse goes too far (2 points on the left and on the right). You can see an other example
(center of the ellipse (xc, yc) and the upper right button (x2,y2) that is ~(xc+15,yc+18) in this example)
The algorithm is deduced from the incremental algorithm with the second-order logic.
Here is my code, (a is abs(x2-xc), and b is abs(y2-yc))
ellipse(int a, int b, int xc, int yc) {
int a2 = a*a, b2 = b*b;
int x = 0, y = b; //Starting point
int incSW = b2*2 + a2*2;
int deltaW = b2*(-2*x + 3); //deduced from incremental algorithm with the second-order logic
int deltaS = a2*(-2*y + 3);
int deltaSW = deltaW + deltaS;
int d1 = b2 - a2*b + a2/4; //dp starting value in the first region
int d2 = b2*(x - 0.5)*(x - 0.5) + a2*(y - 1)*(y - 1) - a2*b2; //dp starting value in the second region
//First region
while(a2*(y-0.5) >= b2*(-x-1)) {
DrawPixel(g,-x+xc, -y+yc); // 1st case
DrawPixel(g,-x+xc, y+yc); // 2nd case
DrawPixel(g,x+xc, y+yc); // 3rd case
DrawPixel(g,x+xc, -y+yc); // 4th case
if(d1>0) {
d1+=deltaSW;
deltaW+=b2*2;
deltaSW+=incSW;
y--;
}
else {
d1+=deltaW;
deltaW+=2*b2;
deltaSW+=2*b2;
}
x--;
}
deltaSW = b2*(2 - 2*x) + a2*(-2*y + 3);
//Second region
while(y>=0) {
DrawPixel(g,-x+xc, -y+yc); // 1st case
DrawPixel(g,-x+xc, y+yc); // 2nd case
DrawPixel(g,x+xc, y+yc); // 3rd case
DrawPixel(g,x+xc, -y+yc); // 4th case
if(d2>0) {
d2+=deltaS;
deltaS+=a2*2;
deltaSW+=a2*2;
}
else {
d2+=deltaSW;
deltaSW+=incSW;
deltaS+=a2*2;
x--;
}
y--;
}
}
I hope you can help me, thanks.
Using the error term e = a x^2 + b y^2 - r^2, it's pretty easy to show that a step from (x,y) to (x,y+1) changes the error by 2by + b, a step to (x+1,y+1) by 2ax + a + 2by + b, and a step to (x+1,y) by 2ax + a.
Starting from a point (-x0, 0), choose the least absolute error step from these three. The first two cases are the norm for the "first region" as you call it.
The first time a step right, (x,y) to (x+1,y), produces least error, you know you're in the second region. At this point the first case is no longer needed. The quarter ellipse can be finished using only the second two cases.
Note this check avoids the floating point operations you've used. The whole point of Bresenham-ish algorithms is to avoid floating point.
The last bit to notice is that you don't want to compute 2ax or 2by each iteration. The multiplications can be avoided by maintaining variables, say dx=2ax and dy=2by, and updating them. A step from x to x+1 increments dx by 2a, a constant. Similarly a step from y to y+1 increments dy by 2b.
Putting all this together, you get the (rough) code below.
Note that you can check the incremental error computation by verifying it against the original error term. If (x0,0) is the initial point, then you know x0^2 = r^2. So the actual error in every iteration is a * x^2 + b * y^2 - x0^2. This ought to equal e in the code below, and it does.
import static java.lang.Math.abs;
import java.util.Arrays;
import java.util.function.BiConsumer;
public class EllipseTracer {
static char [] [] raster = new char[51][101];
static void trace(int x, int y, int a, int b, BiConsumer<Integer, Integer> emitter) {
emitter.accept(x, y);
int e = 0;
int dx = 2 * a * x;
int dy = 2 * b * y;
// First region: stepping north and northeast.
while (x < 0) {
int dxa = dx + a;
int dyb = dy + b;
int eUp = e + dyb;
int eRt = e + dxa;
int eDg = e + dxa + dyb;
if (abs(eUp) < abs(eDg)) {
emitter.accept(x, ++y);
e = eUp;
dy += 2 * b;
} else {
if (abs(eRt) < abs(eDg)) {
// Step east is least error. Found second region.
emitter.accept(++x, y);
e = eRt;
dx += 2 * a;
break;
}
emitter.accept(++x, ++y);
e = eDg;
dy += 2 * b;
dx += 2 * a;
}
}
// Second region: step northeast and east.
while (x < 0) {
int dxa = dx + a;
int dyb = dy + b;
int eRt = e + dxa;
int eDg = e + dxa + dyb;
if (abs(eRt) < abs(eDg)) {
emitter.accept(++x, y);
e = eRt;
dx += 2 * a;
} else {
emitter.accept(++x, ++y);
e = eDg;
dy += 2 * b;
dx += 2 * a;
}
}
}
static void emit(int x, int y) {
raster[y][x + 100] = '*';
}
public static void main(String [] args) {
for (int i = 0; i < raster.length; ++i) {
Arrays.fill(raster[i], ' ');
}
trace(-100, 0, 1, 4, EllipseTracer::emit);
for (int i = 0; i < raster.length; ++i) {
System.out.println(raster[i]);
}
}
}
You can add more tricks to avoid the absolute values, but I'll let you look for those.

Distance check between two circles without sqrt

I'm trying to do distance checks between two circles without using square roots. According to many sources online (such as this one), the way to do this is to subtract the square of the sum of the circles' radii from the squared distance (using Pythagoras's theorem).
However, this doesn't seem to work. According to Wolfram Alpha and my own tests, the check doesn't work like the sqrt version.
Here's the two equations compared in Wolfram: http://www.wolframalpha.com/input/?i=sqrt%28d%29+-+%28r1%2Br2%29+%3C+y+%3D%3D+d+-+%28r1%2Br2%29%5E2+%3C+y
As well as the relevant code that fails to comply:
T DistanceTo(Point p) const {
return sqrt((p.x - x)*(p.x - x) + (p.y - y)*(p.y - y));
}
T DistanceToSq(Point p) const {
return (p.x - x)*(p.x - x) + (p.y - y)*(p.y - y);
}
float Unit::GetDistanceTo(Unit * tgt) const {
auto dist = _pos.DistanceTo(tgt->GetPos());
dist -= GetRadius() + tgt->GetRadius();
return dist;
}
float Unit::GetDistanceToSq(Unit * tgt) const {
auto dist = _pos.DistanceToSq(tgt->GetPos());
auto radii = (GetRadius() + tgt->GetRadius());
dist -= radii * radii;
return dist;
}
template<typename Func>
void ForEachRange(Unit * owner, float range, Func func = [](Unit * tgt)) {
auto range_sq = range * range;
for(Unit * p : m_players) {
if(owner == p || owner->GetDistanceToSq(p) >= range_sq) {
if(owner != p && owner->GetDistanceTo(p) < range)
assert(0);
continue;
}
assert(owner->GetDistanceTo(p) < range);
func(p);
}
}
Am I doing something wrong or is the formula simply incorrect?
You asked Wolfram Alpha if sqrt(d) - (r1+r2) < y is equivalent to d - (r1+r2)^2 < y and Alpha said “no”.
Let's take the first inequality from your query and eliminate the square root using algebra:
sqrt(d) - (r1 + r2) < y
sqrt(d) < y + r1 + r2
d < (y + r1 + r2)²
Do you see how this is different from your second inequality d - (r1+r2)^2 < y?
You can follow your gut or you can follow the rules of algebra, but one of them gives better answers. ;^)
Your for loop doesn't end when you find something within range. I think you want a break instead of continue. You should also have something for when the loop failed to find anything within range.

How to generate a random convex polygon?

I'm trying to devise a method for generating random 2D convex polygons. It has to have the following properties:
coordinates should be integers;
the polygon should lie inside a square with corners (0, 0) and (C, C), where C is given;
the polygon should have number of vertices close to a given number N.
For example, generate random polygons that have 10 vertices and lie inside square [0..100]x[0..100].
What makes this task hard, is the fact that the coordinates should be integers.
The approach I tried was to generate random set of points in the given square and compute the convex hull of these points. But the resultant convex hull is very little vertices compared to N.
Any ideas?
Here is the fastest algorithm I know that generates each convex polygon with equal probability. The output has exactly N vertices, and the running time is O(N log N), so it can generate even large polygons very quickly.
Generate two lists, X and Y, of N random integers between 0 and C. Make sure there are no duplicates.
Sort X and Y and store their maximum and minimum elements.
Randomly divide the other (not max or min) elements into two groups: X1 and X2, and Y1 and Y2.
Re-insert the minimum and maximum elements at the start and end of these lists (minX at the start of X1 and X2, maxX at the end, etc.).
Find the consecutive differences (X1[i + 1] - X1[i]), reversing the order for the second group (X2[i] - X2[i + 1]). Store these in lists XVec and YVec.
Randomize (shuffle) YVec and treat each pair XVec[i] and YVec[i] as a 2D vector.
Sort these vectors by angle and then lay them end-to-end to form a polygon.
Move the polygon to the original min and max coordinates.
An animation and Java implementation is available here: Generating Random Convex Polygons.
This algorithm is based on a paper by Pavel Valtr: “Probability that n random points are in convex position.” Discrete & Computational Geometry 13.1 (1995): 637-643.
Following #Mangara answer there is JAVA implementation, if someone is interested in Python port of it
import random
from math import atan2
def to_convex_contour(vertices_count,
x_generator=random.random,
y_generator=random.random):
"""
Port of Valtr algorithm by Sander Verdonschot.
Reference:
http://cglab.ca/~sander/misc/ConvexGeneration/ValtrAlgorithm.java
>>> contour = to_convex_contour(20)
>>> len(contour) == 20
True
"""
xs = [x_generator() for _ in range(vertices_count)]
ys = [y_generator() for _ in range(vertices_count)]
xs = sorted(xs)
ys = sorted(ys)
min_x, *xs, max_x = xs
min_y, *ys, max_y = ys
vectors_xs = _to_vectors_coordinates(xs, min_x, max_x)
vectors_ys = _to_vectors_coordinates(ys, min_y, max_y)
random.shuffle(vectors_ys)
def to_vector_angle(vector):
x, y = vector
return atan2(y, x)
vectors = sorted(zip(vectors_xs, vectors_ys),
key=to_vector_angle)
point_x = point_y = 0
min_polygon_x = min_polygon_y = 0
points = []
for vector_x, vector_y in vectors:
points.append((point_x, point_y))
point_x += vector_x
point_y += vector_y
min_polygon_x = min(min_polygon_x, point_x)
min_polygon_y = min(min_polygon_y, point_y)
shift_x, shift_y = min_x - min_polygon_x, min_y - min_polygon_y
return [(point_x + shift_x, point_y + shift_y)
for point_x, point_y in points]
def _to_vectors_coordinates(coordinates, min_coordinate, max_coordinate):
last_min = last_max = min_coordinate
result = []
for coordinate in coordinates:
if _to_random_boolean():
result.append(coordinate - last_min)
last_min = coordinate
else:
result.append(last_max - coordinate)
last_max = coordinate
result.extend((max_coordinate - last_min,
last_max - max_coordinate))
return result
def _to_random_boolean():
return random.getrandbits(1)
This isn't quite complete, but it may give you some ideas.
Bail out if N < 3. Generate a unit circle with N vertices, and rotate it random [0..90] degrees.
Randomly extrude each vertex outward from the origin, and use the sign of the cross product between each pair of adjacent vertices and the origin to determine convexity. This is the step where there are tradeoffs between speed and quality.
After getting your vertices set up, find the vertex with the largest magnitude from the origin. Divide every vertex by that magnitude to normalize the polygon, and then scale it back up by (C/2). Translate to (C/2, C/2) and cast back to integer.
A simple algorithm would be:
Start with random line (a two vertices and two edges polygon)
Take random edge E of the polygon
Make new random point P on this edge
Take a line L perpendicular to E going through point P. By calculating intersection between line T and lines defined by the two edges adjacent to E, calculate the maximum offset of P when the convexity is not broken.
Offset the point P randomly in that range.
If not enough points, repeat from 2.
I've made the ruby port as well thanks to both #Mangara's answer and #Azat's answer:
#!/usr/bin/env ruby
# frozen_string_literal: true
module ValtrAlgorithm
module_function def random_polygon(length)
raise ArgumentError, "length should be > 2" unless length > 2
min_x, *xs, max_x = Array.new(length) { rand }.sort
min_y, *ys, max_y = Array.new(length) { rand }.sort
# Divide the interior points into two chains and
# extract the vector components.
vec_xs = to_random_vectors(xs, min_x, max_x)
vec_ys = to_random_vectors(ys, min_y, max_y).
# Randomly pair up the X- and Y-components
shuffle
# Combine the paired up components into vectors
vecs = vec_xs.zip(vec_ys).
# Sort the vectors by angle, in a counter clockwise fashion. Remove the
# `-` to make it clockwise.
sort_by { |x, y| - Math.atan2(y, x) }
# Lay them end-to-end
point_x = point_y = 0
min_polygon_x = min_polygon_y = 0
points = []
vecs.each do |vec_x, vec_y|
points.append([vec_x, vec_y])
point_x += vec_x
point_y += vec_y
min_polygon_x = [min_polygon_x, point_x].min
min_polygon_y = [min_polygon_y, point_y].min
end
shift_x = min_x - min_polygon_x
shift_y = min_y - min_polygon_y
result = points.map { |point_x, point_y| [point_x + shift_x, point_y + shift_y] }
# Append first point to make it a valid linear ring
result << result.first
end
private def to_random_vectors(coordinates, min, max)
last_min = last_max = min
ary = []
coordinates.each do |coordinate|
if rand > 0.5
ary << coordinate - last_min
last_min = coordinate
else
ary << last_max - coordinate
last_max = coordinate
end
end
ary << max - last_min << last_max - max
end
end
Here's another version of Valtr's algorithm using numpy. :)
import numpy as np
import numpy.typing and npt
def generateConvex(n: int) -> npt.NDArray[np.float64]:
'''
Generate convex shappes according to Pavel Valtr's 1995 alogrithm. Ported from
Sander Verdonschot's Java version, found here:
https://cglab.ca/~sander/misc/ConvexGeneration/ValtrAlgorithm.java
'''
# initialise random coordinates
X_rand, Y_rand = np.sort(np.random.random(n)), np.sort(np.random.random(n))
X_new, Y_new = np.zeros(n), np.zeros(n)
# divide the interior points into two chains
last_true = last_false = 0
for i in range(1, n):
if i != n - 1:
if random.getrandbits(1):
X_new[i] = X_rand[i] - X_rand[last_true]
Y_new[i] = Y_rand[i] - Y_rand[last_true]
last_true = i
else:
X_new[i] = X_rand[last_false] - X_rand[i]
Y_new[i] = Y_rand[last_false] - Y_rand[i]
last_false = i
else:
X_new[0] = X_rand[i] - X_rand[last_true]
Y_new[0] = Y_rand[i] - Y_rand[last_true]
X_new[i] = X_rand[last_false] - X_rand[i]
Y_new[i] = Y_rand[last_false] - Y_rand[i]
# randomly combine x and y and sort by polar angle
np.random.shuffle(Y_new)
vertices = np.stack((X_new, Y_new), axis=-1)
vertices = vertices[np.argsort(np.arctan2(vertices[:, 1], vertices[:, 0]))]
# arrange points end to end to form a polygon
vertices = np.cumsum(vertices, axis=0)
# center around the origin
x_max, y_max = np.max(vertices[:, 0]), np.max(vertices[:, 1])
vertices[:, 0] += ((x_max - np.min(vertices[:, 0])) / 2) - x_max
vertices[:, 1] += ((y_max - np.min(vertices[:, 1])) / 2) - y_max
return vertices
Here is an C++11 realization of the Pavel Valtr algorithm introduced in Mangara's Answer with some tricks similar to lewiswolf's Answer and more randomness realised by separated division process of X and Y coordinate.
#include <algorithm>
#include <iostream>
#include <random>
struct randPoly {
int RND_MAX = 655369;
std::random_device dev;
std::mt19937 rng;
std::uniform_int_distribution<std::mt19937::result_type> random_numer;
std::uniform_int_distribution<std::mt19937::result_type> random_logic;
randPoly() : rng(dev()), random_numer(0, RND_MAX), random_logic(0, 1) {}
virtual ~randPoly() {}
int generate(const int n, const double r0, std::vector<double>& poly_x,
std::vector<double>& poly_y) {
auto gen = [&]() { return random_numer(rng); };
// initialize random samples and sort them
int m = n / 2;
std::vector<int> x(n), y(n), vx(n), vy(n), idx(n);
std::vector<double> a(n);
std::generate(x.begin(), x.end(), gen);
std::generate(y.begin(), y.end(), gen);
std::iota(idx.begin(), idx.end(), 0);
std::sort(x.begin(), x.end());
std::sort(y.begin(), y.end());
// divide samples and get vector component
int x0 = x[0], x1 = x0;
for (int k = 1; k < n - 1; ++k) {
if (random_logic(rng)) {
vx[k - 1] = x[k] - x0;
x0 = x[k];
} else {
vx[k - 1] = x1 - x[k];
x1 = x[k];
}
}
vx[n - 2] = x[n - 1] - x0;
vx[n - 1] = x1 - x[n - 1];
int y0 = y[0], y1 = y0;
for (int k = 1; k < n - 1; ++k) {
if (random_logic(rng)) {
vy[k - 1] = y[k] - y0;
y0 = y[k];
} else {
vy[k - 1] = y1 - y[k];
y1 = y[k];
}
}
vy[n - 2] = y[n - 1] - y0;
vy[n - 1] = y1 - y[n - 1];
// random pair up vector components and sort by angle
std::shuffle(vy.begin(), vy.end(), rng);
for (int k = 0; k < n; ++k) {
a[k] = std::atan2(vy[k], vx[k]);
}
std::sort(idx.begin(), idx.end(),
[&a](int& lhs, int& rhs) { return a[lhs] < a[rhs]; });
// form the polygon by connencting vectors
double x_max = 0, y_max = 0, x_min = 0, y_min = 0;
x[0] = y[0] = 0;
for (int k = 1; k < n; ++k) {
x[k] = x[k - 1] + vx[idx[k - 1]];
y[k] = y[k - 1] + vy[idx[k - 1]];
if (x[k] > x_max) {
x_max = x[k];
} else if (x[k] < x_min) {
x_min = x[k];
}
if (y[k] > y_max) {
y_max = y[k];
} else if (y[k] < y_min) {
y_min = y[k];
}
}
// center and resize the polygon
poly_x.resize(n);
poly_y.resize(n);
double x_offset = -(x_max + x_min) / 2.0;
double y_offset = -(y_max + y_min) / 2.0;
double scale = r0 / std::max(x_max - x_min, y_max - y_min);
for (int k = 0; k < n; ++k) {
poly_x[k] = scale * (x[k] + x_offset);
poly_y[k] = scale * (y[k] + y_offset);
}
return 0;
}
};
int main(int, char**) {
randPoly rp;
std::vector<double> poly_x, poly_y;
rp.generate(8, 2.0, poly_x, poly_y);
for (int k = 0; k < poly_x.size(); ++k) {
std::cout << poly_x[k] << " " << poly_y[k] << std::endl;
}
}
Example shown in Rviz
Your initial approach is correct - calculating the convex hull is the only way you will satisfy randomness, convexity and integerness.
The only way I can think of optimizing your algorithm to get "more points" out is by organizing them around a circle instead of completely randomly. Your points should more likely be near the "edges" of your square than near the center. At the center, the probability should be ~0, since the polygon must be convex.
One simple option would be setting a minimum radius for your points to appear - maybe C/2 or C*0.75. Calculate the center of the C square, and if a point is too close, move it away from the center until a minimum distance is reached.

Optimizing / simplifying a path

Say I have a path with 150 nodes / verticies. How could I simplify if so that for example a straight line with 3 verticies, would remove the middle one since it does nothing to add to the path. Also how could I avoid destroying sharp corners? And how could I remove tiny variations and have smooth curves remaining.
Thanks
For every 3 vertices, pick the middle one and calculate its distance to the line segment between the other two. If the distance is less than the tolerance you're willing to accept, remove it.
If the middle vertex is very close to one of the endpoints, you should tighten the tolerance to avoid removing rounded corners for instance.
The simpler approach. Take 3 verticies a, b and c and calculate the angle/inclination between verticies.
std::vector<VERTEX> path;
//fill path
std::vector<int> removeList;
//Assure that the value is in the same unit as the return of angleOf function.
double maxTinyVariation = SOMETHING;
for (int i = 0; i < quantity-2; ++i) {
double a = path[i], b = path[i + 1] , c = path[i + 2]
double abAngle = angleOf(a, b);
double bcAngle = angleOf(b, c);
if (abs(ab - bc) <= maxTinyVariation) {
//a, b and c are colinear
//remove b later
removeList.push_back(i+1);
}
}
//Remove vertecies from path using removeList.
How could I simplify if so that for example a straight line with 3 verticies, would remove the middle one since it does nothing to add to the path.
For each set of three consecutive vertices, test whether they are all in a straight line. If they are, remove the middle vertex.
Also how could I avoid destroying sharp corners?
If you're only removing vertices that fall on a straight line between two others, then you won't have a problem with this.
Use Douglas-Peucker method to simplify a Path.
epsilon parameter defines level of "simplicity":
private List<Point> douglasPeucker (List<Point> points, float epsilon){
int count = points.size();
if(count < 3) {
return points;
}
//Find the point with the maximum distance
float dmax = 0;
int index = 0;
for(int i = 1; i < count - 1; i++) {
Point point = points.get(i);
Point lineA = points.get(0);
Point lineB = points.get(count-1);
float d = perpendicularDistance(point, lineA, lineB);
if(d > dmax) {
index = i;
dmax = d;
}
}
//If max distance is greater than epsilon, recursively simplify
List<Point> resultList;
if(dmax > epsilon) {
List<Point> recResults1 = douglasPeucker(points.subList(0,index+1), epsilon);
List<Point> recResults2 = douglasPeucker(points.subList(index, count), epsilon);
List<Point> tmpList = new ArrayList<Point>();
tmpList.addAll(recResults1);
tmpList.remove(tmpList.size()-1);
tmpList.addAll(recResults2);
resultList = tmpList;
} else {
resultList = new ArrayList<Point>();
resultList.add(points.get(0));
resultList.add(points.get(count-1));
}
return resultList;
}
private float perpendicularDistance(Point point, Point lineA, Point lineB){
Point v1 = new Point(lineB.x - lineA.x, lineB.y - lineA.y);
Point v2 = new Point(point.x - lineA.x, point.y - lineA.y);
float lenV1 = (float)Math.sqrt(v1.x * v1.x + v1.y * v1.y);
float lenV2 = (float)Math.sqrt(v2.x * v2.x + v2.y * v2.y);
float angle = (float)Math.acos((v1.x * v2.x + v1.y * v2.y) / (lenV1 * lenV2));
return (float)(Math.sin(angle) * lenV2);
}
Let A, B, C be some points.
The easiest way to check they lie on the same line is to count crossproduct of vectors
B-A, C-A.
If it equals zero, they lie on the same line:
// X_ab, Y_ab - coordinates of vector B-A.
float X_ab = B.x - A.x
float Y_ab = B.y - A.y
// X_ac, Y_ac - coordinates of vector C-A.
float X_ac = C.x - A.x
float Y_ac = C.y - A.y
float crossproduct = Y_ab * X_ac - X_ab * Y_ac
if (crossproduct < EPS) // if crossprudct == 0
{
// on the same line.
} else {
// not on the same line.
}
After you know that A, B, C lie on the same line it is easy to know whether B lies between A and C throw innerproduct of vectors B-A and C-A. If B lies between A and C, then (B-A) has the same direction as (C-A), and innerproduct > 0, otherwise < 0:
float innerproduct = X_ab * X_ac + Y_ab * Y_ac;
if (innerproduct > 0) {
// B is between A and C.
} else {
// B is not between A and C.
}

Resources