快速查找包含点的网格内部的三角形

时间:2015-08-26 23:02:50

标签: c++ performance computational-geometry cgal

我遇到了我需要完成的任务的性能问题。目前的一个瓶颈是从非结构化网格获取插值字段值。

在给定2D点和非结构化2D网格的情况下,慢速部分找到紧邻该点的网格点。只要找到它落入的三角形就好了。

现在我正在使用CGAL,但它太慢了。对于当前的实现,整个任务将需要数天才能完成,并在高端CPU上并行运行。

我认为缓慢的部分是CGAL :: natural_neighbor_coordinates_2。

#ifndef FIELD_INTERPOLATOR_H
#define FIELD_INTERPOLATOR_H

#include "Vec.h"

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Interpolation_traits_2.h>
#include <CGAL/natural_neighbor_coordinates_2.h>
#include <CGAL/interpolation_functions.h>

#include <map>
#include <vector>

typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef CGAL::Delaunay_triangulation_2< Kernel > Delaunay_triangulation;

typedef Kernel::FT FieldType;
typedef Kernel::Point_2 MeshType;

struct FieldInterpolator23 {

    Delaunay_triangulation m_triangulation;

    std::map< MeshType, FieldType, Kernel::Less_xy_2 > m_vX;
    std::map< MeshType, FieldType, Kernel::Less_xy_2 > m_vY;
    std::map< MeshType, FieldType, Kernel::Less_xy_2 > m_vZ;

    typedef CGAL::Data_access< std::map< MeshType, FieldType, Kernel::Less_xy_2 > > ValueAccess;

    FieldInterpolator23() {}

    FieldInterpolator23( const std::vector< TN::Vec2 > & mesh, const std::vector< TN::Vec3 > & field )
    {
        const int N = mesh.size();
        for ( int i = 0; i < N; ++i ) {

            MeshType p( mesh[i].x(), mesh[i].y() );

            m_triangulation.insert( p );
            m_vX.insert( std::make_pair( p, field[i].x() ) );
            m_vY.insert( std::make_pair( p, field[i].y() ) ); 
            m_vZ.insert( std::make_pair( p, field[i].z() ) );                        
        }       
    }

    void set( const std::vector< TN::Vec2 > & mesh, const std::vector< TN::Vec3 > & field ) {

        m_triangulation.clear();
        m_vX.clear();
        m_vY.clear();
        m_vZ.clear();

        const int N = mesh.size();
        for ( int i = 0; i < N; ++i ) {

            MeshType p( mesh[i].x(), mesh[i].y() );

            m_triangulation.insert( p );
            m_vX.insert( std::make_pair( p, field[i].x() ) );
            m_vY.insert( std::make_pair( p, field[i].y() ) );
            m_vZ.insert( std::make_pair( p, field[i].z() ) );
        }
    }

    TN::Vec3 operator() ( TN::Vec2 p ) {

        MeshType pos( p.x(), p.y() );

        std::vector< std::pair< MeshType, FieldType > > coords;

        FieldType norm =
            CGAL::natural_neighbor_coordinates_2( m_triangulation, pos, std::back_inserter( coords ) ).second;

        FieldType resX =
            CGAL::linear_interpolation(
                coords.begin(), 
                coords.end(),
                norm,
                ValueAccess( m_vX )
        );

        FieldType resY =
            CGAL::linear_interpolation(
                coords.begin(), 
                coords.end(),
                norm,
                ValueAccess( m_vY )
        );

        FieldType resZ =
            CGAL::linear_interpolation(
                coords.begin(), 
                coords.end(),
                norm,
                ValueAccess( m_vZ )
        );

        return TN::Vec3( resX, resY, resZ );
    }
};

#endif

有人能指出我可接受的更高性能解决方案的方向,无论是不同的库还是算法?

3 个答案:

答案 0 :(得分:3)

CGAL包含a的实现 <{3}}

  

实现了一个用数据结构增强的三角测量,以有效地回答点位置查询。 [...]数据结构仍然很小,可以对实际数据进行快速点位置查询。

对于Delaunay三角测量,它的性能是最佳的。

<小时/> Triangulation Hierarchy
图36.8

答案 1 :(得分:2)

