Ricky Ricky - 1 month ago 12
C++ Question

Why are points being generated outside of a my triangle in C++?

So I've read this formula generates a uniform random point inside of a triangle, from this article... http://www.cs.princeton.edu/~funk/tog02.pdf

P = (1 - sqrt(R1)) * A + (sqrt(R1) * (1 - R2)) * B + (sqrt(R1) * R2) * C


Where...
R1 & R2 are random floats in between 0 and 1.

A, B, & C are the points that create our triangle.

I've implemented this formula in my code with MANY test conditions, expecting each condition to have a point generated in the triangle... However, there always seems to be a few cases of it outside the triangle.

Here is my code (in C++)...

#include "stdafx.h"
#include <random>
#include <time.h>

class Location
{
public:
Location(
int x,
int y)
{
m_x = x;
m_y = y;
}

int m_x;
int m_y;

private:

};

double RandomValueBetweenZeroAndOne()
{
return ((double)rand() / (RAND_MAX));
}

float GetArea(
float x1, float y1,
float x2, float y2,
float x3, float y3)
{
return abs((x1*(y2 - y3) + x2*(y3 - y1) + x3*(y1 - y2)) / 2.0f);
}

bool InsideTriangle(
float Ax, float Ay,
float Bx, float By,
float Cx, float Cy,
float Px, float Py)
{
/* Calculate area of triangle ABC */
float A = GetArea(Ax, Ay, Bx, By, Cx, Cy);

/* Calculate area of triangle PBC */
float A1 = GetArea(Px, Py, Bx, By, Cx, Cy);

/* Calculate area of triangle PAC */
float A2 = GetArea(Ax, Ay, Px, Py, Cx, Cy);

/* Calculate area of triangle PAB */
float A3 = GetArea(Ax, Ay, Bx, By, Px, Py);

/* Check if sum of A1, A2 and A3 is same as A */
if ((A == (A1 + A2 + A3)))
{
return true;
}

return false;
};

int main()
{
srand(time(0));

Location* A = new Location(-54900, 933200);
Location* B = new Location(-62800, 934300);
Location* C = new Location(-70000, 932100);

bool in_triangle = true;
int i = 0;
do
{
float R1 = static_cast<float>(RandomValueBetweenZeroAndOne());
float R2 = static_cast<float>(RandomValueBetweenZeroAndOne());

float random_x = (1.0f - sqrt(R1)) * static_cast<float>(A->m_x) + (sqrt(R1) * (1.0f - R2)) * static_cast<float>(B->m_x) + (sqrt(R1) * R2) * static_cast<float>(C->m_x);
float random_y = (1.0f - sqrt(R1)) * static_cast<float>(A->m_y) + (sqrt(R1) * (1.0f - R2)) * static_cast<float>(B->m_y) + (sqrt(R1) * R2) * static_cast<float>(C->m_y);

in_triangle = InsideTriangle(
static_cast<float>(A->m_x), static_cast<float>(A->m_y),
static_cast<float>(B->m_x), static_cast<float>(B->m_y),
static_cast<float>(C->m_x), static_cast<float>(C->m_y),
random_x, random_y);

if (!in_triangle)
{
printf("Point located outside of Triangle on %i iteration", i);
}
i++;
} while (in_triangle);

system("pause");
}


In one example... The equation assigned the random coordinates as follows:

random_x = -66886;
random_y = 932326;


I even made a C# program to verify if the point was truley outside of the triangle (visually). Here's the results, everything above the line that is visible is INSIDE of the triangle... Everything Below the line that is visible is OUTSIDE of the triangle...

https://puu.sh/rJtSJ/7a7a88c346.png

For reference, I know that I can just wrap the number generating in a do while loop until a value inside the triangle is generated... I just wanted to know why it's generating outside when it's not supposed to be, and where the bug is...

Answer

You have a rounding problem, this needs to round down to smaller precision. Also using double instead of float will give better result.

Instead of comparing two double (or float) values to see if they are the same, take their difference and see if it is within a margin of error. The function below has arbitrary margin of 0.00001

bool InsideTriangle(double Ax, double Ay, double Bx, double By, 
    double Cx, double Cy, double Px, double Py)
{
    double A = GetArea(Ax, Ay, Bx, By, Cx, Cy);
    double A1 = GetArea(Px, Py, Bx, By, Cx, Cy);
    double A2 = GetArea(Ax, Ay, Px, Py, Cx, Cy);
    double A3 = GetArea(Ax, Ay, Bx, By, Px, Py);

    double diff = A1 + A2 + A3 - A;
    if (abs(diff) < 0.00001) return true;
    std::cout << diff << "\n";
    return false;
};

To compare the values directly, use the example below:

double getround(double f)
{
    return floor(f * 1000.0f + 0.5f) / 1000.0f;
}

bool InsideTriangle(double Ax, double Ay, double Bx, double By, 
    double Cx, double Cy, double Px, double Py)
{
    double A = GetArea(Ax, Ay, Bx, By, Cx, Cy);
    double A1 = GetArea(Px, Py, Bx, By, Cx, Cy);
    double A2 = GetArea(Ax, Ay, Px, Py, Cx, Cy);
    double A3 = GetArea(Ax, Ay, Bx, By, Px, Py);
    if ((A == (A1 + A2 + A3)))
    {
        return true;
    }

    double t1 = getround(A1 + A2 + A3);
    double t2 = getround(A);

    if (t1 == t2)
        return true;
    std::cout << t1 << ", " << t2 << "\n";

    return false;
};

Note that you should not use cast unless the compiler complains. For example the following cast is not needed

float foo(){return 0.1f;}
...
float R1 = static_cast<float>(foo());

Because foo is already float. If you automatically put cast everywhere then it can hide potential problems.

Avoid using memory allocation when it's not need. For example, use Location A(-54900, 933200); instead of new operator. If you use new then be sure to free the memory with delete when it is no longer needed.