Introdução

Os autores Dmitry Anisimov, David Bommes, Kai Hormann, e Pierre Alliez

O pacote 2D Coordenadas Baricêntricas Generalizadas oferece uma implementação eficiente e robusta de coordenadas baricêntricas bidimensionais de forma fechada generalizada definidas para polígonos bidimensionais simples. Se forem necessárias coordenadas com respeito a pontos dispersos multivariados em vez de um polígono, por favor consulte as coordenadas naturais vizinhas do pacote 2D e Interpolação da Função de Superfície.

Em particular, o pacote inclui uma implementação de Wachspress, valor médio, e coordenadas harmónicas discretas e fornece algumas funções extra para calcular coordenadas baricêntricas com respeito a segmentos (coordenadas de segmento) e triângulos (coordenadas triangulares). A seção Teoria das Coordenadas Baricêntricas Generalizadas 2D dá uma breve introdução ao tópico de coordenadas baricêntricas.

Cada classe que computa coordenadas baricêntricas é parametrizada por uma classe de traços. Esta classe de traços especifica tipos e primitivas geométricas que são usadas no cálculo e deve ser um modelo do conceito BarycentricTraits_2.

O principal ponto de entrada para o componente é um iterador de entrada sobre os vértices de um polígono. Os vértices do polígono devem seguir a ordem no sentido horário ou anti-horário e podem ser de qualquer tipo. Entretanto, internamente as classes usam o tipo CGAL::Point_2, por isso uma classe de traços apropriada que converte o tipo do usuário para CGAL::Point_2 deve ser fornecida. O mesmo argumento vale para os pontos de consulta.

Coordenadas de valor do método são as coordenadas mais genéricas neste pacote porque permitem um polígono simples arbitrário como entrada. Wachspress e discretas coordenadas harmônicas são, por definição, limitadas a polígonos estritamente convexos. As coordenadas de segmento tomam como entrada qualquer segmento não degenerado, e as coordenadas triangulares permitem um triângulo não degenerado arbitrário.

As coordenadas de segmento e triângulo podem ser calculadas usando uma função global ou criando a classe correspondente. Todas as outras coordenadas generalizadas podem ser calculadas criando uma instância da classe CGAL::Barycentric_coordinates::Generalized_barycentric_coordinates_2 parametrizada por um tipo de coordenada apropriado que deve ser um modelo do conceito BarycentricCoordinates_2.

Any point in the plan pode ser tomado como um ponto de consulta. No entanto, não recomendamos a utilização de Wachspress e discretas coordenadas harmônicas com pontos de consulta fora do fechamento de um polígono porque em alguns desses pontos essas coordenadas não estão bem definidas, como explicado na seção Degeneracies e Casos Especiais.

Once instanciadas para algum polígono, as coordenadas podem ser computadas várias vezes para diferentes pontos de consulta com respeito a todos os vértices do polígono fornecido. Use o Manual de Referência para a interface detalhada.

A saída do cálculo é um conjunto de valores de coordenadas no ponto de consulta atual com respeito a todos os vértices do polígono. Esta saída pode ser armazenada em um recipiente arbitrário, fornecendo um iterador de saída apropriado. Além disso, todas as classes retornam um ponteiro para o último elemento armazenado e um status do cálculo (Boolean true ou false).

Segment Coordinates

Este é um exemplo simples para mostrar o uso da função global CGAL::Barycentric_coordinates::compute_segment_coordinates_2(). Nós calculamos coordenadas em três pontos verdes ao longo do segmento \(\) e em dois pontos azuis fora deste segmento mas ao longo da sua linha de suporte. Usamos o kernel exato e as coordenadas de retorno como um array de dois valores. Novamente, a simetria dos pontos de consulta nos ajuda a reconhecer erros que podem ter ocorrido durante o cálculo.

segment_coordinates_example.png
Figura 96.1 Exemplo de padrão de pontos.

Arquivo_coordenadas_baricêntricas_2/Segmento_coordenadas_exemplo.cpp

#incluir <CGAL/Exact_predicates_exact_constructions_kernel.h>
#incluir <CGAL/coordenadas_baricêntricas_2/Segmento_coordenadas_2.h>
/ Namespace alias.
namespace BC = CGAL::Barycentric_coordinates;
/ Alguns convenientes typedefs.
typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
typedef Kernel::FT Scalar;
typedef Kernel::Point_2 Point;
typedef std:array<Scalar,2> Par;
usando std::cout; usando std::endl; usando std::string;
int main()
{
// Constrói um segmento.
const Point first_vertex(0, Scalar(2)/Scalar(5));
const Point second_vertex(2, Scalar(2)/Scalar(5));
// Instancie três pontos de consulta interiores e dois exteriores.
const Point query_points = { Point(Scalar(2)/Scalar(5), Scalar(2)/Scalar(5)), // pontos de consulta interior
Point(1 , Scalar(2)/Scalar(5)),
Point(Scalar(8)/Scalar(5), Scalar(2)/Scalar(5)),
Point(Scalar(-1)/Scalar(5), Scalar(2)/Scalar(5)), // pontos de consulta externa
Point(Scalar(11)/Scalar(5), Scalar(2)/Scalar(5))
};
// Calcular as coordenadas do segmento para todos os pontos definidos.
// Utilizamos uma função global e retornamos as coordenadas de segmento armazenadas em um array do tipo std::array<FT,2>.
cout << endl << “Computed segment coordinates: ” << endl << endl;
for(int i = 0; i < 5; ++i) {
const Par = BC::compute_segment_coordenates_2(first_vertex, second_vertex, query_points, Kernel());
// Saída de ambas as coordenadas para cada ponto.
cout << “Par de coordenadas #”, ” << i + 1 << ” = (” << par << “, ” << par << “);”, ” << endl;
}
cout << endl;
return EXIT_SUCCESS;
}

Triangle Coordinates

Neste exemplo mostramos como usar a classe CGAL::Barycentric_coordinates::Triangle_coordinates_2 com o kernel Simple_cartesian para tipo duplo. Calculamos as coordenadas para três conjuntos de pontos: interior (verde), limite (vermelho), e exterior (azul). Note que alguns dos valores das coordenadas para os pontos exteriores são negativos. Usamos um container padrão do tipo std::vector e std::insert_iterator para acessar e armazenar os valores das coordenadas resultantes.

triangle_coordinates_example.png
Figura 96.2 Exemplo de padrão de pontos.

Arquivo_coordenadas_baricêntricas_2/Triângulo_coordenadas_exemplo.cpp

