3D多面体的表面

3D多面体表面

1、介绍

三维多面体曲面由顶点、边、面及其上的入射关系组成。下面的结构是一个半边数据结构,它将可表示曲面的类别限定为可定向的 2-流形-有边界和无边界。如果表面是闭合的,我们称其为多面体,例如,下面的撞木鲛模型:

多面体表面被实现为一个容器类,它管理顶点、半边、小平面及其发生率,并保持它们的组合完整性。它基于halfedge数据结构的高度灵活设计,参见Halfedge Data Structures一章和[3]的介绍。然而,在不了解底层设计的情况下,也可以使用和理解多面体表面。本章中的一些示例也逐渐介绍了这种灵活性的首次应用。

2、定义

一个三维的多面体表面 (Polyhedron_3 PolyhedronTraits_3)由顶点V、边E、面 F 和它们上的入射关系组成。每条边由两个方向相反的半边表示。用半边存储的入射关系如下图所示:

顶点代表空间中的点。边是两个端点之间的直线段。刻面是没有孔的平面多边形。刻面由沿其边界的环形半边序列定义。多面体表面本身可以有孔(至少有两个刻面围绕它,因为单个刻面不能有孔)。沿孔边界的半边称为边界半边,没有入射面。如果一条边的一个半边是边界半边,则该边就是边界边。如果表面不包含半边边界,则该表面是闭合的。封闭曲面是多面体在三个维度上的边界表示。按照惯例,从多面体的外部看,半边沿逆时针方向围绕刻面。这意味着半边围绕顶点顺时针方向。由半边方向定义的刻面的实体边的概念扩展到具有边界边的多面体表面,尽管它们不定义闭合对象。如果考虑面的法线向量,则法线指向外部(遵循右手规则)。

严格的定义可以在 [3]中找到。该定义的一个含义是多面体表面始终是具有边界边缘的可定向和定向的 2 流形,即多面体表面上每个点的邻域要么同胚于圆盘,要么同胚于半圆盘,除了顶点许多带有边界的孔和曲面可以连接起来。另一个含义是避免自相交的最小可表示曲面是三角形(对于具有边界边缘的多面体曲面)或四面体(对于多面体)。可定向 2 流形的边界表示在欧拉运算下是封闭的。它们通过在曲面中创建或关闭孔的操作进行扩展。

不允许除关联关系外的其他交叉点。然而,这不会在操作中自动验证,因为自相交不容易有效地检查。 ( Polyhedron_3<PolyhedronTraits_3>)仅保持多面体表面的组合完整性(使用欧拉运算),不考虑点的坐标或任何其他几何信息。

Polyhedron_3<PolyhedronTraits_3> 可以表示多面体表面以及多面体。该界面的设计方式很容易忽略边界边缘并且仅适用于多面体。

3、示例程序

多面体曲面基于半边数据结构的高度灵活设计。 这种灵活性的示例可以在 Extending Vertices, Halfedges, and Facets 和半边数据结构一章的部分示例 Examples 中找到。 此设计不是理解以下示例的先决条件。 See also the Section sectionPolyAdvanced below for some advanced examples。

3.1、使用默认值的第一个示例

第一个示例使用内核作为特征类来实例化多面体。它创建一个四面体并将对其半边之一的引用存储在Halfedge_handle中。句柄,也称为普通迭代器,用于保留对半边、顶点或面的引用以供将来使用。还有一个用于枚举半边的 Halfedge_iterator 类型。这种迭代器类型可以在任何需要句柄的地方使用。还提供了用于常量多面体的相应 Halfedge_const_handle 和 Halfedge_const_iterator 以及具有 Vertex_ 和 Facet_ 前缀的类似句柄和迭代器。

该示例继续测试半边是否实际指向四面体。该测试检查半边 h 所指的连接分量,而不是整个多面体表面。此示例仅适用于多面体表面的组合级别。下一个示例添加几何。

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polyhedron_3.h>
typedef CGAL::Simple_cartesian<double>     Kernel;
typedef CGAL::Polyhedron_3<Kernel>         Polyhedron;
typedef Polyhedron::Halfedge_handle        Halfedge_handle;
int main() {
    Polyhedron P;
    Halfedge_handle h = P.make_tetrahedron();
    if ( P.is_tetrahedron(h))
        return 0;
    return 1;
}

