CGAL笔记之单元格复合体和多面体篇—三维多面体曲面

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


1 介绍

三维多面体曲面由顶点、边、面以及它们之间的重合关系组成。下面的组织是半边数据结构,它将可表示表面的类别限制为可定向的 2-流形 - 有边界和无边界。如果曲面是封闭的,称它为多面体*,*例如以下模型:

在这里插入图片描述

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

2 定义

三维多面体表面由顶点、边、面和它们的重合关系组成。每条边由两个方向相反的半边表示。用半边存储的事件如下图所示:Polyhedron_3<PolyhedronTraits_3>VEF
在这里插入图片描述

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

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

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

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

3 示例程序

多面体曲面基于半边数据结构的高度灵活设计。这种灵活性的示例可以在扩展顶点、半边和面部分以及半边数据结构一章的示例部分中找到。此设计不是理解以下示例的先决条件。

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

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

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

3.3 仿射变换示例

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

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

3.4 示例计算平面方程

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

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

#include <CGAL/Simple_cartesian.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::Simple_cartesian<double> 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::IO::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定义了使用的半边数据结构,在本例中是基于列表的表示。另一种方法是基于矢量的表示。使用向量为多面体表面的元素提供了随机访问并且空间效率更高,但不能任意删除元素。使用列表允许任意删除,但仅提供双向迭代器并且空间效率较低。以下示例再次创建具有给定点的四面体,但采用基于矢量的表示形式。

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

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

#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::IO::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 循环器写入对象文件格式示例(关闭)

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

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

#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::IO::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.
        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 使用 Euler 运算符构建立方体的示例

欧拉算子是修改多面体表面的自然方式。我们为多面体提供了一组操作: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);
}

3.8 画一个多面体

可以通过调用CGAL::draw()函数来可视化多面体,如下例所示。此函数打开一个显示给定多面体的新窗口。对该函数的调用是阻塞的,也就是说,只要用户关闭窗口,程序就会继续。

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/IO/Polyhedron_iostream.h>
#include <CGAL/draw_polyhedron.h>
#include <fstream>
typedef CGAL::Exact_predicates_inexact_constructions_kernel  Kernel;
typedef CGAL::Polyhedron_3<Kernel>                       Polyhedron;
int main(int argc, char* argv[])
{
  Polyhedron P;
  std::ifstream in1((argc>1)?argv[1]:CGAL::data_file_path("meshes/cross_quad.off"));
  in1 >> P;
  CGAL::draw(P);
  return EXIT_SUCCESS;
}

在这里插入图片描述

4 文件输入输出

文件 I/O 目前只考虑表面的拓扑结构及其点坐标。它忽略可能的平面方程或任何用户添加的属性,例如颜色。

CGAL 中支持输出和输入的默认文件格式是目标文件格式 (OFF),文件扩展名为.off. 修饰符set_pretty_mode()可用于在输出中允许(一些)结构化注释。否则,输出将没有注释。写作的默认是没有评论。由于此文件格式是默认格式,因此为它提供了 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)、VRML 1.0 和 2.0 ( .wrl)和.obj。这些输出函数作为流运算符提供,现在作用于相应格式的流类型。

#include <CGAL/IO/Polyhedron_inventor_ostream.h>
#include <CGAL/IO/Polyhedron_VRML_1_ostream.h>
#include <CGAL/IO/Polyhedron_VRML_2_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);

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

5 扩展顶点、半边和小平面

在使用向量而不是列表表示的示例部分中,我们已经了解了如何更改默认列表表示

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

到底层半边数据结构的基于向量的表示。现在我们想仔细看看第二个模板参数,Polyhedron_items_3它指定使用哪种顶点、半边和面。的实现Polyhedron_items_3看起来有点涉及嵌套包装类模板。但是忽略这个技术细节,剩下的是三个局部类型定义,它们定义了多面体表面的、 和Vertex。请注意,我们在这里使用而不是 facet。面是用于半边数据结构的术语。只有多面体表面的顶层给出了别名,可以面对面地重命名。Halfedge``Face``Face

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::IO::Color color;
};

新的项目类派生自旧的项目类,包含面部 typedef 的包装器被覆盖。请注意,包装器的名称及其模板参数是固定的。即使在本例中没有使用模板参数,它们也不能更改。

struct My_items : public CGAL::Polyhedron_items_3 {
template <class Refs, class Traits>
struct Face_wrapper {
typedef My_face<Refs> Face;
};
};

