Почему точки генерируются за пределами моего треугольника в C ?

#c #geometry

#c #геометрия

Вопрос:

Итак, я прочитал, что эта формула генерирует однородную случайную точку внутри треугольника из этой статьи … http://www.cs.princeton.edu /~фанк/tog02.pdf

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

Где…
R1 и R2 являются случайными числами с плавающей запятой между 0 и 1.

A, B и C — это точки, которые создают наш треугольник.

Я реализовал эту формулу в своем коде со МНОГИМИ условиями тестирования, ожидая, что каждое условие будет иметь точку, сгенерированную в треугольнике… Тем не менее, всегда кажется, что за пределами треугольника есть несколько случаев.

Вот мой код (на 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");
}
 

В одном примере… Уравнение присвоило случайные координаты следующим образом:

 random_x = -66886;
random_y = 932326;
 

Я даже создал программу на C #, чтобы проверить, действительно ли точка находится за пределами треугольника (визуально). Вот результаты: все, что находится над видимой линией, находится ВНУТРИ треугольника… Все, что находится ниже видимой линии, находится ЗА пределами треугольника…

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

Для справки, я знаю, что я могу просто обернуть число, генерируемое в цикле do while, до тех пор, пока не будет сгенерировано значение внутри треугольника… Я просто хотел знать, почему он генерируется снаружи, когда этого не должно быть, и где ошибка…

Комментарии:

1. Для справки, я знаю, что я могу просто обернуть число, генерируемое в цикле do while, до тех пор, пока не будет сгенерировано значение внутри треугольника… Я просто хотел знать, почему он генерируется снаружи, когда этого не должно быть, и где ошибка…

2. О коде в целом: Location* A = new Location(-54900, 933200); Такие вещи совершенно не нужны и являются дополнительной возможностью для ошибок. Не «пишите C # на C «.

3. Я не уверен, что ты имеешь в виду @deviantfan…. Вы имеете в виду тот факт, что я сделал его указателем? Это небольшой фрагмент большого набора работ, причина, по которой он является указателем, заключается в том, что он затем распределяется между несколькими объектами. Поэтому вместо того, чтобы копировать местоположение 5-6 раз, мы создаем указатель на него.

4. Проблемой может быть усечение «интерполяции» до целых чисел.

5. @MartinR Я тоже об этом думал, поэтому я округлил значения random_x и random_y до приведения с использованием ceil, а также полученную область, используя ceil снова перед приведением (чтобы избежать усечения)… Результат был все тот же. Иногда генерируется случайный набор координат, который находится за пределами треугольника, чего, насколько я понимаю, не должно быть.

Ответ №1:

Арифметика с плавающей запятой не идеальна.Это означает, что когда вы выполняете художественную операцию с плавающими точками, она может не дать точного ожидаемого результата.В этом случае, как правило, вам следует корректировать свой код, разумно используя небольшое число. В этом случае часть, которая должна быть исправлена, должна быть

 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);
 

Поскольку я не понял формулу, я не могу ее исправить.Но если бы вы использовали эту формулу:

   if(R1 R2>1)
  {
      R1=1-R1;
      R2=1-R2;
  }
  float random_x = A->m_x (B->m_x-A->m_x)*R1 (C->m_x-A->m_x)*R2;
  float random_y = A->m_y (B->m_y-A->m_y)*R1 (C->m_y-A->m_y)*R2;
 

Вы могли бы исправить это как:

   if(R1 R2>1)
  {
      R1=1-R1;
      R2=1-R2;
  }
  float error=0.01;
  float D=1-error;
  float random_x = D*A->m_x D*(B->m_x-A->m_x)*R1 D*(C->m_x-A->m_x)*R2 error*(A->m_x B->m_x C->m_x)/3;
  float random_y = D*A->m_y D*(B->m_y-A->m_y)*R1 D*(C->m_y-A->m_y)*R2 error*(A->m_x B->m_x C->m_x)/3;
 

Это гарантирует, что точки будут внутри треугольника.

Ответ №2:

У вас проблема с округлением, это нужно округлить до меньшей точности. Также использование double вместо float даст лучший результат.

Вместо сравнения двух значений double (или float), чтобы увидеть, совпадают ли они, возьмите их разницу и посмотрите, находится ли она в пределах погрешности. Приведенная ниже функция имеет произвольный запас 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;
};
 

Чтобы напрямую сравнить значения, используйте приведенный ниже пример:

 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;
};
 

Обратите внимание, что вы не должны использовать приведение, если компилятор не жалуется. Например, следующее приведение не требуется

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

Потому foo что уже float есть . Если вы автоматически помещаете приведение везде, это может скрыть потенциальные проблемы.

Избегайте использования выделения памяти, когда это не нужно. Например, используйте Location A(-54900, 933200); вместо new оператора. Если вы используете new then, обязательно освободите память, delete когда она больше не понадобится.

Комментарии:

1. Превосходно! Спасибо!

2. Добро пожаловать. См. Обновление для альтернативного сравнения.