3.2顶点中的几何实例

我们在四面体的构造中添加几何。四个点作为参数传递给构造。此示例还演示了顶点迭代器的使用以及对顶点中点的访问。注意自然访问符号 v->point()。类似地,存储在顶点、半边和面中的所有信息都可以通过给定句柄或迭代器的成员函数来访问。例如,给定一个半边句柄 h,我们可以编写 h->next() 来获取到下一个半边的半边句柄,h->opposite() 获取相对的半边,h->vertex() 获取在h 的尖端,以此类推。该程序的输出将是:

1 0 0
0 1 0
0 0 1
0 0 0
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polyhedron_3.h>
#include <iostream>
typedef CGAL::Simple_cartesian<double>     Kernel;
typedef Kernel::Point_3                    Point_3;
typedef CGAL::Polyhedron_3<Kernel>         Polyhedron;
typedef Polyhedron::Vertex_iterator        Vertex_iterator;
int main() {
    Point_3 p( 1.0, 0.0, 0.0);
    Point_3 q( 0.0, 1.0, 0.0);
    Point_3 r( 0.0, 0.0, 1.0);
    Point_3 s( 0.0, 0.0, 0.0);
    Polyhedron P;
    P.make_tetrahedron( p, q, r, s);
    CGAL::set_ascii_mode( std::cout);
    for ( Vertex_iterator v = P.vertices_begin(); v != P.vertices_end(); ++v)
        std::cout << v->point() << std::endl;
    return 0;
}

为方便起见,多面体提供了一个点迭代器。上面的 for 循环通过使用 std::copy 和 ostream 迭代器适配器简化为单个语句。

std::copy( P.points_begin(), P.points_end(), 
std::ostream_iterator<Point_3>(std::cout,"\n"));

3.3仿射变换示例

仿射变换 A 可以充当变换点的函子,并且可以方便地为多面体表面定义点迭代器。所以,假设我们只想要多面体 P 的点坐标变换,std::transform 在一行中完成这项工作。

std::transform( P.points_begin(), P.points_end(), P.points_begin(), A);

3.4计算平面方程示例

多面体表面已经规定为每个面存储一个平面方程。但是,它不提供计算平面方程的函数。

此示例计算多面体表面的平面方程。实际计算在 Plane_equation 函子中实现。根据算术(精确/不精确)和刻面的形状(凸/非凸)不同的方法是有用的。我们在这里假设严格的凸面和精确的算术。在我们的示例中,具有 int 坐标的均匀表示就足够了。四面体的四个平面方程是程序的输出。

#include <CGAL/Homogeneous.h>
#include <CGAL/Polyhedron_3.h>
#include <iostream>
#include <algorithm>
struct Plane_equation {
    template <class Facet>
    typename Facet::Plane_3 operator()( Facet& f) {
        typename Facet::Halfedge_handle h = f.halfedge();
        typedef typename Facet::Plane_3  Plane;
        return Plane( h->vertex()->point(),
                      h->next()->vertex()->point(),
                      h->next()->next()->vertex()->point());
    }
};
typedef CGAL::Homogeneous<int>      Kernel;
typedef Kernel::Point_3             Point_3;
typedef Kernel::Plane_3             Plane_3;
typedef CGAL::Polyhedron_3<Kernel>  Polyhedron;
int main() {
    Point_3 p( 1, 0, 0);
    Point_3 q( 0, 1, 0);
    Point_3 r( 0, 0, 1);
    Point_3 s( 0, 0, 0);
    Polyhedron P;
    P.make_tetrahedron( p, q, r, s);
    std::transform( P.facets_begin(), P.facets_end(), P.planes_begin(),
                    Plane_equation());
    CGAL::set_pretty_mode( std::cout);
    std::copy( P.planes_begin(), P.planes_end(),
               std::ostream_iterator<Plane_3>( std::cout, "\n"));
    return 0;
}