根据我自己的经验,Axis Aligned Bouding Box树对于这个问题是合理有效的(我观察到的性能比#34;在三角测量中行走更多,即使用locate())。

我正在使用我自己的实现(对于3D网格,但可以很容易地适应2D): http://alice.loria.fr/software/geogram/doc/html/classGEO_1_1MeshFacetsAABB.html

AABB树也在CGAL中实现: http://doc.cgal.org/latest/AABB_tree/

注意:如果您的查询点是结构化的(例如,在常规网格上组织),那么有很多方法可以获得大量性能,例如:

  • 在CGAL中使用Delaunay三角剖分的locate()函数,它接受一个提示作为参数,对于提示,使用一个入射到前一个点的三角形。这确保了点位置算法不必走得太远。通过这种方法,增益通常非常显着。如果您不想更改您的类API,您可以拥有一个存储提示的可变成员(如果这些点不是结构化的,但也需要在空间上对它们进行排序,请参阅Marc Glisse的评论)。 / p>

  • &#34;绘图&#34;点上的三角形(每个三角形&#34;发送&#34;它的值到它覆盖的点)。这可以通过&#34;计算机图形&#34;用于在屏幕上绘制三角形的算法。使用这种方法获得的收益更为重要(但需要更多工作,这将彻底改变您的算法)。

答案 2 :(得分:2)

如果网格网格是不变的,则可以使用哈希表在O(1)时间内访问任何条目。假设数据是通过水平x和垂直y值访问的,则在网格周围放置一个边界框,并将其垂直和水平切割成大致等于平均网格元素面积的正方形。将Nx设置为垂直切片的数量,将Ny设置为水平切片的数量。如果网格数据从X_min延伸到X_max并且从Y_min延伸到Y_max,则设置Sx = Nx /(X_max-Xmin)并且Sy = Ny /(Y_max-Y_min)。然后从左到右递增编号垂直列,从圆(Sx * X_min)开始到圆(Sx * X_max)。同样,从圆形(Sy * Y_min)到圆形(Sy * Y_max)从下到上逐渐增加水平行的编号。

如果网格大致为正方形,则大约有1000列和1000行,并且几乎每个正方形都与三角形相关联。如果网格具有不规则的形状或孔,则许多正方形可能不会落在网格上。这对哈希表没有任何影响。要设置哈希表,每个方块最多只能有一个三角形。如果两个或更多个三角形想要与同一个正方形相关联,则正方形太大。要获得更多正方形,请设置较小的区域以获得更大的Nx和Ny。既然每个三角形与某些列#X和行#Y中的一个正方形相关联,则可以生成一百万个关键字。对于#X列和#Y行中的单元格,设置关键字字符串“±#X±#Y”,其中±#X和±#Y表示如果数字大于-1则带有前导“+”符号的整数如果数字小于零,则为“ - ”前导符号。要为所有关键字提供相同的长度,请使用前导零填充整数值。典型的唯一关键字可能看起来像“-0378 + 0087”或“+ 0029-1007”。在这些示例中,每个关键字恰好是10个字符。按照存储三角形的顺序在哈希表中设置关键字。

要使用哈希表,对于给定的x和y点,设置整数ix = round(Sx * x)和整数iy = round(Sy * y)并根据关键字“±ix±iy”设置ix和iy的标志。如果x和y在网格中,关键字“±ix±iy”应该在哈希表中,它应该返回包围x和y的三角形的索引或非常接近该点的三角形。在某些情况下,生成的“±ix±iy”关键字可能不在哈希表中。在这种情况下,哈希表函数将返回一些随机三角形索引。当发生这种情况时,最好的解决方法是将ix和/或iy扰乱一个单元并生成另一个关键字。当然,如果该点位于网格之外,则哈希表可能会发回大量随机三角形索引。因此,请确保该点有效。

这个讨论假设三角形的形状合理且面积大致均匀。如果不是这种情况并且几个三角形必须适合同一个正方形,或者几个正方形必须适合同一个三角形,则修复方法是在侧面生成重定向表。当几个三角形适合同一个方形时,将最中心的三角形分配给方形,同时理解需要快速搜索所需的三角形。当几个正方形必须适合同一个三角形时,将其中一个正方形指向三角形索引,将所有其他正方形指向一个间接表,其索引在三角形索引之后开始。然后,间接表将用于指向包含这些方块的三角形索引。