I'd like to implement a Bézier curve. I've done this in C# before, but I'm totally unfamiliar with the C++ libraries. How should I go about creating a quadratic curve?
void printQuadCurve(float delta, Vector2f p0, Vector2f p1, Vector2f p2);
Clearly we'd need to use linear interpolation, but does this exist in the standard math library? If not, where can I find it?
I'm using Linux.
Recently I ran across the same question and wanted to implemented it on my own.
This image from Wikipedia helped me:
The following code is written in C++ and shows how to compute a quadratic bezier.
int getPt( int n1 , int n2 , float perc )
{
int diff = n2 - n1;
return n1 + ( diff * perc );
}
for( float i = 0 ; i < 1 ; i += 0.01 )
{
// The Green Line
xa = getPt( x1 , x2 , i );
ya = getPt( y1 , y2 , i );
xb = getPt( x2 , x3 , i );
yb = getPt( y2 , y3 , i );
// The Black Dot
x = getPt( xa , xb , i );
y = getPt( ya , yb , i );
drawPixel( x , y , COLOR_RED );
}
With (x1|y1), (x2|y2) and (x3|y3) being P0, P1 and P2 in the image. Just for showing the basic idea...
For the ones who ask for the cubic bezier, it just works analogue (also from Wikipedia):
This answer provides Code for it.
Here is a general implementation for a curve with any number of points.
vec2 getBezierPoint( vec2* points, int numPoints, float t ) {
vec2* tmp = new vec2[numPoints];
memcpy(tmp, points, numPoints * sizeof(vec2));
int i = numPoints - 1;
while (i > 0) {
for (int k = 0; k < i; k++)
tmp[k] = tmp[k] + t * ( tmp[k+1] - tmp[k] );
i--;
}
vec2 answer = tmp[0];
delete[] tmp;
return answer;
}
Note that it uses heap memory for a temporary array which is not all that efficient. If you only need to deal with a fixed number of points you could hard-code the numPoints value and use stack memory instead.
Of course, the above assumes you have a vec2 structure and operators for it like this:
struct vec2 {
float x, y;
vec2(float x, float y) : x(x), y(y) {}
};
vec2 operator + (vec2 a, vec2 b) {
return vec2(a.x + b.x, a.y + b.y);
}
vec2 operator - (vec2 a, vec2 b) {
return vec2(a.x - b.x, a.y - b.y);
}
vec2 operator * (float s, vec2 a) {
return vec2(s * a.x, s * a.y);
}
You have a choice between de Casteljau's method, which is to recursively split the control path until you arrive at the point using a linear interpolation, as explained above, or Bezier's method which is to blend the control points.
Bezier's method is
p = (1-t)^3 *P0 + 3*t*(1-t)^2*P1 + 3*t^2*(1-t)*P2 + t^3*P3
for cubics and
p = (1-t)^2 *P0 + 2*(1-t)*t*P1 + t*t*P2
for quadratics.
t is usually on 0-1 but that's not an essential - in fact the curves extend to infinity. P0, P1, etc are the control points. The curve goes through the two end points but not usually through the other points.
Did you use a C# library earlier?
In C++, no standard library function for Bezier curves is available (yet). You can of course roll your own (CodeProject sample) or look for a math library.
This blogpost explains the idea nicely but in Actionscript. Translation should not be much of a problem.
To get an individual point (x, y) along a cubic curve at a given percent of travel (t), with given control points (x1, y1), (x2, y2), (x3, y3), and (x4, y4) I expanded De Casteljau’s algorithm and rearranged the equation to minimize exponents:
//Generalized code, not C++
variables passed to function: t, x1, y1, x2, y2, x3, y3, x4, y4
variables declared in function: t2, t3, x, y
t2 = t * t
t3 = t * t * t
x = t3*x4 + (3*t2 - 3*t3)*x3 + (3*t3 - 6*t2 + 3*t)*x2 + (3*t2 - t3 - 3*t + 1)*x1
y = t3*y4 + (3*t2 - 3*t3)*y3 + (3*t3 - 6*t2 + 3*t)*y2 + (3*t2 - t3 - 3*t + 1)*y1
(t) is a decimal value between 0 and 1 (0 <= t <= 1) that represents percent of travel along the curve.
The formula is the same for x and y, and you can write a function that takes a generic set of 4 control points or group the coefficients together:
t2 = t * t
t3 = t * t * t
A = (3*t2 - 3*t3)
B = (3*t3 - 6*t2 + 3*t)
C = (3*t2 - t3 - 3*t + 1)
x = t3*x4 + A*x3 + B*x2 + C*x1
y = t3*y4 + A*y3 + B*y2 + C*y1
For quadratic functions, a similar approach yields:
t2 = t * t
A = (2*t - 2*t2)
B = (t2 - 2*t + 1)
x = t2*x3 + A*x2 + B*x1
y = t2*y3 + A*y2 + B*y1
If you just want to display a Bezier curve, you can use something like PolyBezier for Windows.
If you want to implement the routine yourself, you can find linear interpolation code all over the Intarnetz.
I believe the Boost libraries have support for this. Linear interpolation, not Beziers specifically. Don't quote me on this, however.
I made an implementation based on this example https://stackoverflow.com/a/11435243/15484522 but for any amount of path points
void bezier(int [] arr, int size, int amount) {
int a[] = new int[size * 2];
for (int i = 0; i < amount; i++) {
for (int j = 0; j < size * 2; j++) a[j] = arr[j];
for (int j = (size - 1) * 2 - 1; j > 0; j -= 2)
for (int k = 0; k <= j; k++)
a[k] = a[k] + ((a[k+2] - a[k]) * i) / amount;
circle(a[0], a[1], 3); // draw a circle, in Processing
}
}
Where arr is array of points {x1, y1, x2, y2, x3, y3... xn, yn}, size is amount of points (twice smaller than array size), and amount is number of output points.
For optimal calculations you can use 2^n amount and bit shift:
void bezier(int [] arr, int size, int dense) {
int a[] = new int[size * 2];
for (int i = 0; i < (1 << dense); i++) {
for (int j = 0; j < size * 2; j++) a[j] = arr[j];
for (int j = (size - 1) * 2 - 1; j > 0; j -= 2)
for (int k = 0; k <= j; k++)
a[k] = a[k] + (((a[k+2] - a[k]) * i) >> dense);
circle(a[0], a[1], 3); // draw a circle, in Processing
}
}
This implementation on github shows how to calculate a simple cubic bezier, with normal and tangent values for values of 't' from 0->1.
It is a direct transposition of the formulas at wikipedia.
Related
I am attempting to generate noise similar to perlin or value noise.
I am using stb_image_write library from here to write to image file (i write to disk as hdr and converted to png with GIMP to post on here).
This code requires C++ 20 because I am using std::lerp();
Because I am generating a linear gradient as testing I am expecting a linear gradient as output.
I know there are more steps to generating the desired noise but, this is where I'm having issues.
#include <cmath>
template <size_t size>
void noise(float seed[size][size], float output[size][size], size_t octave);
int main()
{
// generate test gradient
float seedNoise[64][64] = {};
for ( size_t x = 0; x < 64; x++ )
{
for ( size_t y = 0; y < 64; y++ )
{
seedNoise[x][y] = ((((float)x) / 64.0f + ((float)y / 64.0f)) / 2.0f);
}
}
float _map[64][64] = { 0 };
noise<64>(seedNoise, _map, 4);
}
template <size_t size>
void noise(float seed[size][size], float output[size][size], size_t octave)
{
size_t step = size / octave; // went back to this
// size_t step = (size - 1) / octave; // took this back out
for ( size_t x = 0; x <= size - step; x += step )
{
for ( size_t y = 0; y <= size - step; y += step )
{ // each octave
// extract values at corners octave from seed
float a = seed[x][y];
float b = seed[x + (step - 1)][y]; // changed this
float c = seed[x][y + (step - 1)]; // this
float d = seed[x + (step - 1)][y + (step - 1)]; // and this
for ( size_t u = 0; u < step; u++ )
{
float uStep = ((float)u) / ((float)step); // calculate x step
for ( size_t v = 0; v < step; v++ )
{ // each element in each octave
float vStep = ((float)v) / ((float)step); // calculate y step
float x1 = std::lerp(a, b, uStep); // interpolate top edge
float x2 = std::lerp(c, d, uStep); // interpolate bottom edge
float y1 = std::lerp(a, c, vStep); // interpolate left edge
float y2 = std::lerp(b, d, vStep); // interpolate right edge
float x3 = std::lerp(x1, x2, vStep); // interpolate between top and bottom edges
float y3 = std::lerp(y1, y2, uStep); // interpolate between left and right edges
float odat = (x3 + y3) / 2; // average top/bottom and left/right interpolations
output[x + u][y + v] = odat;
}
}
}
}
}
Source gradient I think this should be similar to what the output should be.
Output As you can see here the right and bottom of the output is all messed up.
new output
I think you're accessing the imago outside it's boundaries.
X and y can go up to 60 in the loops:
for ( size_t x = 0; x <= size - step; x += step )
And the you are accessing position y+step and x+step, which gives 64.
I'm trying to implement the Delaunay triangulation in C++. Currently it's working, but I'm not getting the correct amount of triangles.
I try it with 4 points in a square pattern : (0,0), (1,0), (0,1), (1,1).
Here's the algorithm I use :
std::vector<Triangle> Delaunay::triangulate(std::vector<Vec2f> &vertices) {
// Determinate the super triangle
float minX = vertices[0].getX();
float minY = vertices[0].getY();
float maxX = minX;
float maxY = minY;
for(std::size_t i = 0; i < vertices.size(); ++i) {
if (vertices[i].getX() < minX) minX = vertices[i].getX();
if (vertices[i].getY() < minY) minY = vertices[i].getY();
if (vertices[i].getX() > maxX) maxX = vertices[i].getX();
if (vertices[i].getY() > maxY) maxY = vertices[i].getY();
}
float dx = maxX - minX;
float dy = maxY - minY;
float deltaMax = std::max(dx, dy);
float midx = (minX + maxX) / 2.f;
float midy = (minY + maxY) / 2.f;
Vec2f p1(midx - 20 * deltaMax, midy - deltaMax);
Vec2f p2(midx, midy + 20 * deltaMax);
Vec2f p3(midx + 20 * deltaMax, midy - deltaMax);
// Add the super triangle vertices to the end of the vertex list
vertices.push_back(p1);
vertices.push_back(p2);
vertices.push_back(p3);
// Add the super triangle to the triangle list
std::vector<Triangle> triangleList = {Triangle(p1, p2, p3)};
// For each point in the vertex list
for(auto point = begin(vertices); point != end(vertices); point++)
{
// Initialize the edges buffer
std::vector<Edge> edgesBuff;
// For each triangles currently in the triangle list
for(auto triangle = begin(triangleList); triangle != end(triangleList);)
{
if(triangle->inCircumCircle(*point))
{
Edge tmp[3] = {triangle->getE1(), triangle->getE2(), triangle->getE3()};
edgesBuff.insert(end(edgesBuff), tmp, tmp + 3);
triangle = triangleList.erase(triangle);
}
else
{
triangle++;
}
}
// Delete all doubly specified edges from the edge buffer
// Black magic by https://github.com/MechaRage
auto ite = begin(edgesBuff), last = end(edgesBuff);
while(ite != last) {
// Search for at least one duplicate of the current element
auto twin = std::find(ite + 1, last, *ite);
if(twin != last)
// If one is found, push them all to the end.
last = std::partition(ite, last, [&ite](auto const &o){ return !(o == *ite); });
else
++ite;
}
// Remove all the duplicates, which have been shoved past "last".
edgesBuff.erase(last, end(edgesBuff));
// Add the triangle to the list
for(auto edge = begin(edgesBuff); edge != end(edgesBuff); edge++)
triangleList.push_back(Triangle(edge->getP1(), edge->getP2(), *point));
}
// Remove any triangles from the triangle list that use the supertriangle vertices
triangleList.erase(std::remove_if(begin(triangleList), end(triangleList), [p1, p2, p3](auto t){
return t.containsVertex(p1) || t.containsVertex(p2) || t.containsVertex(p3);
}), end(triangleList));
return triangleList;
}
And here's what I obtain :
Triangle:
Point x: 1 y: 0
Point x: 0 y: 0
Point x: 1 y: 1
Triangle:
Point x: 1 y: 0
Point x: 1 y: 1
Point x: 0 y: 1
Triangle:
Point x: 0 y: 0
Point x: 1 y: 1
Point x: 0 y: 1
While this would be the correct output :
Triangle:
Point x: 1 y: 0
Point x: 0 y: 0
Point x: 0 y: 1
Triangle:
Point x: 1 y: 0
Point x: 1 y: 1
Point x: 0 y: 1
I have no idea why there is a triangle with the (0, 0) and the (1, 1).
I need an outside eye to review the code and find out what's going wrong.
All the sources are on my Github repo. Feel free to fork it and to PR your code.
Thanks!
what about this implementation of Paul Bourke's Delaunay triangulation algorithm. Take a look at Triangulate() I have used this source many times without any complains
#include <iostream>
#include <stdlib.h> // for C qsort
#include <cmath>
#include <time.h> // for random
const int MaxVertices = 500;
const int MaxTriangles = 1000;
//const int n_MaxPoints = 10; // for the test programm
const double EPSILON = 0.000001;
struct ITRIANGLE{
int p1, p2, p3;
};
struct IEDGE{
int p1, p2;
};
struct XYZ{
double x, y, z;
};
int XYZCompare(const void *v1, const void *v2);
int Triangulate(int nv, XYZ pxyz[], ITRIANGLE v[], int &ntri);
int CircumCircle(double, double, double, double, double, double, double, double, double&, double&, double&);
using namespace std;
////////////////////////////////////////////////////////////////////////
// CircumCircle() :
// Return true if a point (xp,yp) is inside the circumcircle made up
// of the points (x1,y1), (x2,y2), (x3,y3)
// The circumcircle centre is returned in (xc,yc) and the radius r
// Note : A point on the edge is inside the circumcircle
////////////////////////////////////////////////////////////////////////
int CircumCircle(double xp, double yp, double x1, double y1, double x2,
double y2, double x3, double y3, double &xc, double &yc, double &r){
double m1, m2, mx1, mx2, my1, my2;
double dx, dy, rsqr, drsqr;
/* Check for coincident points */
if(abs(y1 - y2) < EPSILON && abs(y2 - y3) < EPSILON)
return(false);
if(abs(y2-y1) < EPSILON){
m2 = - (x3 - x2) / (y3 - y2);
mx2 = (x2 + x3) / 2.0;
my2 = (y2 + y3) / 2.0;
xc = (x2 + x1) / 2.0;
yc = m2 * (xc - mx2) + my2;
}else if(abs(y3 - y2) < EPSILON){
m1 = - (x2 - x1) / (y2 - y1);
mx1 = (x1 + x2) / 2.0;
my1 = (y1 + y2) / 2.0;
xc = (x3 + x2) / 2.0;
yc = m1 * (xc - mx1) + my1;
}else{
m1 = - (x2 - x1) / (y2 - y1);
m2 = - (x3 - x2) / (y3 - y2);
mx1 = (x1 + x2) / 2.0;
mx2 = (x2 + x3) / 2.0;
my1 = (y1 + y2) / 2.0;
my2 = (y2 + y3) / 2.0;
xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2);
yc = m1 * (xc - mx1) + my1;
}
dx = x2 - xc;
dy = y2 - yc;
rsqr = dx * dx + dy * dy;
r = sqrt(rsqr);
dx = xp - xc;
dy = yp - yc;
drsqr = dx * dx + dy * dy;
return((drsqr <= rsqr) ? true : false);
}
///////////////////////////////////////////////////////////////////////////////
// Triangulate() :
// Triangulation subroutine
// Takes as input NV vertices in array pxyz
// Returned is a list of ntri triangular faces in the array v
// These triangles are arranged in a consistent clockwise order.
// The triangle array 'v' should be malloced to 3 * nv
// The vertex array pxyz must be big enough to hold 3 more points
// The vertex array must be sorted in increasing x values say
//
// qsort(p,nv,sizeof(XYZ),XYZCompare);
///////////////////////////////////////////////////////////////////////////////
int Triangulate(int nv, XYZ pxyz[], ITRIANGLE v[], int &ntri){
int *complete = NULL;
IEDGE *edges = NULL;
IEDGE *p_EdgeTemp;
int nedge = 0;
int trimax, emax = 200;
int status = 0;
int inside;
int i, j, k;
double xp, yp, x1, y1, x2, y2, x3, y3, xc, yc, r;
double xmin, xmax, ymin, ymax, xmid, ymid;
double dx, dy, dmax;
/* Allocate memory for the completeness list, flag for each triangle */
trimax = 4 * nv;
complete = new int[trimax];
/* Allocate memory for the edge list */
edges = new IEDGE[emax];
/*
Find the maximum and minimum vertex bounds.
This is to allow calculation of the bounding triangle
*/
xmin = pxyz[0].x;
ymin = pxyz[0].y;
xmax = xmin;
ymax = ymin;
for(i = 1; i < nv; i++){
if (pxyz[i].x < xmin) xmin = pxyz[i].x;
if (pxyz[i].x > xmax) xmax = pxyz[i].x;
if (pxyz[i].y < ymin) ymin = pxyz[i].y;
if (pxyz[i].y > ymax) ymax = pxyz[i].y;
}
dx = xmax - xmin;
dy = ymax - ymin;
dmax = (dx > dy) ? dx : dy;
xmid = (xmax + xmin) / 2.0;
ymid = (ymax + ymin) / 2.0;
/*
Set up the supertriangle
his is a triangle which encompasses all the sample points.
The supertriangle coordinates are added to the end of the
vertex list. The supertriangle is the first triangle in
the triangle list.
*/
pxyz[nv+0].x = xmid - 20 * dmax;
pxyz[nv+0].y = ymid - dmax;
pxyz[nv+1].x = xmid;
pxyz[nv+1].y = ymid + 20 * dmax;
pxyz[nv+2].x = xmid + 20 * dmax;
pxyz[nv+2].y = ymid - dmax;
v[0].p1 = nv;
v[0].p2 = nv+1;
v[0].p3 = nv+2;
complete[0] = false;
ntri = 1;
/*
Include each point one at a time into the existing mesh
*/
for(i = 0; i < nv; i++){
xp = pxyz[i].x;
yp = pxyz[i].y;
nedge = 0;
/*
Set up the edge buffer.
If the point (xp,yp) lies inside the circumcircle then the
three edges of that triangle are added to the edge buffer
and that triangle is removed.
*/
for(j = 0; j < ntri; j++){
if(complete[j])
continue;
x1 = pxyz[v[j].p1].x;
y1 = pxyz[v[j].p1].y;
x2 = pxyz[v[j].p2].x;
y2 = pxyz[v[j].p2].y;
x3 = pxyz[v[j].p3].x;
y3 = pxyz[v[j].p3].y;
inside = CircumCircle(xp, yp, x1, y1, x2, y2, x3, y3, xc, yc, r);
if (xc + r < xp)
// Suggested
// if (xc + r + EPSILON < xp)
complete[j] = true;
if(inside){
/* Check that we haven't exceeded the edge list size */
if(nedge + 3 >= emax){
emax += 100;
p_EdgeTemp = new IEDGE[emax];
for (int i = 0; i < nedge; i++) { // Fix by John Bowman
p_EdgeTemp[i] = edges[i];
}
delete []edges;
edges = p_EdgeTemp;
}
edges[nedge+0].p1 = v[j].p1;
edges[nedge+0].p2 = v[j].p2;
edges[nedge+1].p1 = v[j].p2;
edges[nedge+1].p2 = v[j].p3;
edges[nedge+2].p1 = v[j].p3;
edges[nedge+2].p2 = v[j].p1;
nedge += 3;
v[j] = v[ntri-1];
complete[j] = complete[ntri-1];
ntri--;
j--;
}
}
/*
Tag multiple edges
Note: if all triangles are specified anticlockwise then all
interior edges are opposite pointing in direction.
*/
for(j = 0; j < nedge - 1; j++){
for(k = j + 1; k < nedge; k++){
if((edges[j].p1 == edges[k].p2) && (edges[j].p2 == edges[k].p1)){
edges[j].p1 = -1;
edges[j].p2 = -1;
edges[k].p1 = -1;
edges[k].p2 = -1;
}
/* Shouldn't need the following, see note above */
if((edges[j].p1 == edges[k].p1) && (edges[j].p2 == edges[k].p2)){
edges[j].p1 = -1;
edges[j].p2 = -1;
edges[k].p1 = -1;
edges[k].p2 = -1;
}
}
}
/*
Form new triangles for the current point
Skipping over any tagged edges.
All edges are arranged in clockwise order.
*/
for(j = 0; j < nedge; j++) {
if(edges[j].p1 < 0 || edges[j].p2 < 0)
continue;
v[ntri].p1 = edges[j].p1;
v[ntri].p2 = edges[j].p2;
v[ntri].p3 = i;
complete[ntri] = false;
ntri++;
}
}
/*
Remove triangles with supertriangle vertices
These are triangles which have a vertex number greater than nv
*/
for(i = 0; i < ntri; i++) {
if(v[i].p1 >= nv || v[i].p2 >= nv || v[i].p3 >= nv) {
v[i] = v[ntri-1];
ntri--;
i--;
}
}
delete[] edges;
delete[] complete;
return 0;
}
int XYZCompare(const void *v1, const void *v2){
XYZ *p1, *p2;
p1 = (XYZ*)v1;
p2 = (XYZ*)v2;
if(p1->x < p2->x)
return(-1);
else if(p1->x > p2->x)
return(1);
else
return(0);
}
I didn't go with a debugger, but from the resulting triangles it seems that this is an accuracy/ambiguity problem.
When you are triangulating a square there are two ways to split it into triangles and both are OK from Delaunay criteria (circumscribed circle center is on border of triangle).
So if you evaluate every triangle independently you may sometimes get even 4 triangles (depending on implementation).
Normally in such cases I recommend to build algorithm as a series of questions which cannot produce contradicting answers. In this case the question is "to which point goes triangle based on edge (1,0)-(1,1)". But often this requires significant changes to the algorithm.
A quick fix usually involves adding some tolerances for comparisons and extra checks (like non-intersecting triangles). But usually it just makes problems rarer.
Most likely you didn't delete all the double edges, especially not the edges from same triangles but with vertices only in another order. The correct function is in the answer from #cMinor.
I have two points and I would like to compute n evenly distributed points on top of the line created by the given line. How could I perform this in c++?
Linear interpolation (affectionately called lerp by the Graphics community) is what you want. Given the end points it can generate the points lying in between with a parameter t.
Let the end points be A (Ax, Ay) and B (Bx, By). The vector spanning from A to B would be given by
V = B − A = <Vx, Vy>
L(t) = A + tV
This essentially means that starting from the point A, we scale the vector V with the scalar t; the point A is displaced by this scaled vector and thus the point we get depends on the value of t, the parameter. When t = 0, we get back A, if t = 1 we get B, if it's 0.5 we get the point midway between A and B.
line A----|----|----|----B
t 0 ¼ ½ ¾ 1
It works for any line (slope doesn't matter). Now coming to your problem of N stops. If you need N to be 10, then you'd have t vary by 1/N, so t = i/10, where i would be the loop iterator.
i = 0, t = 0
i = 1, t = 0.1
i = 2, t = 0.2
⋮
i = 9, t = 0.9
i = 10, t = 1.0
Here's one way to implement it:
#include <iostream>
struct Point {
float x, y;
};
Point operator+ (Point const &pt1, Point const &pt2) {
return { pt1.x + pt2.x, pt1.y + pt2.y };
}
Point operator- (Point const &pt1, Point const &pt2) {
return { pt1.x - pt2.x, pt1.y - pt2.y };
}
Point scale(Point const &pt, float t) {
return { pt.x * t, pt.y * t };
}
std::ostream& operator<<(std::ostream &os, Point const &pt) {
return os << '(' << pt.x << ", " << pt.y << ')';
}
void lerp(Point const &pt1, Point const &pt2, float stops) {
Point const v = pt2 - pt1;
float t = 0.0f;
for (float i = 0.0f; i <= stops; ++i) {
t = i / stops;
Point const p = pt1 + scale(v, t);
std::cout << p << '\n';
}
}
int main() {
lerp({0.0, 0.0}, {5.0f, 5.0f}, 5.0f);
}
Output
(0, 0)
(1, 1)
(2, 2)
(3, 3)
(4, 4)
(5, 5)
Aside
Notice that on every iteration t gets incremented by Δt = 1 / N. Thus another way to update t in a loop would be
t₀ = 0
t₁ = t₀ + Δt
t₂ = t₁ + Δt
⋮
t₉ = t₈ + Δt
t₁₀ = t₉ + Δt
However, this isn't very parallelizable since every iteration of the loop would depend on the previous iteration.
You can use the following give_uniform_points_between(M, N, num_points) which gives a number of #num_points points between M and N. I assume here that the line is not vertical (see below if the line can be vertical).
std::vector<Point> give_uniform_points_between(const Point& M, const Point& N, const unsigned num_points) {
std::vector<Point> result;
// get equation y = ax + b
float a = (N.y - M.y) / (N.x - M.x);
float b = N.y - a * N.x;
float step = std::fabs(M.x - N.x) / num_points;
for (float x = std::min(M.x, N.x); x < std::max(M.x, N.x); x += step) {
float y = a*x+b;
result.push_back(Point{x,y});
}
return result;
}
Demo : Live on Coliru
and result is :
(-3,9);(-2.3,7.6);(-1.6,6.2);(-0.9,4.8);(-0.2,3.4);(0.5,2);(1.2,0.6);(1.9,-0.8);(2.6,-2.2);(3.3,-3.6);
Explanation
From two points (x1,y1) and (x2,y2) you can guess the line equation which pass through these points.
This equation takes the form a*x + b*y + c = 0 or simply y = a*x + b if you cannot have vertical line
where a = (y2 - y1) / (x2 - x1) and you deduce b as shown in the code.
Then you can just vary x or y along your line starting for the point with a minimum value coordinate.
All these (x,y) points you find are on your line and should be uniformely distributed (thanks to the fixed step).
View the line as (x1,y1) + λ(x2-x1,y2-y1), i.e. the first point, plus a multiple of the vector between them.
When λ=0 you have the first point and when λ=1 you have the second.
So you just want to take n equally distributed values of λ between 0 and 1.
How you do this depends on what you mean by between: are the end points included or not?
For example you could take λ=0/(n-1), λ=1/(n-1), λ=2/(n-1), ... λ=(n-1)/(n-1).
That would give n equally distributed points including the endpoints.
Or you could take λ=1/(n+1), λ=2/(n+1), ... λ=n/(n+1).
That would give n equally distributed points not including the endpoints.
Not so mcuh math though...
vector<Rect> Utils::createReactsOnLine(Point pt1, Point pt2, int numRects, int height, int width){
float x1 = pt1.x;
float y1 = pt1.y;
float x2 = pt2.x;
float y2 = pt2.y;
float x_range = std::abs(x2 - x1);
float y_range = std::abs(y2 - y1);
// Find center points of rects on the line
float x_step_size = x_range / (float)(numRects-1);
float y_step_size = y_range / (float)(numRects-1);
float x_min = std::min(x1,x2);
float y_min = std::min(x1,x2);
float x_max = std::max(x1,x2);
float y_max = std::max(x1,x2);
cout << numRects << endl;
float next_x = x1;
float next_y = y1;
cout << "Next x, y: "<< next_x << "," << next_y << endl;
for(int i = 0; i < numRects-1; i++){
if (x1 < x2)
next_x = next_x + x_step_size;
else
next_x = next_x - x_step_size;
if (y1 < y2)
next_y = next_y + y_step_size;
else
next_y = next_y - y_step_size;
cout << "Next x, y: "<< next_x << "," << next_y << endl;
}
return vector<Rect>();
}
I'm trying to write a code to calculate the area of a polygon, but it looks like something isn't adding up. Might be that the formula is off, or I need to use the absolute value function?
double polygon_area(int actual_size, double x[], double y[])
{
printf("In polygon.area\n"); //Initial basic test
int i;
double area;
area = 0.0;
for (i = 0; i <= max_size; i = i + 1)
{
area = (x[i + 1] + x[i]) * (y[i + 1] - y[i]);
area = (area * 0.50);
}
printf("The area of the polygon is %lf \n", area);
return (area);
}
Problems that I see:
You are computing the area as if there are max_size elements to compute the area from. I think you need to use actual_size.
Since C++ indices for arrays start with 0, they need to stop at actual_size-1, not actual_size. Instead of
for (i = 0; i <= max_size; i = i + 1)
use
for (i = 0; i < actual_size; ++i)
// Using ++i is more idiomatic that using i = i + 1
Computation of area is wrong. Replace the lines:
area = (x[i + 1] + x[i]) * (y[i + 1] - y[i]);
area = (area * 0.50);
by
area += 0.5*(the area term);
Calculation of the area term needs to use wrap around indices. Say you have 5 points. When processing the 5-th point, you have to use indices 4 and 0, not 4 and 5.
Instead of
x[i + 1] + x[i]) * (y[i + 1] - y[i]);
use
x[(i + 1)%actual_size] + x[i]) * (y[(i + 1)%actual_size] - y[i]);
The computation for the area term needs to be fixed. Instead of using
0.5*(x2 + x1) * (y2 - y1))
you need to use:
0.5*(x1.y2 - x2.y1)
Here's a simplified version of the function:
double polygon_area(int actual_size, double x[], double y[])
{
printf("In polygon.area\n");
double area = 0.0;
for (int i = 0; i < actual_size; ++i)
{
int j = (i + 1)%actual_size;
area += 0.5 * (x[i]*y[j] - x[j]*y[i]);
}
printf("The area of the polygon is %lf \n", area);
return (area);
}
To get the center, I have tried, for each vertex, to add to the total, divide by the number of vertices.
I've also tried to find the topmost, bottommost -> get midpoint... find leftmost, rightmost, find the midpoint.
Both of these did not return the perfect center because I'm relying on the center to scale a polygon.
I want to scale my polygons, so I may put a border around them.
What is the best way to find the centroid of a polygon given that the polygon may be concave, convex and have many many sides of various lengths?
The formula is given here for vertices sorted by their occurance along the polygon's perimeter.
For those having difficulty understanding the sigma notation in those formulas, here is some C++ code showing how to do the computation:
#include <iostream>
struct Point2D
{
double x;
double y;
};
Point2D compute2DPolygonCentroid(const Point2D* vertices, int vertexCount)
{
Point2D centroid = {0, 0};
double signedArea = 0.0;
double x0 = 0.0; // Current vertex X
double y0 = 0.0; // Current vertex Y
double x1 = 0.0; // Next vertex X
double y1 = 0.0; // Next vertex Y
double a = 0.0; // Partial signed area
// For all vertices except last
int i=0;
for (i=0; i<vertexCount-1; ++i)
{
x0 = vertices[i].x;
y0 = vertices[i].y;
x1 = vertices[i+1].x;
y1 = vertices[i+1].y;
a = x0*y1 - x1*y0;
signedArea += a;
centroid.x += (x0 + x1)*a;
centroid.y += (y0 + y1)*a;
}
// Do last vertex separately to avoid performing an expensive
// modulus operation in each iteration.
x0 = vertices[i].x;
y0 = vertices[i].y;
x1 = vertices[0].x;
y1 = vertices[0].y;
a = x0*y1 - x1*y0;
signedArea += a;
centroid.x += (x0 + x1)*a;
centroid.y += (y0 + y1)*a;
signedArea *= 0.5;
centroid.x /= (6.0*signedArea);
centroid.y /= (6.0*signedArea);
return centroid;
}
int main()
{
Point2D polygon[] = {{0.0,0.0}, {0.0,10.0}, {10.0,10.0}, {10.0,0.0}};
size_t vertexCount = sizeof(polygon) / sizeof(polygon[0]);
Point2D centroid = compute2DPolygonCentroid(polygon, vertexCount);
std::cout << "Centroid is (" << centroid.x << ", " << centroid.y << ")\n";
}
I've only tested this for a square polygon in the upper-right x/y quadrant.
If you don't mind performing two (potentially expensive) extra modulus operations in each iteration, then you can simplify the previous compute2DPolygonCentroid function to the following:
Point2D compute2DPolygonCentroid(const Point2D* vertices, int vertexCount)
{
Point2D centroid = {0, 0};
double signedArea = 0.0;
double x0 = 0.0; // Current vertex X
double y0 = 0.0; // Current vertex Y
double x1 = 0.0; // Next vertex X
double y1 = 0.0; // Next vertex Y
double a = 0.0; // Partial signed area
// For all vertices
int i=0;
for (i=0; i<vertexCount; ++i)
{
x0 = vertices[i].x;
y0 = vertices[i].y;
x1 = vertices[(i+1) % vertexCount].x;
y1 = vertices[(i+1) % vertexCount].y;
a = x0*y1 - x1*y0;
signedArea += a;
centroid.x += (x0 + x1)*a;
centroid.y += (y0 + y1)*a;
}
signedArea *= 0.5;
centroid.x /= (6.0*signedArea);
centroid.y /= (6.0*signedArea);
return centroid;
}
The centroid can be calculated as the weighted sum of the centroids of the triangles it can be partitioned to.
Here is the C source code for such an algorithm:
/*
Written by Joseph O'Rourke
orourke#cs.smith.edu
October 27, 1995
Computes the centroid (center of gravity) of an arbitrary
simple polygon via a weighted sum of signed triangle areas,
weighted by the centroid of each triangle.
Reads x,y coordinates from stdin.
NB: Assumes points are entered in ccw order!
E.g., input for square:
0 0
10 0
10 10
0 10
This solves Exercise 12, p.47, of my text,
Computational Geometry in C. See the book for an explanation
of why this works. Follow links from
http://cs.smith.edu/~orourke/
*/
#include <stdio.h>
#define DIM 2 /* Dimension of points */
typedef int tPointi[DIM]; /* type integer point */
typedef double tPointd[DIM]; /* type double point */
#define PMAX 1000 /* Max # of pts in polygon */
typedef tPointi tPolygoni[PMAX];/* type integer polygon */
int Area2( tPointi a, tPointi b, tPointi c );
void FindCG( int n, tPolygoni P, tPointd CG );
int ReadPoints( tPolygoni P );
void Centroid3( tPointi p1, tPointi p2, tPointi p3, tPointi c );
void PrintPoint( tPointd p );
int main()
{
int n;
tPolygoni P;
tPointd CG;
n = ReadPoints( P );
FindCG( n, P ,CG);
printf("The cg is ");
PrintPoint( CG );
}
/*
Returns twice the signed area of the triangle determined by a,b,c,
positive if a,b,c are oriented ccw, and negative if cw.
*/
int Area2( tPointi a, tPointi b, tPointi c )
{
return
(b[0] - a[0]) * (c[1] - a[1]) -
(c[0] - a[0]) * (b[1] - a[1]);
}
/*
Returns the cg in CG. Computes the weighted sum of
each triangle's area times its centroid. Twice area
and three times centroid is used to avoid division
until the last moment.
*/
void FindCG( int n, tPolygoni P, tPointd CG )
{
int i;
double A2, Areasum2 = 0; /* Partial area sum */
tPointi Cent3;
CG[0] = 0;
CG[1] = 0;
for (i = 1; i < n-1; i++) {
Centroid3( P[0], P[i], P[i+1], Cent3 );
A2 = Area2( P[0], P[i], P[i+1]);
CG[0] += A2 * Cent3[0];
CG[1] += A2 * Cent3[1];
Areasum2 += A2;
}
CG[0] /= 3 * Areasum2;
CG[1] /= 3 * Areasum2;
return;
}
/*
Returns three times the centroid. The factor of 3 is
left in to permit division to be avoided until later.
*/
void Centroid3( tPointi p1, tPointi p2, tPointi p3, tPointi c )
{
c[0] = p1[0] + p2[0] + p3[0];
c[1] = p1[1] + p2[1] + p3[1];
return;
}
void PrintPoint( tPointd p )
{
int i;
putchar('(');
for ( i=0; i<DIM; i++) {
printf("%f",p[i]);
if (i != DIM - 1) putchar(',');
}
putchar(')');
putchar('\n');
}
/*
Reads in the coordinates of the vertices of a polygon from stdin,
puts them into P, and returns n, the number of vertices.
The input is assumed to be pairs of whitespace-separated coordinates,
one pair per line. The number of points is not part of the input.
*/
int ReadPoints( tPolygoni P )
{
int n = 0;
printf("Polygon:\n");
printf(" i x y\n");
while ( (n < PMAX) && (scanf("%d %d",&P[n][0],&P[n][1]) != EOF) ) {
printf("%3d%4d%4d\n", n, P[n][0], P[n][1]);
++n;
}
if (n < PMAX)
printf("n = %3d vertices read\n",n);
else
printf("Error in ReadPoints:\too many points; max is %d\n", PMAX);
putchar('\n');
return n;
}
There's a polygon centroid article on the CGAFaq (comp.graphics.algorithms FAQ) wiki that explains it.
boost::geometry::centroid(your_polygon, p);
Here is Emile Cormier's algorithm without duplicated code or expensive modulus operations, best of both worlds:
#include <iostream>
using namespace std;
struct Point2D
{
double x;
double y;
};
Point2D compute2DPolygonCentroid(const Point2D* vertices, int vertexCount)
{
Point2D centroid = {0, 0};
double signedArea = 0.0;
double x0 = 0.0; // Current vertex X
double y0 = 0.0; // Current vertex Y
double x1 = 0.0; // Next vertex X
double y1 = 0.0; // Next vertex Y
double a = 0.0; // Partial signed area
int lastdex = vertexCount-1;
const Point2D* prev = &(vertices[lastdex]);
const Point2D* next;
// For all vertices in a loop
for (int i=0; i<vertexCount; ++i)
{
next = &(vertices[i]);
x0 = prev->x;
y0 = prev->y;
x1 = next->x;
y1 = next->y;
a = x0*y1 - x1*y0;
signedArea += a;
centroid.x += (x0 + x1)*a;
centroid.y += (y0 + y1)*a;
prev = next;
}
signedArea *= 0.5;
centroid.x /= (6.0*signedArea);
centroid.y /= (6.0*signedArea);
return centroid;
}
int main()
{
Point2D polygon[] = {{0.0,0.0}, {0.0,10.0}, {10.0,10.0}, {10.0,0.0}};
size_t vertexCount = sizeof(polygon) / sizeof(polygon[0]);
Point2D centroid = compute2DPolygonCentroid(polygon, vertexCount);
std::cout << "Centroid is (" << centroid.x << ", " << centroid.y << ")\n";
}
Break it into triangles, find the area and centroid of each, then calculate the average of all the partial centroids using the partial areas as weights. With concavity some of the areas could be negative.