3.5使用向量代替列表的示例

多面体类模板实际上有四个参数,其中三个具有默认值。在上面的示例中为三个参数显式使用默认值 - 忽略第四个参数,这将是容器类的标准分配器 - 多面体的定义如下所示:

typedef CGAL::Polyhedron_3< Traits, 
CGAL::Polyhedron_items_3, 
CGAL::HalfedgeDS_default> Polyhedron;

Polyhedron_items_3 类包含用于顶点、边和面的类型。 HalfedgeDS_default 类定义使用的半边数据结构,在这种情况下是基于列表的表示。另一种方法是基于向量的表示。使用向量为多面体表面中的元素提供了随机访问,并且更节省空间,但不能任意删除元素。使用列表允许任意删除,但仅提供双向迭代器并且空间效率较低。以下示例再次创建具有给定点的四面体,但采用基于矢量的表示。

如果保留的容量不足以用于创建的新项目,则基于矢量的表示会自动调整大小。在调整所有句柄、迭代器和循环器的大小时,它们将变得无效。它们在半边数据结构中的正确更新代价高昂,因此建议提前预留足够的空间,如注释中的替代构造函数所示。

先决条件 请注意,多面体而不是底层的半边数据结构会触发调整大小操作,因为调整大小操作需要满足一些先决条件,例如有效发生率,而只有多面体才能保证。

#include <CGAL/Simple_cartesian.h>
#include <CGAL/HalfedgeDS_vector.h>
#include <CGAL/Polyhedron_3.h>
#include <iostream>
typedef CGAL::Simple_cartesian<double>                 Kernel;
typedef Kernel::Point_3                                Point_3;
typedef CGAL::Polyhedron_3< Kernel,
                            CGAL::Polyhedron_items_3,
                            CGAL::HalfedgeDS_vector>   Polyhedron;
int main() {
    Point_3 p( 1.0, 0.0, 0.0);
    Point_3 q( 0.0, 1.0, 0.0);
    Point_3 r( 0.0, 0.0, 1.0);
    Point_3 s( 0.0, 0.0, 0.0);
    Polyhedron P;    // alternative constructor: Polyhedron P(4,12,4);
    P.make_tetrahedron( p, q, r, s);
    CGAL::set_ascii_mode( std::cout);
    std::copy( P.points_begin(), P.points_end(),
           std::ostream_iterator<Point_3>( std::cout, "\n"));
    return 0;
}

3.6循环器写入目标文件格式(OFF)的示例

我们创建一个四面体并使用对象文件格式 (OFF) [5] 将其写入 std::cout。此示例使用 STL 算法(std::copy、std::distance)、STL std::ostream_iterator 和 CGAL 循环器。多面体表面为围绕小平面的逆时针圆形半边序列和围绕顶点的顺时针圆形半边序列提供了方便的环行器。

但是,不建议使用 std::distance 函数在 facet 输出的内部循环中计算顶点索引,因为非随机访问迭代器需要线性时间,这会导致二次运行时间。为了更好的运行时间,顶点索引需要单独存储并在写入构面之前计算一次。例如,它可以存储在顶点本身或散列结构中。另请参见 File I/O 部分。

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polyhedron_3.h>
#include <iostream>
typedef CGAL::Simple_cartesian<double>               Kernel;
typedef Kernel::Point_3                              Point_3;
typedef CGAL::Polyhedron_3<Kernel>                   Polyhedron;
typedef Polyhedron::Facet_iterator                   Facet_iterator;
typedef Polyhedron::Halfedge_around_facet_circulator Halfedge_facet_circulator;
int main() {
    Point_3 p( 0.0, 0.0, 0.0);
    Point_3 q( 1.0, 0.0, 0.0);
    Point_3 r( 0.0, 1.0, 0.0);
    Point_3 s( 0.0, 0.0, 1.0);
    Polyhedron P;
    P.make_tetrahedron( p, q, r, s);
    // Write polyhedron in Object File Format (OFF).
    CGAL::set_ascii_mode( std::cout);
    std::cout << "OFF" << std::endl << P.size_of_vertices() << ' '
              << P.size_of_facets() << " 0" << std::endl;
    std::copy( P.points_begin(), P.points_end(),
               std::ostream_iterator<Point_3>( std::cout, "\n"));
    for (  Facet_iterator i = P.facets_begin(); i != P.facets_end(); ++i) {
        Halfedge_facet_circulator j = i->facet_begin();
        // Facets in polyhedral surfaces are at least triangles.
        CGAL_assertion( CGAL::circulator_size(j) >= 3);
        std::cout << CGAL::circulator_size(j) << ' ';
        do {
            std::cout << ' ' << std::distance(P.vertices_begin(), j->vertex());
        } while ( ++j != i->facet_begin());
        std::cout << std::endl;
    }
    return 0;
}