#incluir <CGAL/Simples_cartesiano.h>
#incluir <CGAL/CGAL_coordenadas_2/Triangle_coordinates_2.h>
// Alguns convenientemente digitados.
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::FT Scalar;
typedef Kernel:Point_2 Point;
typedef std::vector<Scalar> Scalar_vector;
typedef CGAL:Barycentric_coordinates::Triangle_coordinates_2<Kernel> Triangle_coordinates;
usando std::cout; usando std::endl; usando std::string;
int main()
{
// Constrói um triângulo.
const Point first_vertex(0.0f, 0.0f);
const Point second_vertex(2.0f, 0.5f);
const Point third_vertex(1.0f, 2.0f);
// Criar um std::vector para armazenar coordenadas.
Coordenadas_vector_escalar;
// Instanciar a classe Triangle_coordates_2 para o triângulo definido acima.
Triangle_coordinates triangle_coordinates(first_vertex, second_vertex, third_vertex);
// Imprimir alguma informação sobre o triângulo e as coordenadas.
triangle_coordinates.print_information();
// Instancie alguns pontos de consulta interiores, limites, e exteriores para os quais calculamos coordenadas.
const int número_de_consulta_pontos = 18;
const Ponto de consulta_pontos = { Ponto(0.5f , 0.5f ), Ponto(1.0f , 0.5f ), Ponto(1.0f , 0.75f), Ponto(1.0f , 1.0f), // pontos de consulta interior
Ponto(1.0f , 1.25f), Ponto(1.0f, 1.5f ), Ponto(0.75f, 1.0f ), Ponto(1.25f, 1.0f), Ponto(1.5f, 0.75f),
Ponto(1.0f , 0.25f), Ponto(0.5f, 1.0f ), Ponto(1.5f , 1.25f), Ponto(1.0f , 2.0f), Ponto(2.0f, 0.5f ), // pontos de consulta de fronteira
Ponto(0.25f, 1.0f ), Ponto(0.5f, 1.75f), Point(1.5f , 1.75f), Point(1.75f, 1.5f) // pontos de consulta exterior
};
// Reserva de memória para guardar coordenadas triangulares para 18 pontos de consulta.
coordinates.reserve(number_of_query_points * 3);
// Calcular coordenadas triangulares para estes pontos.
cout << endl << “Cálculo de coordenadas triangulares: ” << endl << endl;
for(int i = 0; i < number_of_query_points; ++i) {
triangle_coordates(query_points, std::insertter(coordinates, coordinates.end()));
// Saída das coordenadas para cada ponto.
cout << “Ponto ” << i + 1 <<< “: “;
for(int j = 0; j < 3; ++j)
cout << “coordenada ” << j + 1 << ” = ” << coordenadas << “; “;
cout << endl << endl;
}
return EXIT_SUCCESS;
}

Wachspress Coordinates

No exemplo seguinte criamos 1000 pontos aleatórios, depois tomamos o casco convexo deste conjunto de pontos como nosso polígono, e calculamos as coordenadas Wachspress em todos os pontos definidos. Usamos o kernel Simple_cartesian com duplo tipo como uma classe de traços e armazenamos os valores das coordenadas obtidas em um container do tipo std::vector. O iterador de saída é std::back_insert_iterator.

File Barycentric_coordinates_2/Wachspress_coordinates_example.cpp