当我们将新的项目类与多面体表面一起使用时,我们的新面类将用于半边数据结构,并且颜色属性在类型中可用Polyhedron_3::Facet。但是,Polyhedron_3::Facet与我们的本地人脸类型定义不同My_face,但它是从中派生的。因此,除了构造函数之外,我们放入本地面类型的所有内容都可以在该Polyhedron_::Facet类型中使用。

将所有部分放在一起,完整的示例程序说明了颜色属性在定义后是多么容易访问。

#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::IO::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::IO::red();
    return 0;
}

我们回到Refs包装类的第一个模板参数 。该参数为我们提供了局部类型,允许我们在顶点、半边和小平面之间进行进一步的引用,这在当前的设计中还没有准备好。这些本地类型分别是Polyhedron_3::Vertex_handle, Polyhedron_3::Halfedge_handle,Polyhedron_3::Facet_handle.._const_handle。我们现在向面类添加一个新的顶点引用,如下所示。可以添加封装和访问功能以进行更彻底的设计。

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

6个 高级示例程序

6.1 创建细分曲面的示例

该程序从标准输入中读取一个多面体曲面,并将经过优化的多面体曲面写入标准输出。输入和输出的对象文件格式为 OFF,文件扩展名为`.off

细化是一个单独的步骤,它是 3 \sqrt{3} 3 -scheme 创建细分曲面的方案。每一步都围绕一个新的中心顶点将一个面细分为三角形,平滑旧顶点的位置,并翻转旧边。该计划是按照这个大纲组织的。在每个部分中,程序都有效地利用了新创建的顶点、边和面已添加到序列末尾的知识。该程序只需要额外的处理内存用于旧顶点的平滑步骤。

在这里插入图片描述

上图显示了三个示例对象,每个都细分了四次。左侧序列的初始对象是三个单元立方体的闭合曲面,它们粘在一起。此处显示的示例程序只能处理闭合曲面,但扩展示例examples/Polyhedron/polyhedron_prog_subdiv_with_boundary.cpp可以处理带边界的曲面。因此,中间序列从其中一个刻面已被移除的同一表面开始。边界细分为一个漂亮的圆圈。第三个序列使用对象表示中的技巧创建锐边。锋利的边缘实际上是一个孔,其顶点坐标将孔挤压关闭以形成边缘。示例目录examples/Polyhedron/包含此处使用的 OFF 文件。

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polyhedron_3.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <fstream>
#include <cassert>
typedef CGAL::Simple_cartesian<double>                       Kernel;
typedef Kernel::Vector_3                                     Vector;
typedef Kernel::Point_3                                      Point;
typedef CGAL::Polyhedron_3<Kernel>                           Polyhedron;
typedef Polyhedron::Vertex                                   Vertex;
typedef Polyhedron::Vertex_iterator                          Vertex_iterator;
typedef Polyhedron::Halfedge_handle                          Halfedge_handle;
typedef Polyhedron::Edge_iterator                            Edge_iterator;
typedef Polyhedron::Facet_iterator                           Facet_iterator;
typedef Polyhedron::Halfedge_around_vertex_const_circulator  HV_circulator;
typedef Polyhedron::Halfedge_around_facet_circulator         HF_circulator;
void create_center_vertex( Polyhedron& P, Facet_iterator f) {
    Vector vec( 0.0, 0.0, 0.0);
    std::size_t order = 0;
    HF_circulator h = f->facet_begin();
    do {
        vec = vec + ( h->vertex()->point() - CGAL::ORIGIN);
        ++ order;
    } while ( ++h != f->facet_begin());
    assert( order >= 3); // guaranteed by definition of polyhedron
    Point center =  CGAL::ORIGIN + (vec / static_cast<double>(order));
    Halfedge_handle new_center = P.create_center_vertex( f->halfedge());
    new_center->vertex()->point() = center;
}
struct Smooth_old_vertex {
    Point operator()( const Vertex& v) const {
        CGAL_precondition((CGAL::circulator_size( v.vertex_begin()) & 1) == 0);
        std::size_t degree = CGAL::circulator_size( v.vertex_begin()) / 2;
        double alpha = ( 4.0 - 2.0 * std::cos( 2.0 * CGAL_PI / degree)) / 9.0;
        Vector vec = (v.point() - CGAL::ORIGIN) * ( 1.0 - alpha);
        HV_circulator h = v.vertex_begin();
        do {
            vec = vec + ( h->opposite()->vertex()->point() - CGAL::ORIGIN)
              * alpha / static_cast<double>(degree);
            ++ h;
            assert( h != v.vertex_begin()); // even degree guaranteed
            ++ h;
        } while ( h != v.vertex_begin());
        return (CGAL::ORIGIN + vec);
    }
};
void flip_edge( Polyhedron& P, Halfedge_handle e) {
    Halfedge_handle h = e->next();
    P.join_facet( e);
    P.split_facet( h, h->next()->next());
}
void subdiv( Polyhedron& P) {
    if ( P.size_of_facets() == 0)
        return;
    // We use that new vertices/halfedges/facets are appended at the end.
    std::size_t nv = P.size_of_vertices();
    Vertex_iterator last_v = P.vertices_end();
    -- last_v;  // the last of the old vertices
    Edge_iterator last_e = P.edges_end();
    -- last_e;  // the last of the old edges
    Facet_iterator last_f = P.facets_end();
    -- last_f;  // the last of the old facets
    Facet_iterator f = P.facets_begin();    // create new center vertices
    do {
        create_center_vertex( P, f);
    } while ( f++ != last_f);
    std::vector<Point> pts;                    // smooth the old vertices
    pts.reserve( nv);  // get intermediate space for the new points
    ++ last_v; // make it the past-the-end position again
    std::transform( P.vertices_begin(), last_v, std::back_inserter( pts),
                    Smooth_old_vertex());
    std::copy( pts.begin(), pts.end(), P.points_begin());
    Edge_iterator e = P.edges_begin();              // flip the old edges
    ++ last_e; // make it the past-the-end position again
    while ( e != last_e) {
        Halfedge_handle h = e;
        ++e; // careful, incr. before flip since flip destroys current edge
        flip_edge( P, h);
    };
    CGAL_postcondition( P.is_valid());
}
int main(int argc, char* argv[]) {
    Polyhedron P;
    std::ifstream in1((argc>1)?argv[1]:CGAL::data_file_path("meshes/cube_quad.off"));
    in1 >> P;
    P.normalize_border();
    if ( P.size_of_border_edges() != 0) {
        std::cerr << "The input object has border edges. Cannot subdivide."
                  << std::endl;
        std::exit(1);
    }
    subdiv( P);
    std::cout << std::setprecision(17) << P;
    return 0;
}

6.2 使用增量构建器和修改器机制的示例

实用程序类Polyhedron_incremental_builder_3有助于从点列表创建多面体表面,然后是表示为点列表索引的小平面列表。这对于实现常见文件格式的文件阅读器特别有用。它在这里用于创建一个三角形。

修改器机制允许以受控方式访问多面体表面的内部表示,即半边数据结构。修饰符基本上是使用函数对象的回调机制。调用时,函数对象接收内部 halfedge 数据结构作为参数并可以对其进行修改。返回时,多面体可以检查半边数据结构的有效性。这样的修饰符对象必须始终返回一个半边数据结构,该数据结构是一个有效的多面体表面。有效性检查在成员函数的末尾作为昂贵的后置条件实现Polyhedron_3::delegate(),即默认情况下不调用它,只有在激活昂贵的检查时才会调用。

在这个例子中,Build_triangle是这样一个函数对象派生自. 多面体的成员函数接受这个函数对象,并通过引用其内部使用的半边数据结构来调用它。因此,这个成员函数可以在半边数据结构中创建三角形。Modifier_base<HalfedgeDS>``Polyhedron_3::delegate()``Modifier_base::operator()()``Build_triangle

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Polyhedron_incremental_builder_3.h>
#include <CGAL/Polyhedron_3.h>
#include <cassert>
// A modifier creating a triangle with the incremental builder.
template <class HDS>
class Build_triangle : public CGAL::Modifier_base<HDS> {
public:
    Build_triangle() {}
    void operator()( HDS& hds) {
        // Postcondition: hds is a valid polyhedral surface.
        CGAL::Polyhedron_incremental_builder_3<HDS> B( hds, true);
        B.begin_surface( 3, 1, 6);
        typedef typename HDS::Vertex   Vertex;
        typedef typename Vertex::Point Point;
        B.add_vertex( Point( 0, 0, 0));
        B.add_vertex( Point( 1, 0, 0));
        B.add_vertex( Point( 0, 1, 0));
        B.begin_facet();
        B.add_vertex_to_facet( 0);
        B.add_vertex_to_facet( 1);
        B.add_vertex_to_facet( 2);
        B.end_facet();
        B.end_surface();
    }
};
typedef CGAL::Simple_cartesian<double>     Kernel;
typedef CGAL::Polyhedron_3<Kernel>         Polyhedron;
typedef Polyhedron::HalfedgeDS             HalfedgeDS;
int main() {
    Polyhedron P;
    Build_triangle<HalfedgeDS> triangle;
    P.delegate( triangle);
    assert( P.is_triangle( P.halfedges_begin()));
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值