3.7使用欧拉运算符构建立方体的示例

欧拉运算符重载是修改多面体表面的自然方式。我们为多面体提供了一组操作:split_facet()join_facet()split_vertex()join_vertex()split_loop()join_loop()。我们添加了更方便的运算符,例如 split_edge()。但是,它们可以使用上面的六个运算符来实现。此外,我们提供了更多的运算符来处理具有边界边缘的多面体表面,例如,创建和删除孔。欧拉算子的定义和图解请参考参考手册。

下面的示例实现了一个将单位立方体附加到多面体表面的函数。为了在创建立方体期间跟踪不同的步骤,一系列草图可能有助于为程序代码中出现的不同句柄添加标签。下图显示了从创建序列中选择的六个步骤。这些步骤也标记在程序代码中。

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polyhedron_3.h>
#include <iostream>
template <class Poly>
typename Poly::Halfedge_handle make_cube_3( Poly& P) {
    // appends a cube of size [0,1]^3 to the polyhedron P.
    CGAL_precondition( P.is_valid());
    typedef typename Poly::Point_3         Point;
    typedef typename Poly::Halfedge_handle Halfedge_handle;
    Halfedge_handle h = P.make_tetrahedron( Point( 1, 0, 0),
                                            Point( 0, 0, 1),
                                            Point( 0, 0, 0),
                                            Point( 0, 1, 0));
    Halfedge_handle g = h->next()->opposite()->next();             // Fig. (a)
    P.split_edge( h->next());
    P.split_edge( g->next());
    P.split_edge( g);                                              // Fig. (b)
    h->next()->vertex()->point()     = Point( 1, 0, 1);
    g->next()->vertex()->point()     = Point( 0, 1, 1);
    g->opposite()->vertex()->point() = Point( 1, 1, 0);            // Fig. (c)
    Halfedge_handle f = P.split_facet( g->next(),
                                       g->next()->next()->next()); // Fig. (d)
    Halfedge_handle e = P.split_edge( f);
    e->vertex()->point() = Point( 1, 1, 1);                        // Fig. (e)
    P.split_facet( e, f->next()->next());                          // Fig. (f)
    CGAL_postcondition( P.is_valid());
    return h;
}
typedef CGAL::Simple_cartesian<double>     Kernel;
typedef CGAL::Polyhedron_3<Kernel>         Polyhedron;
typedef Polyhedron::Halfedge_handle        Halfedge_handle;
int main() {
    Polyhedron P;
    Halfedge_handle h = make_cube_3( P);
    return (P.is_tetrahedron(h) ? 1 : 0);
}

4、文件 I/O

库中已经提供了多面体表面的简单文件 I/O。到目前为止,文件 I/O 仅考虑曲面的拓扑及其点坐标。它会忽略可能的平面方程或任何用户添加的属性,例如颜色。

CGAL 中支持的输出和输入的默认文件格式是对象文件格式,OFF,文件扩展名为 .off,通过Geomview [5] 也可以理解。对于 OFF,存在 ASCII 和二进制格式。可以分别使用流的 CGAL 修饰符 set_ascii_mode()set_binary_mode() 选择格式。修饰符 set_pretty_mode()可用于在输出中允许(一些)结构化注释。否则,输出将没有评论。写入的默认值为不带注释的 ASCII。 ASCII 和二进制格式都可以独立于流设置进行读取。由于此文件格式是默认格式,因此为其提供了 iostream 运算符。