#include <CGAL/convex_hull_2.h>
#incluir <CGAL/Simple_cartesian.h>
#incluir <CGAL/point_generators_2.h>
#incluir <CGAL/coordenadas_baricêntricas_2/Wachspress_2.h>
#incluir <CGAL/coordenadas_baricêntricas_2/Generalizadas_baricêntricas_2.h>
/ Alguns convenientes typedefs.
typedef CGAL::Simple_cartesian<double> Kernel;
typedef Kernel::FT Scalar;
typedef Kernel::Point_2 Point;
typedef std::Vector<Scalar> Scalar_vector;
typedef std::vector< Ponto> Ponto_vector;
typedef CGAL::Creator_uniform_2<double, Ponto> Creator;
typedef CGAL:CGAL::Barycentric_coordinates::Wachspress_2<Kernel> Wachspress;
typedef CGAL::Barycentric_coordinates::Generalized_barycentric_coordinates_2<Wachspress, Kernel> Wachspress_coordinates;
usando std::cout; usando std::endl; usando std::string;
int main()
{
// Escolha quantos pontos aleatórios queremos gerar.
const int número_de_pontos = 1000;
// Criar vectores para armazenar os pontos e vértices gerados de um polígono convexo.
Ponto_de_vectores, vértices;
// Gerar um conjunto de pontos aleatórios.
CGAL::Random_points_in_square_2<Point,Creator> point_generator(1.0);
std::copy_n(point_generator, number_of_points, std::back_inserter(points));
// Encontre o casco convexo do conjunto de pontos gerados.
// Este casco convexo dá os vértices de um polígono convexo que contém todos os pontos gerados.
CGAL::convex_hull_2(points.begin(), points.end(), std::back_inserter(vertices));
const size_t number_of_vertices = vertices.size();
// Instanciar a classe com coordenadas Wachspress para o polígono convexo definido acima.
Wachspress_coordinates wachspress_coordinates(vertices.begin(), vertices.end());
// Imprimir algumas informações sobre o polígono e coordenadas.
wachspress_coordinates.print_information();
// Compute Wachspress coordinates for all the randomly defined points.
cout << endl << “Computed Wachspress coordinates: ” << endl << endl;
for(int i = 0; i < number_of_points; ++i) {
// Calcular coordenadas.
Coordenadas_vector_escalar;
coordinates.reserve(number_of_vertices);
wachspress_coordinates(points, std::back_inserter(coordinates));
// Saída das coordenadas calculadas.
cout << “Point ” << i + 1 <<< “: ” << endl;
for(int j = 0; j < int(number_of_vertices); ++j) cout << “Coordinate ” << j + 1 << ” = ” <<> coordenadas << “; ” << endl;
cout << endl;
}
return EXIT_SUCCESS;
>

Coordenadas Harmônicas Discretas

Neste exemplo calculamos coordenadas harmônicas discretas para um conjunto de pontos verdes (interior), vermelhos (limite), e azuis (exterior) em relação a um quadrado unitário. Também mostramos como especificar a localização de um ponto de consulta usando parâmetros adicionais da função. O kernel utilizado é exato, e usamos um container de saída do tipo std::vector. Como todos os pontos são simétricos, é fácil debugar a correção dos valores das coordenadas obtidas. O iterador de saída é std::back_insert_iterator.

discrete_harmonic_coordinates_example.png
Figura 96.3 Exemplo de padrão de pontos.

Arquivo_coordenadas_baricêntricas_2/Discretas_coordenadas_armónicas_exemplo.cpp

>#incluir <CGAL/Exacta_predicates_exacta_constructions_kernel.h>
#incluir <CGAL/Coordenadas_baricêntricas_2/Discretas_armónicas_2.h>
#incluir <CGAL/Coordenadas_baricêntricas_2/Generalizadas_baricêntricas_2.h>
// Alguns convenientes typedefs.
typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
typedef Kernel::FT Scalar;
typedef Kernel::Point_2 Point;
typedef std::typedef std::Vetor<Scalar> Scalar_vector;
typedef std::Vetor<Ponto> Ponto_vetor;
typedef std::back_insert_iterator<Scalar_vector> Vetor_insert_iterator;
typedef boost:optional<Vector_insert_iterator> Saída_tipo;
typedef CGAL::Barycentric_coordenates::Discrete_harmonic_2<Kernel> Discrete_harmonic;
typedef CGAL::Barycentric_coordinates::Generalized_barycentric_coordinates_2<Discrete_harmonic, Kernel> Discrete_harmonic_coordinates;
using std::cout; using std::endl; using std::string;
int main()
{
// Construir um quadrado unitário.
const int número_de_vértices = 4;
Ponto_vértices(número_de_vértices);
vértices = Ponto(0, 0); vértices = Ponto(1, 0); vértices = Ponto(1, 1); vértices = Ponto(0, 1);
// Criar um std::vector para armazenar coordenadas.
Scalar_vector coordinates;
// Instanciar a classe com coordenadas harmónicas discretas para a unidade quadrada definida acima.
Coordenadas_harmónicas_distintas discretas(vértices.start(), vértices.end());
// Imprimir alguma informação sobre o polígono e coordenadas.
discrete_harmonic_coordinates.print_information();
// Instanciar o ponto central do quadrado da unidade.
Const Point center(Scalar(1)/Scalar(2), Scalar(1)/Scalar(2));
// Calcular coordenadas harmónicas discretas para o ponto central.
// Use o parâmetro query_point_location = CGAL::Barycentric_coordates::ON_BOUNDED_SIDE.
Resultado do tipo_de_saída = discrete_harmonic_coordinates(center, std::back_inserter(coordinates), CGAL::Barycentric_coordinates::ON_BOUNDED_SIDE);
// Instanciar outros 4 pontos interiores.
const int número_de_pontos_interiores = 4;
constant Point interior_points = { Point(Scalar(1)/Scalar(5), Scalar(1)/Scalar(5)) ,
Point(Scalar(4)/Scalar(5), Scalar(1)/Scalar(5)) ,
Point(Scalar(4)/Scalar(5), Scalar(4)/Scalar(5)) ,
Point(Scalar(1)/Scalar(5), Scalar(4)/Scalar(5)) };
// Calcule coordenadas harmónicas discretas para estes pontos e guarde-as no mesmo vector “coordenadas” que anteriormente.
for(int i = 0; i < número_de_pontos_interiores; ++i)
resultado = coordenadas_armónicas_descretas(pontos_interiores, std::back_inserter(coordenadas), CGAL::Coordenadas_baricêntricas::ON_BOUNDED_SIDE);
// Instanciar 2 pontos de fronteira na segunda e última aresta.
const Point second_edge(1, Scalar(4)/Scalar(5));
const Point last_edge(0, Scalar(4)/Scalar(5));
// Calcular coordenadas harmónicas discretas para estes 2 pontos.
// Use o parâmetro query_point_location = CGAL::Barycentric_coordates::ON_BOUNDARY.
resultado = discrete_harmonic_coordinates(second_edge, std::back_inserter(coordinates), CGAL::Barycentric_coordinates::ON_BOUNDARY);
resultado = discrete_harmonic_coordinates(last_edge , std:back_inserter(coordinates), CGAL::Barycentric_coordinates::ON_BOUNDARY);
// Instanciar 2 outros pontos de fronteira na primeira e terceira arestas.
constant Point first_edge(Scalar(1)/Scalar(2), 0);
const Point third_edge(Scalar(1)/Scalar(2), 1);
// Calcular coordenadas harmónicas discretas usando o índice de uma aresta apropriada.
// Não se esqueça que a contagem do índice começa de zero.
resultado = discrete_harmonic_coordinates.compute_on_edge(first_edge, 0, std::back_inserter(coordinates));
resultado = discrete_harmonic_coordinates.compute_on_edge(first_edge, 0, std::back_inserter(coordinates)).compute_on_edge(third_edge, 2, std::back_inserter(coordinates));
// Calcular coordenadas harmónicas discretas para os pontos no primeiro e terceiro vértices do quadrado da unidade.
resultado = discrete_harmonic_coordinates.compute_on_vertex(0, std::back_inserter(coordinates));
resultado = discrete_harmonic_coordinates.compute_on_vertex(2, std::back_inserter(coordinates));
// Pontos instantâneos no segundo e quarto vértices do quadrado da unidade.
const Ponto segundo_vertex(1, 0);
const Ponto quarto_vertex(0, 1);
// Calcular coordenadas harmónicas discretas para estes pontos.
// Use o parâmetro query_point_location = CGAL::Barycentric_coordates::ON_VERTEX.
resultado = discrete_harmonic_coordinates(second_vertex, std::back_inserter(coordinates), CGAL::Barycentric_coordinates::ON_VERTEX);
resultado = discrete_harmonic_coordinates(fourth_vertex, std::back_inserter(coordenadas), CGAL::Barycentric_coordinates::ON_VERTEX);
// Instanciar 2 pontos fora do quadrado da unidade – um da esquerda e um da direita.
constant Point left_most(Scalar(-1)/Scalar(2), Scalar(1)/Scalar(2));
const Point right_most(Scalar(3)/Scalar(2), Scalar(1)/Scalar(2));
// Calcular coordenadas harmónicas discretas para estes 2 pontos.
// Use o parâmetro query_point_location = CGAL::Barycentric_coordates::ON_UNBOUNDED_SIDE.
resultado = discrete_harmonic_coordinates(left_most , std::back_inserter(coordinates), CGAL::Barycentric_coordinates::ON_UNBOUNDED_SIDE);
resultado = discrete_harmonic_coordinates(right_most, std:back_inserter(coordinates), CGAL::Barycentric_coordinates::ON_UNBOUNDED_SIDE);
// Saída dos valores das coordenadas computadas.
cout << endl << “Coordenadas harmónicas discretas exactas para todos os pontos definidos”: ” << endl << endl;
const size_t number_of_query_points = coordenadas.size();
for(int index = 0; index < int(number_of_query_points); ++index) {
cout << “Coordinate ” << index % number_of_vertices + 1 << ” = ” << coordinates << ” “;
if((index + 1) % número_de_vértices == 0) cout << endl;
if((index + 13) % (4 * número_de_vértices) == 0) cout << endl;
}
// Estado de retorno do último cálculo.
const string status = (resultado ? “SUCESSO.” : “FALHA.”);
cout << endl << “Status do último cálculo: ” << status << endl << endl;
return EXIT_SUCCESS;
>

Coordenadas do valor médio

Este é um exemplo que mostra como calcular as coordenadas do valor médio para um conjunto de pontos verdes num polígono em forma de estrela. Observamos que este tipo de coordenadas é bem definido para um polígono tão côncavo enquanto que Wachspress e discretas coordenadas harmónicas não o são. No entanto, ele pode dar valores de coordenadas negativas para pontos fora do núcleo do polígono (mostrado em vermelho). Usamos um tipo de dado inexato, um container de saída do tipo std::vector, e um iterador de saída do tipo std::back_insert_iterator para calcular, acessar, e armazenar os valores das coordenadas resultantes. Também mostramos como escolher diferentes algoritmos para calcular coordenadas baricêntricas generalizadas (uma é mais precisa enquanto a outra é mais rápida).

mean_value_coordinates_example.png
Figura 96.4 Exemplo de padrão de pontos.

File Barycentric_coordinates_2/Mean_value_coordinates_example.cpp

#include <CGAL/Barycentric_coordinates_2/Mean_value_2.h>
#incluir <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#incluir <CGAL/Barycentric_coordates_2/Generalized_barycentric_coordates_2.h>
/ Alguns convenient typedefs.
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::FT Scalar;
typedef Kernel::Point_2 Point;
typedef std::typedef std::Vetor<Scalar> Scalar_vector;
typedef std::Vetor<Ponto> Ponto_vetor;
typedef std::back_insert_iterator<Scalar_vector> Vetor_insert_iterator;
typedef boost::optional<Vector_insert_iterator> Output_type;
typedef CGAL::Barycentric_coordenates::Mean_value_2<Kernel> Mean_value;
typedef CGAL::Barycentric_coordenates::Barycentric_coordenates::Generalized_barycentric_coordinates_2<Mean_value, Kernel> Mean_value_coordinates;
using std::cout; using std::endl; using std::string;
int main()
{
// Constrói um polígono em forma de estrela.
const int número_de_vértices = 10;
Ponto_vértices(número_de_vértices);
vértices = Ponto(0.0, 0.0); vértices = Ponto(0.1, -0.8); vértices = Ponto(0.3, 0.0); vértices = Ponto(0.6, -0.5); vértices = Ponto(0.6 , 0.1);
vértices = Ponto(1.1, 0.6); vértices = Ponto(0.3, 0.2); vértices = Ponto(0.1, 0.8); vértices = Ponto(0.1, 0.2); vértices = Ponto(-0.7, 0.0);
// Criar um std::vector para armazenar coordenadas.
Scalar_vector coordinates;
// Instanciar a classe com coordenadas de valor médio para o polígono definido acima.
Coordenadas_valor_médio_valor_médio(vértices.begin(), vértices.end());
// Imprimir alguma informação sobre o polígono e coordenadas.
valor_médio_coordenadas.print_information();
// Instanciar alguns pontos interiores do polígono.
const int número_de_pontos_interiores = 8;
const Ponto pontos_interiores = { Ponto(0.12, -0.45), Ponto(0.55, -0.3), Ponto(0.9 , 0.45),
Ponto(0.15, 0.35), Ponto(-0.4, 0.04), Ponto(0.11, 0.11),
Point(0.28, 0.12), // o único ponto no núcleo do polígono em forma de estrela
Point(0.55, 0.11)
};
// Calcular as coordenadas do valor médio para todos os pontos interiores definidos.
// Aceleramos o cálculo usando o algoritmo O(n) chamado com o parâmetro CGAL::Barycentric_coordenates::FAST.
// O padrão é CGAL::Barycentric_coordinates::PRECISE.
const CGAL::Barycentric_coordates::Type_of_algorithm type_of_algorithm = CGAL::Barycentric_coordates::FAST;
// Também aceleramos o cálculo usando o parâmetro query_point_location = CGAL::Barycentric_coordates::ON_BOUNDED_SIDE.
const CGAL::Barycentric_coordates::Query_point_location query_point_location = CGAL::Barycentric_coordates::ON_BOUNDED_SIDE;
for(int i = 0; i < número_de_pontos_interiores; ++i) {
const Resultado do tipo_de_saída = valor_médio_de_coordenadas(pontos_interiores, std::back_inserter(coordenadas), query_point_location, tipo_de_algoritmo);
// Saída das coordenadas para cada ponto.
const string status = (resultado ? “SUCCESS.” : “FAILURE.”);
cout << endl << “Para o ponto ” << i + 1 << ” status do cálculo: ” << estado << endl;
for(int j = 0; j < número_de_vértices; ++j)
cout << “Coordenadas” << j + 1 << ” = ” <<> coordenadas << endl;
}
// Se precisarmos apenas dos pesos não normalizados para algum ponto (vamos pegar o último), podemos calculá-los como se segue.
// Instancie um std::vector para armazenar os pesos.
Pesos escalares_vetoriais;
// Calcule os pesos médios dos valores.
const int último_ponto_index = número_de_pontos_interiores – 1;
const Resultado do tipo Output_type = valor_médio_coordenadas.compute_weights(interior_points, std::back_inserter(weights));
// Calcular a sua soma.
Scalar mv_denominator = Scalar(0);
for(int j = 0; j < number_of_vertices; ++j) mv_denominator += weights;
// Inverter esta soma.
const Scalar mv_inverted_denominator = Scalar(1) / mv_denominator;
// Saída dos pesos do valor médio.
const string status = (resultado ? “SUCESSO.” : “FAILURE.”);
cout << endl << “Status do cálculo dos pesos para o ponto ” << last_point_index + 1 << “: ” << status << endl;
for(int j = 0; j < número_de_vértices; ++j)
cout << “Peso” << j + 1 << ” = ” << pesos << endl;
// Agora, se normalizarmos os pesos, recuperamos os valores das coordenadas do valor médio para o último ponto computado anteriormente.
cout << endl << “Após a normalização, para o ponto ” << last_point_index + 1 << ” as coordenadas do valor médio são ” << endl;
for(int j = 0; j < número_de_vértices; ++j)
cout << “Coordinate ” << j + 1 << ” = ” << pesos * mv_inverted_denominator << endl;
cout << endl;
return EXIT_SUCCESS;
}

Interpolação em altura para modelação de terreno

Este é um exemplo avançado que mostra como usar coordenadas baricêntricas generalizadas para interpolação em altura com aplicações para modelação de terreno. Ele também mostra como usar uma classe de traços não-definidos com nosso pacote ao invés de uma classe de traços Kernel. Suponha que conhecemos o limite de um pedaço de terreno tridimensional que pode ser representado como um polígono com vários vértices tridimensionais, onde a terceira dimensão dá a altura correspondente. A tarefa é propagar a altura a partir dos pontos de amostra conhecidos no limite para o interior do polígono. Isto dá uma estimativa aproximada da superfície do terreno nesta região.

terrain.png
Figura 96.5 Um polígono 2D com 50 vértices representando um pedaço de terreno com partes convexas e côncavas. A altura não é mostrada.

Neste exemplo projetamos um polígono tridimensional ortogonalmente no plano bidimensional usando a classe CGAL::Projection_traits_xy_3, triangulamos seu interior usando a classe CGAL::Delaunay_mesher_2, e calculamos as coordenadas do valor médio para todos os pontos obtidos em relação a todos os vértices do polígono. Finalmente, interpolamos os dados de altura do limite do polígono para o seu interior usando as coordenadas computadas e a função de interpolação global do pacote 2D e Interpolação da Função de Superfície.

File Barycentric_coordenates_2/Terrain_height_modeling.cpp

#incluir <CGAL/Delaunay_mesher_2.h>
#incluir <CGAL/Interpolation_traits_2.h>
#incluir <CGAL/Projecção_traits_xy_3.h>
#include <CGAL/interpolation_functions.h>
#include <CGAL/Delaunay_mesh_face_base_2.h>
#include <CGAL/Delaunay_mesh_size_criteria_2.h>
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
#incluir <CGAL/coordenadas_baricêntricas_2/valor_mean_2.h>
#incluir <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#incluir <CGAL/Barycentric_coordates_2/Generalized_barycentric_coordates_2.h>
/ Alguns convenientemente digitados.
/ Geral.
typedef CGAL::Exact_predicates_inexact_constructions_kernel;
typedef CGAL::Projection_traits_xy_3<Kernel> Projection;
typedef Projection::FT Scalar;
typedef Projection::FT Scalar;
typedef Projection::Ponto_2 Ponto;

>

typedef std::vector<Scalar> Scalar_vector;

>

typedef std::vector<Ponto> Ponto_vector;

>

/ Coordenadas relacionadas.
typedef CGAL::Barycentric_coordinates::Mean_value_2<Projection> Mean_value;
typedef CGAL::Barycentric_coordinates::Generalized_barycentric_coordinates_2<Mean_value, Projection> Mean_value_coordinates;
// Triangulation related.
typedef CGAL::Delaunay_mesh_face_base_2<Projeção> Face_base;
typedef CGAL::Delaunay_mesh_face_base_2<Projeção> Face_base;
typedef CGAL::Triangulação_base_vertex_2<Projeção> Base_vertex;
typedef CGAL::Triangulação_base_dados_2< Base_vertex, Face_base> TDS;
typedef CGAL::Constrained_Delaunay_triangulation_2<Projection, TDS> CDT;
typedef CGAL::Delaunay_mesh_size_criteria_2<CDT> Criteria;
typedef CGAL::Delaunay_mesher_2<CDT, Critério> Mesher;
typedef CDT::Finite_vertices_iterator Vertex_iterator;
typedef CDT::Vertex_handle Vertex_handle;
relacionado à interpolação.<:interpolation_traits_2 interpolation_traits>

typedef CGAL::Data_access< std::map<Point, Scalar, Projection::Less_xy_2 > > Value_access;
// STD.
usando std::cout; usando std::endl; usando std::string;
int main()
{
// Constrói um polígono delimitando um pedaço de terreno tridimensional.
// Note que a coordenada z de cada vértice representa a função altura.
// A projecção em 2D é feita automaticamente pela classe Traços de Projecção.
const int número_de_vértices = 50;
Ponto_vértices(número_de_vértices);
vértices = Ponto(0.03, 0,05, 0,000); vértices = Ponto(0,07, 0,04, 10,00); vértices = Ponto(0,10, 0,04, 20,00);
vértices = Ponto(0,14, 0,04, 30.00); vértices = Ponto(0,17, 0,07, 40,00); vértices = Ponto(0,19, 0,09, 50,00);
vértices = Ponto(0,22, 0,11, 60.00); vértices = Ponto(0,25, 0,11, 70,00); vértices = Ponto(0,27, 0,10, 80,00);
vértices = Ponto(0,30, 0,07, 90,00); vértices = Ponto(0.31, 0,04, 100,0); vértices = Ponto(0,34, 0,03, 110,0);
vértices = Ponto(0,37, 0,02, 120,0); vértices = Ponto(0,40, 0.03, 130,0); vértices = Ponto(0,42, 0,04, 140,0);
vértices = Ponto(0,44, 0,07, 150,0); vértices = Ponto(0,45, 0,10, 160.0); vértices = Ponto(0,46, 0,13, 170,0);
vértices = Ponto(0,46, 0,19, 180,0); vértices = Ponto(0,47, 0,26, 190,0); vértices = Ponto(0.47, 0,31, 200,0);
vértices = Ponto(0,47, 0,35, 210,0); vértices = Ponto(0,45, 0,37, 220,0); vértices = Ponto(0,41, 0,38, 230.0);
vértices = Ponto(0,38, 0,37, 240,0); vértices = Ponto(0,35, 0,36, 250,0); vértices = Ponto(0,32, 0,35, 260.0);
vértices = Ponto(0,30, 0,37, 270,0); vértices = Ponto(0,28, 0,39, 280,0); vértices = Ponto(0,25, 0,40, 290,0);
vértices = Ponto(0.23, 0,39, 300,0); vértices = Ponto(0,21, 0,37, 310,0); vértices = Ponto(0,21, 0,34, 320,0);
vértices = Ponto(0,23, 0,32, 330.0); vértices = Ponto(0,24, 0,29, 340,0); vértices = Ponto(0,27, 0,24, 350,0);
vértices = Ponto(0,29, 0,21, 360,0); vértices = Ponto(0.29, 0,18, 370,0); vértices = Ponto(0,26, 0,16, 380,0);
vértices = Ponto(0,24, 0,17, 390,0); vértices = Ponto(0,23, 0,19, 400.0); vértices = Ponto(0,24, 0,22, 410,0);
vértices = Ponto(0,24, 0,25, 420,0); vértices = Ponto(0,21, 0,26, 430,0); vértices = Ponto(0.17, 0,26, 440,0);
vértices = Ponto(0,12, 0,24, 450,0); vértices = Ponto(0,07, 0,20, 460,0); vértices = Ponto(0,03, 0.15, 470.0);
vértices = Ponto(0.01, 0.10, 480.0); vértices = Ponto(0.02, 0.07, 490.0);
// Mesh este polígono.
// Criar uma triangulação Delaunay restrita.
CDT cdt;
std::vector<Vertex_handle> vertex_handle(number_of_vertices);
// Insira os vértices do polígono como nosso conjunto inicial de pontos.
for(int i = 0; i < number_of_vertices; ++i) vertex_handle = cdt.insert(vertices);
// Inserir restrições – bordas do polígono – de forma a malhar apenas o interior do polígono.
for(int i = 0; i < number_of_vertices; ++i) cdt.insert_constraint(vertex_handle, vertex_handle);
Mesher mesher(cdt);
// Defina um critério sobre como fazer mesh.
mesher.set_criteria(Criteria(0.01, 0.01));
// Mesher o polígono.
mesher.refine_mesh();
// Calcular coordenadas do valor médio e usá-las para interpolar dados do limite do polígono para o seu interior.
// Associar cada ponto com o valor da função e coordenadas correspondentes.
std::map<Point, Scalar, Projection::Less_xy_2> point_function_value;
std::vector< std::par<Ponto, Scalar>>coordenadas_do_ponto(número_de_vértices);
for(int i = 0; i < número_de_vértices; ++i)
valor_de_função_do_ponto.insert(std::make_pair(vtices, vértices.z()));
// Criar uma instância da classe com coordenadas de valor médio.
Média_valor_coordenadas_valor_médio(vtices.begin(), vtices.end()));
// Armazene aqui todos os novos pontos interiores com dados interpolados.
std::vector< Ponto> pontos(cdt.number_of_vertices());
cout <> endl << “Resultado da interpolação em altura”: ” << endl << endl;
// Calcular as coordenadas e interpolar os dados de contorno para o interior do polígono.
int index = 0;
for(Vertex_iterator vertex_iterator = cdt.finite_vertices_begin(); vertex_iterator != cdt.finite_vertices_end(); ++vertex_iterator) {
Scalar_vector coordinates;
const Point & point = vertex_iterator-> point();
mean_value_coordates(point, std::back_inserter(coordinates));
for(int j = 0; j < number_of_vertices; ++j)
point_coordinates = std::make_pair(vertices, coordenadas);
Scalar f = CGAL::linear_interpolation(point_coordates.begin(), point_coordinates.end(), Scalar(1), Value_access(point_function_value));
points = Point(point.x(), point.y(), f);
cout << “A altura interpolada com índice” << índice << ” é ” << f << “;” << endl;
++index;
}
cout << endl;
return EXIT_SUCCESS;
}

>

Como resultado obtemos uma função suave dentro do polígono que se aproxima da superfície do terreno subjacente.

terrain_interpolated.png
Figura 96.6 Os dados interpolados. A barra de cores representa a altura correspondente.

Degenerações e casos especiais

Coordenadas do segmento

As coordenadas do segmento podem ser calculadas exatamente se um tipo exato de dado for escolhido. O segmento em si, com respeito ao qual calculamos as coordenadas, deve ser não degenerado. Se ambas as condições forem satisfeitas, então o cálculo nunca falha. No entanto, para calcular as coordenadas, o usuário deve ter certeza de que o ponto de consulta está exatamente na linha que suporta o segmento. Como em muitas aplicações este não é o caso, e um ponto de consulta pode estar muito próximo mas não exatamente nesta linha, a classe também é capaz de lidar com esta situação.

projection.png
Figura 96.7 A projeção ortogonal do vetor \(p’\) (verde) sobre o vetor \(q\) (vermelho).

Suponha que algum ponto de interrogação (v) não esteja exatamente na linha (L), mas esteja a alguma distância como mostrado na figura acima. Se quisermos calcular a coordenada baricêntrica do segmento (b_1(v)) com respeito ao vértice (v_1), primeiro encontramos a projecção ortogonal do vector (p’p) sobre o vector (q) e depois normalizamo-la pelo comprimento do vértice (q). Isto dá a coordenada baricêntrica do segmento \(b_1(v’) = b_1(v)) se o segmento estiver exactamente na linha.

Aviso: não abuse da funcionalidade descrita acima porque não dá coordenadas baricêntricas correctas do segmento para o ponto mas sim para o ponto. Além disso, as coordenadas baricêntricas do segmento para um ponto (v), que não está exatamente sobre a linha (L), não existem. Mas se a distância não zero for devida a alguma instabilidade numérica no cálculo da localização do ponto ou qualquer outro problema, que faça com que o ponto não esteja exactamente na linha, as coordenadas finais do segmento serão, pelo menos aproximadamente, correctas.

Com tipos de dados inexactos, os valores das coordenadas resultantes são correctos até à precisão do tipo escolhido.

Coordenadas Triangulares

Estas coordenadas podem ser calculadas exactamente se for escolhido um tipo de dados exacto, para qualquer ponto de consulta no plano e em relação a qualquer triângulo não degenerado. Nenhum caso especial é tratado. O cálculo dá sempre o resultado correcto. A noção de exatidão depende da precisão do tipo de dado utilizado. Note que para pontos exteriores alguns valores de coordenadas serão negativos.

Wachspress Coordinates

Wachspress coordinates are welldefined in the closure of any strict convex polygon. Portanto, para qualquer ponto de consulta do fechamento do polígono com um tipo de dado exato, estas coordenadas são computadas exatamente e nenhum resultado falso é esperado. Para tipos de dados inexatos, a precisão resultante do cálculo é devido ao algoritmo envolvido e ao tipo de dados escolhido. No parágrafo seguinte discutimos dois algoritmos disponíveis para o cálculo das coordenadas Wachspress. Um deles é CGAL::Barycentric_coordinates::PRECISE, o outro é CGAL::Barycentric_coordinates::FAST.

wp_notations.png
Figura 96.8 Notação para coordenadas Wachspress.

Para calcular os pesos do Wachspress, seguimos e usamos a fórmula

\(w_i = \frac{C_i}{A_{i-1}A_i})

with \(i = 1\pontos n\) onde \(n\) é o número dos vértices do polígono. Para calcular as coordenadas, normalizamos estes pesos,

{b_i = {w_i}{W^{wp}}{qquad} com {qquad W^{wp} = {sum_{j=1}^n w_j.\Esta fórmula torna-se instável quando se aproxima da fronteira do polígono ((Aproximadamente 1.0e-10) e mais próxima). Para corrigir o problema, nós modificamos os pesos como
{w}_i = C_i\prod_{j\not=i-1,i} A_j\).

Após a normalização como acima, isto nos dá o algoritmo preciso para computar as coordenadas Wachspress mas apenas com o desempenho {\n^2)}. O algoritmo rápido \i(O(n)^2)\i usa os pesos padrão. Note que matematicamente esta modificação não altera as coordenadas.

Sabe-se que para polígonos estritamente convexos o conjunto zero do denominador das coordenadas Wachspress ( \(W^{wp} = 0~\)) é uma curva, que (em muitos casos) fica bastante distante do polígono. Falando precisamente, ela interpola os pontos de intersecção das continuações das arestas do polígono. Portanto, o cálculo das coordenadas Wachspress fora do polígono só é possível em pontos que não pertencem a esta curva.

zero_set.png
Figura 96.9 Conjunto zero (vermelho) do denominador das coordenadas Wachspress \(W^{wp}\) para um hexágono não regular.

Aviso: não recomendamos a utilização das coordenadas Wachspress para pontos exteriores!

Coordenadas Harmónicas Discretas

As coordenadas harmónicas discretas têm os mesmos requisitos que as coordenadas Wachspress. Elas são bem definidas no fechamento de qualquer polígono estritamente convexo e, se um tipo de dado exato for escolhido, elas são computadas exatamente. Mas, ao contrário das funções básicas Wachspress, estas coordenadas não são necessariamente positivas. Em particular, o peso é positivo se e só se o peso for positivo se e só se o peso for <pi) (ver a figura abaixo para a notação). Para tipos de dados imprecisos, a precisão do cálculo deve-se ao algoritmo envolvido e ao tipo de dados escolhido. Mais uma vez, descrevemos dois algoritmos para calcular as coordenadas: um é preciso e outro rápido.

dh_notations.png
Figura 96.10 Notação para coordenadas harmónicas discretas.

Para calcular os pesos harmónicos discretos, seguimos e usamos a fórmula

\(w_i = \frac{r_{i+1}^2A_{i-1}-r_i^2B_i+r_{i-1}^2A_i}{A_{i-1}A_i}{A_{i-1}A_i})

with \(i = 1\\i n=) onde { n=) é o número dos vértices do polígono. Para calcular as coordenadas, normalizamos estes pesos,

{b_i = {w_i}{W^{dh}}{qquad} com {qquad W^{dh} = {sum_{j=1}^n w_j.\Esta fórmula torna-se instável ao aproximar-se da fronteira do polígono ((aprox. 1.0e-10) e mais próxima). Para resolver o problema, tal como na subsecção anterior, modificamos os pesos como
{w}_i = (r_{i+1}^2A_{i-1}-r_i^2B_i+r_{i-1}^2A_i){j=i-1,i} A_j\).

Após a normalização como acima, isto dá o algoritmo preciso para calcular coordenadas harmónicas discretas mas apenas com desempenho \(O(n^2)\). O algoritmo rápido \i(O(n)^2)\i usa os pesos padrão. Mais uma vez, matematicamente esta modificação não altera as coordenadas.

Aviso: tal como para as coordenadas Wachspress, não recomendamos a utilização de coordenadas harmónicas discretas para pontos exteriores porque a curva \(W^{dh} = 0\) pode ter vários componentes, e um deles interpola os vértices do polígono. No entanto, se tiver a certeza que o ponto de consulta não pertence a esta curva, pode calcular as coordenadas como mostrado neste exemplo.

Coordenadas do valor médio

Não como as coordenadas anteriores, as coordenadas do valor médio não podem ser calculadas exactamente devido a uma inevitável operação da raiz quadrada. Embora, se for utilizado um tipo de dado exato, a precisão padrão do cálculo depende apenas de duas funções CGAL: CGAL::to_double() e CGAL::sqrt(). Por outro lado, as coordenadas do valor médio são bem definidas em qualquer parte do plano para qualquer polígono simples. Além disso, se a sua classe de características fornece uma versão mais precisa da função raiz quadrada, a precisão final do cálculo com tipos de dados exatos dependerá apenas da precisão daquela função.

mv_notations.png
Figura 96.11 Notação para coordenadas de valor médio.

Para estas coordenadas também temos dois algoritmos: um é preciso e outro é rápido. O primeiro trabalha em todo o plano, e a precisão do cálculo depende apenas do tipo de dado escolhido, incluindo as observações acima. Este algoritmo é baseado na seguinte fórmula de peso de

^{ w_i = ^sigma_i}bar{w}_i=qquad} com ^(qquad}bar{w}_i = (r_{i-1}r_{i+1}-d_{i-1}d_{i+1})^{1/2}prod_{jnot= i-1,i}(r_jr_{j+1} + d_jd_{j+1})^{1/2}qquad}}onde {qquad r_i = |d_i_i}.