#include <CGAL/IO/Polyhedron_iostream.h>
template <class PolyhedronTraits_3>
ostream& operator<<( ostream& out, 
const CGAL::Polyhedron_3<PolyhedronTraits_3>& P);
template <class PolyhedronTraits_3>
istream& operator>>( istream& in, 
CGAL::Polyhedron_3<PolyhedronTraits_3>& P);

支持写入的其他格式包括 OpenInventor (.iv) [7]、VRML 1.0 和 2.0 (.wrl) [1]、[6]、 [2] 和 Wavefront Advanced Visualizer 对象格式 (.obj)。另一个方便的输出函数将多面体表面写入从 CGAL 程序生成的 Geomview 进程。这些输出函数作为流运算符提供,现在作用于相应格式的流类型。

#include <CGAL/IO/Polyhedron_inventor_ostream.h>
#include <CGAL/IO/Polyhedron_VRML_1_ostream.h>
#include <CGAL/IO/Polyhedron_VRML_2_ostream.h>
#include <CGAL/IO/Polyhedron_geomview_ostream.h>
template <class PolyhedronTraits_3>
Inventor_ostream& operator<<( Inventor_ostream& out, 
const CGAL::Polyhedron_3<PolyhedronTraits_3>& P);
template <class PolyhedronTraits_3>
VRML_1_ostream& operator<<( VRML_1_ostream& out, 
const CGAL::Polyhedron_3<PolyhedronTraits_3>& P);
template <class PolyhedronTraits_3>
VRML_2_ostream& operator<<( VRML_2_ostream& out, 
const CGAL::Polyhedron_3<PolyhedronTraits_3>& P);
template <class PolyhedronTraits_3>
Geomview_stream& operator<<( Geomview_stream& out, 
const CGAL::Polyhedron_3<PolyhedronTraits_3>& P);

所有这些文件格式的共同点是它们将一个表面表示为一组刻面。每个方面都是指向一组顶点的索引列表。顶点表示为坐标三元组。多面体表面的文件 I/O Polyhedron_3 对这些格式施加了某些限制。它们必须代表一个允许的多面体表面,例如,一个 2 -流形且没有孤立的顶点,请参阅介绍 Introduction部分。

分发包中的示例 /Polyhedron_IO/ 和 demo/Polyhedron_IO/ 下提供了一些围绕不同文件格式的示例程序。我们展示了一个将 OFF 输入转换为 VRML 1.0 输出的示例。

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/IO/Polyhedron_iostream.h>
#include <CGAL/IO/Polyhedron_VRML_1_ostream.h>
#include <iostream>
typedef CGAL::Simple_cartesian<double> Kernel;
typedef CGAL::Polyhedron_3<Kernel>     Polyhedron;
int main() {
    Polyhedron P;
    std::cin >> P;
    CGAL::VRML_1_ostream out( std::cout);
    out << P;
    return ( std::cin && std::cout) ? 0 : 1;
}

5、扩展顶点、半边和刻面

Example with a Vector Instead of a List Representation示例部分中,我们已经看到了将基于默认列表表示转化为基于向量的表示的底层半边数据结构。现在我们想仔细看看第二个模板参数,Polyhedron_items_3,它指定使用哪种类型的顶点、半边和面。 Polyhedron_items_3 的实现看起来有点涉及嵌套包装类模板。但是忽略这个技术性,剩下的是三个局部类型定义,它们定义了多面体表面的顶点、半边和面。请注意,我们在这里使用 Face 而不是 facet。Face是用于半边数据结构的术语。只有多面体表面的顶层给出别名重命名Face为facet。

typedef CGAL::Polyhedron_3< Traits, 
CGAL::Polyhedron_items_3, 
CGAL::HalfedgeDS_default> Polyhedron;
class Polyhedron_items_3 {
public:
template < class Refs, class Traits>
struct Vertex_wrapper {
typedef typename Traits::Point_3 Point;
typedef CGAL::HalfedgeDS_vertex_base<Refs, CGAL::Tag_true, Point> Vertex;
};
template < class Refs, class Traits>
struct Halfedge_wrapper {
typedef CGAL::HalfedgeDS_halfedge_base<Refs> Halfedge;
};
template < class Refs, class Traits>
struct Face_wrapper {
typedef typename Traits::Plane_3 Plane;
typedef CGAL::HalfedgeDS_face_base<Refs, CGAL::Tag_true, Plane> Face;
};
};

如果我们在参考手册中查找 typedef 中使用的三个类的定义,我们将看到默认多面体使用所有支持的入射、顶点类中的一个点和面类中的平面方程的确认。请注意包装类如何提供两个模板参数,Refs,我们稍后再讨论,以及 Traits,它是多面体表面使用的几何特征类,它在这里为我们提供点和平面方程的类型。

使用这个示例代码,我们可以编写自己的项目类。相反,如果我们只想交换一个类,我们会说明一种更简单的方法。我们使用没有平面方程但添加了颜色属性的更简单的面。为了简化顶点、半边或面类的创建,始终建议从给定的基类之一派生。即使基类不包含数据,它也会提供方便的类型定义。因此,我们从基类派生,必要时重复强制构造函数——这不是面的情况,而是顶点的情况——并添加颜色属性。

template <class Refs>
struct My_face : public CGAL::HalfedgeDS_face_base<Refs> {
CGAL::Color color;
};

当我们将新项目类与多面体表面一起使用时,我们的新面类用于半边数据结构,并且颜色属性在类型 Polyhedron_3::Facet 中可用。但是, Polyhedron_3::Facet 与我们的 My_face 的本地面 typedef 类型不同,但它是从那里派生的。因此,除了构造函数之外,我们放入本地面类型中的所有内容都可以在 Polyhedron_::Facet 类型中使用。有关详细信息,请参阅半边数据结构设计 Halfedge Data Structures的半边数据结构一章。

将所有部分放在一起,完整的示例程序说明了颜色属性一旦定义就可以轻松访问。

#include <CGAL/Simple_cartesian.h>
#include <CGAL/IO/Color.h>
#include <CGAL/Polyhedron_3.h>
// A face type with a color member variable.
template <class Refs>
struct My_face : public CGAL::HalfedgeDS_face_base<Refs> {
    CGAL::Color color;
};
// An items type using my face.
struct My_items : public CGAL::Polyhedron_items_3 {
    template <class Refs, class Traits>
    struct Face_wrapper {
        typedef My_face<Refs> Face;
    };
};
typedef CGAL::Simple_cartesian<double>        Kernel;
typedef CGAL::Polyhedron_3<Kernel, My_items>  Polyhedron;
typedef Polyhedron::Halfedge_handle           Halfedge_handle;
int main() {
    Polyhedron P;
    Halfedge_handle h = P.make_tetrahedron();
    h->facet()->color = CGAL::RED;
    return 0;
}

我们回到包装类的第一个模板参数 Refs。该参数为我们提供了局部类型,允许我们在当前设计中尚未准备好的顶点、半边和面之间进行进一步的引用。这些局部类型是 Polyhedron_3::Vertex_handlePolyhedron_3::Halfedge_handlePolyhedron_3::Facet_handle,以及各自的 .._const_handle。我们现在向一个面类添加一个新的顶点引用,如下所示。可以添加封装和访问功能以获得更彻底的设计,但为简洁起见,我们在此省略。 face 类与 items 类的集成如上所示。

template <class Refs>
struct My_face : public CGAL::HalfedgeDS_face_base<Refs> {
typedef typename Refs::Vertex_handle Vertex_handle;
Vertex_handle vertex_ref;
};

更高级的示例可以在 sectionHdsExamples 中找到,进一步说明了半边数据结构的设计

posted @ 2022-04-05 09:54  彩色回形针  阅读(278)  评论(0编辑  收藏  举报