Desde que o sinal é sempre positivo, temos de lhe anexar o sinal correcto do peso médio assinado, que pode ser encontrado de forma eficiente (ver as figuras abaixo). Basicamente, este peso é sempre positivo para a esquerda da curva linear de secção vermelha, e é negativo para a direita desta curva, deslocando-se no sentido contrário ao dos ponteiros do relógio.

mv_weight_signs_convex.png
mv_weight_signs_concave.png

Figura 96.12 Sinais do peso do valor médio dependendo da região em relação a um polígono convexo e a um polígono côncavo.

Após a normalização destes pesos como antes

{b_i = \frac{w_i}{W^{mv}}{qquad}} com \qquad W^{mv} = \sum_{j=1}^n w_j})

obtemos o algoritmo preciso \(O(n^2)}(O(n^2)}). O algoritmo rápido O(n) calcula os pesos \\(w_i}) usando o pseudo-código daqui. Estes pesos

{ w_i = {t_{i-1} + t_i}{r_i}{qquad}) com {qquad t_i = {frac{det}(d_i, d_{i+1}){r_ir_{i+1} + d_id_{i+1}})

também estão normalizados. Note que eles são instáveis se um ponto de consulta estiver mais próximo do que \(aprox. 1.0e-10) do limite do polígono, de forma semelhante ao Wachspress e coordenadas harmônicas discretas.

Performance

Parte do requisito mais importante em coordenadas baricêntricas para ser o mais preciso possível, é muito importante que eles sejam o mais rápido possível para avaliar. Estas coordenadas são utilizadas em muitas aplicações onde devem ser calculadas para milhões de pontos e, portanto, a utilização em tempo real das coordenadas é crucial. Ao escrever o código, tentamos cumprir este importante requisito, e nesta seção apresentamos alguns resultados sobre os tempos de cálculo das coordenadas implementadas.

A estrutura do teste de velocidade que executamos para todas as funções consiste em calcular valores de coordenadas (ou pesos) a >= 1 milhão de pontos estritamente interiores em relação a algum polígono (ou triângulo, ou segmento). A cada iteração do laço criamos um ponto de consulta, passamos para a função, e calculamos todas as coordenadas relacionadas. Executamos este loop 10 vezes seguidas, e o tempo apresentado no gráfico da escala log-log no final da seção é a média aritmética de todos os testes.

Um exemplo típico deste teste de desempenho para coordenadas triangulares com número reduzido de pontos de consulta pode ser encontrado abaixo. Este exemplo também ilustra como construir um iterador e passá-lo para a classe. Neste exemplo criamos um iterador que escreve valores de coordenadas para cada novo ponto de consulta sobre valores de coordenadas do ponto anterior no array C++ padrão de tamanho fixo, de modo que a memória seja alocada apenas uma vez.

File Barycentric_coordinates_2/Triangle_coordinates_speed_test.cpp

#incluir <CGAL/Real_timer.h>
#incluir <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#incluir <CGAL/coordenadas_baricêntricas_2/Triangle_coordates_2.h>
/ Constrói um iterador que leva como entrada o tipo de dados atual e ponteiro para o primeiro elemento da matriz C++ padrão.
template<typename Scalar>
class overwrite_iterator
{
private:
Scalar* pointer;
public:
explicit overwrite_iterator(Scalar* new_pointer) : pointer(new_pointer) { }
// Há apenas duas operações que precisamos sobrecarregar para usar a classe Triangle_coordates_2.
// Esta operação destina-se a retornar o valor actual das coordenadas.
inline Scalar& operator* () { return *pointer; }
// Esta operação tem como objectivo aumentar o índice da coordenada.
inline void operator++ () { ++pointer; }
};
// Alguns convenientes typedefs.
typedef CGAL::Real_timer Timer;
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::FT Scalar;
typedef Kernel::Ponto_2 Ponto;
typedef overwrite_iterator<Scalar> Overwrite_iterator;
typedef CGAL:Barycentric_coordinates::Triangle_coordinates_2<Kernel> Triangle_coordinates;
usando std::cout; usando std::endl; usando std::string;
int main()
{
// O número de coordenadas x e y juntas dá o número de pontos.
const int número_de_x_coordenadas = 100000;
const int número_de_y_coordenadas = 1000;
// Número de execuções para calcular a média aritmética do tempo.
const int número_de_execuções = 10;
// Calcular o tamanho uniforme dos passos ao longo das direcções x e y para alterar as coordenadas.
const Scalar zero = Scalar(0);
const Scalar um = Scalar(1);
const Scalar dois = Scalar(2);
const Scalar x_passo = um / Scalar(número_de_x_coordenadas);
const Scalar y_step = one / Scalar(number_of_y_coordates);
// Criar um triângulo direito com um leve deslocamento de zero.
const Ponto primeiro_vertex(zero – x_passo, zero – x_passo);
const Ponto segundo_vertex(dois + y_passo, zero – x_passo);
const Ponto terceiro_vertex(zero – x_passo, dois + y_passo);
// Instanciar a classe Triângulo_de_coordenadas_2 para o triângulo direito definido acima.
Triangle_coordinates triangle_coordinates(first_vertex, second_vertex, third_vertex);
// Crie uma instância do array C++ padrão para armazenar coordenadas.
// Tem o tamanho fixo = 3 = número de vértices.
Coordenadas escalares = {0, 0, 0};
// Passa o ponteiro para o primeiro elemento do array com coordenadas a fim de sobregravá-las.
Overwrite_iterator it( &(coordinates) );
// Criar um temporizador.
Timer time_to_compute;
double time = 0.0;
for(int i = 0; i < number_of_runs; ++i) { // Número de execuções
time_to_compute.start(); // Relógio de início
for(Scalar x = zero; x <= um; x += x_passo) {
for(Scalar y = zero; y <= um; y += y_step)
triangle_coordinates(Point(x, y), it); // Calcular 3 valores de coordenadas para cada ponto gerado
}
time_to_compute.stop(); // Parar relógio
time += time_to_compute.time(); // Adicionar tempo da execução atual ao tempo inteiro
time_to_compute.reset(); // Tempo de reinicialização
}
// Calcular a média aritmética de todas as execuções.
constant double mean_time = time / number_of_runs;
// Produzir o tempo resultante.
cout.precision(10);
cout << endl << “CPU time to compute triangle coordinates for ”
<< number_of_x_coordinates * number_of_y_coordinates << ” points = ” << mean_time << ” seconds.”;
cout << endl << endl;
return EXIT_SUCCESS;
}

O tempo para calcular coordenadas depende de muitos factores como alocação de memória, kernel de entrada, contentor de saída, número de pontos, etc. Em nossos testes usamos as características mais padrão C++ e CGAL com alocação mínima de memória. Portanto, o tempo final apresentado é o tempo médio que pode ser esperado sem otimização profunda, mas ainda com alocação eficiente de memória. Isso também significa que pode variar dependendo do uso do pacote.

Para todos os testes usamos um MacBook Pro 2011 com processador Intel Core i7 de 2 GHz (2 núcleos) e memória DDR3 de 8 GB 1333 MHz. O sistema operacional instalado foi o OS X 10.9 Maverick. Para compilar o conjunto de testes de velocidade, usamos o compilador Clang 5.0 de 64 bits. Os tempos resultantes podem ser encontrados na figura abaixo.

time.png
Figura 96.13 Tempo em segundos para calcular os valores das coordenadas para um polígono com vértices em 1 milhão de pontos com os algoritmos rápidos (a tracejado) e lentos (sólidos) para as coordenadas Wachspress (azul), harmônica discreta (vermelho) e valor médio (verde).

Da figura acima é fácil ver que o algoritmo \(O(n^2)\) é tão rápido quanto o algoritmo \(O(n)\) se tivermos um polígono com um pequeno número de vértices. Mas como o número de vértices é aumentado, o algoritmo linear supera o quadrático, como esperado. Uma das razões para este comportamento é que para um pequeno número de vértices as multiplicações de elementos dentro do algoritmo O(n^2) com os algoritmos rápido (tracejado) e lento (sólido) levam quase o mesmo tempo que a divisão correspondente no algoritmo O(n^2). Para um polígono com muitos vértices esta multiplicação é muito mais lenta.

Detalhes de Implementação

O desenho genérico do pacote foi desenvolvido em 2013 por Dmitry Anisimov e David Bommes com muitos comentários úteis por Kai Hormann e Pierre Alliez. O pacote consiste de 6 classes, 2 enumerações e um namespace. Iteradores apropriados são usados para fornecer um acesso eficiente aos dados e para passá-los para um dos algoritmos genéricos para o cálculo de coordenadas. Uma vez instanciadas para um polígono (triângulo, segmento), as coordenadas podem ser calculadas várias vezes para diferentes pontos de consulta com respeito a todos os vértices do polígono fornecido (triângulo, segmento). Todas as classes são totalmente modeladas e têm um desenho simples e similar. Em particular, seguimos a mesma convenção de nomenclatura para todas as funções. No entanto, o número de funções pode diferir de uma classe para outra.

Os algoritmos implementados para calcular coordenadas não dependem de um kernel em particular, e todas as coordenadas podem ser computadas exatamente, se um kernel exato for usado, além das coordenadas de valor médio. As últimas coordenadas envolvem uma operação de raiz quadrada, o que resulta em uma precisão ligeiramente pior com tipos de dados exatos, devido à conversão temporal em um tipo de ponto flutuante. As coordenadas computadas podem ser armazenadas em um container arbitrário se um iterador de saída apropriado for fornecido.

Vale notar que a classe CGAL::Barycentric_coordinates::Segment_coordinates_2 é usada para computar coordenadas baricêntricas generalizadas ao longo do limite do polígono. Portanto, pode-se usar o truque para as coordenadas de segmento da Seção Degenerações e Casos Especiais se estiver convencido de que um ponto deve estar exatamente no limite do polígono mas devido a algumas instabilidades numéricas ele não está.

O pacote é implementado de forma que mais tarde, se necessário, outras coordenadas bidimensionais generalizadas baricêntricas podem ser facilmente adicionadas a este pacote.

Teoria das Coordenadas Baricêntricas Generalizadas 2D

Em 1827, o matemático e astrônomo teórico alemão August Ferdinand Möbius (1790-1868) propôs um método para encontrar coordenadas de um ponto no plano em relação aos vértices de um triângulo. Estas coordenadas são chamadas de coordenadas triangulares baricêntricas (às vezes coordenadas de área), e são amplamente utilizadas em uma variedade de aplicações. Algumas destas aplicações são interpolação linear sobre um triângulo e um teste de inclusão de triângulos. A primeira é usada para o chamado sombreamento, e a segunda surge no passo de rasterização quando uma imagem em formato gráfico vetorial precisa ser convertida em uma imagem rasterizada.

Triângulo baricêntrico de coordenadas tem muitas propriedades importantes, incluindo precisão constante e linear, a propriedade Lagrange, e positividade dentro de um triângulo. Estas propriedades fazem destas coordenadas uma ferramenta única em muitos campos científicos. Se restringirmos as coordenadas triangulares a uma das arestas de um triângulo e sua linha de suporte, obtemos coordenadas baricêntricas em relação a um segmento e as chamamos de coordenadas de segmento.

Vamos mostrar um par de gráficos para as coordenadas descritas acima. Para traçar coordenadas de segmentos, pegamos uma linha \(y = 0,4\) e definimos um segmento \(\) nesta linha. Em seguida, fazemos uma amostragem deste segmento e calculamos as coordenadas do segmento para todos os pontos da amostra. Se traçarmos a função de coordenadas do segmento em todos os pontos definidos em relação ao vértice (v_1), obtemos a linha azul representada na figura abaixo. Ela cresce de zero no vértice \(v_0\) para um no vértice \(v_1\).

seg__coord__interp.png
Figura 96.14 Coordenadas do segmento (azul) para todos os pontos do segmento (verde) em relação ao vértice \(v_1 = (2.0,\ 0.4)\).

Se quisermos traçar coordenadas triangulares, seguimos uma abordagem semelhante. Pegamos um triângulo \(\) no plano e amostramos seu interior e seu limite com um número de pontos. Uma vez que temos esta amostragem, plotamos uma das funções de coordenadas triangulares (aqui com respeito ao terceiro vértice do triângulo) em todos os pontos de amostra definidos. Da mesma forma, podemos traçar a função de coordenadas em relação ao primeiro ou segundo vértice. A função resultante é linear (mostrada na figura abaixo) que cresce de zero ao longo da primeira aresta \(v_2) para uma no vértice escolhido \(v_2).

tri__coord__interp.png
Figura 96.15 Coordenadas triangulares com respeito a \(v_2 = (1.0,\ 2.0)\). A barra de cores indica o intervalo de valores para a coordenada escolhida.

Desde que muitas aplicações requerem trabalhar com formas geométricas planares mais complexas do que segmentos e triângulos, parece natural investigar uma versão generalizada de coordenadas triangulares com respeito a polígonos arbitrários. A primeira tentativa foi feita em 1975 por E. L. Wachspress , e as coordenadas baricêntricas generalizadas resultantes são agora chamadas de coordenadas Wachspress . Estas coordenadas são bem definidas para polígonos arbitrários estritamente convexos e têm todas as propriedades das coordenadas triangulares. Infelizmente, elas não são bem definidas para polígonos fracamente convexos e côncavos.

Analogamente aos casos anteriores, queremos plotar as coordenadas Wachspress e ver como elas se parecem. Vamos escolher um hexágono não regular, rodá-lo ligeiramente e mover um de seus vértices em direção à linha através de seus dois vizinhos adjacentes. Amostra-se o interior e o limite deste polígono como antes e traçamos a função de coordenadas em relação ao vértice que movemos em todos os pontos de amostra. Vemos que obtemos uma função suave, que é linear ao longo de todas as bordas e cresce de zero para um, como a barra de cor indica.

wp__coord__interp.png
Figura 96.16 A função de coordenadas Wachspress em relação ao vértice indicado com valores de zero a um, como indica a barra de cores.

Um outro tipo de coordenadas baricêntricas generalizadas volta a Pinkall e Polthier em 1993 e Eck et al. em 1995 no contexto da parametrização da malha triangular. Elas são chamadas de coordenadas harmônicas discretas. Estas coordenadas são bem definidas, à semelhança das coordenadas Wachspress, para polígonos arbitrários estritamente convexos e herdam todas as propriedades das coordenadas triangulares, à excepção da positividade dentro de um polígono porque podem assumir valores negativos para alguns polígonos. Outra propriedade interessante destas funções de coordenadas é que elas coincidem com as coordenadas Wachspress para qualquer polígono cujos vértices se encontram num círculo comum.

Para traçar coordenadas harmónicas discretas tomamos o mesmo polígono que para as coordenadas Wachspress e traçamos a função de coordenadas em relação ao mesmo vértice. Mais uma vez, obtemos uma função suave, que é linear ao longo de todas as arestas e cresce de zero para um. Isolines no gráfico mostram a diferença entre as coordenadas harmônicas discretas e as coordenadas Wachspress para o polígono e vértice escolhidos.

dh__coord__interp.png
Figura 96.17 A função de coordenadas harmônicas discretas em relação ao vértice indicado com valores de zero a um como a barra de cores indica.

O último tipo de coordenadas baricêntricas generalizadas que discutimos são coordenadas de valores médios propostos por M. Floater em 2003. Com base no teorema do valor médio, estas coordenadas, ao contrário do Wachspress e das coordenadas harmônicas discretas, são bem definidas para polígonos simples arbitrários, herdam todas as propriedades das coordenadas triangulares para qualquer polígono convexo, e carecem apenas da propriedade de positividade para polígonos côncavos gerais. Hormann e Floater provam que estas coordenadas são positivas dentro do núcleo de um polígono em forma de estrela. Elas também são positivas no fechamento de qualquer quadrilátero. Como pesos harmónicos discretos, os pesos médios são frequentemente usados no contexto da parametrização da malha triangular.

Para mostrar o comportamento particular das coordenadas de valores médios com uma aplicação a polígonos côncavos, tomamos um polígono em forma de estrela com dez vértices, amostramos o seu interior e limite, e traçamos a função de coordenadas em relação ao quarto vértice. Como a barra de cores indica, a função obtida cresce de um valor ligeiramente negativo para um no vértice escolhido. Ela também é lisa dentro do polígono e linear em todas as bordas.

mv__coord__interp.png
Figura 96.18 Coordenadas de valores médios em relação ao \(v_3\). A barra de cores indica a gama de valores para a função de coordenadas escolhida.

Fato interessante: todas as coordenadas discutidas nesta seção e implementadas no pacote vêm de uma mesma família de coordenadas barycentric generalizadas nomeadas família de coordenadas de 3 pontos .

Agradecimentos


Deixe uma resposta

O seu endereço de email não será publicado.