3D Surface Subdivision Methods 3D 曲面细分方法

文章目录

  • 1 介绍
  • 2 细分法
  • 3 一个简单的例子:Catmull-Clark 细分
  • 4 Catmull-Clark 细化
  • 5 Refinement Host
  • 6 Geometry Policy
  • 7 四种细分方法
  • 8 示例:自定义细分方法
  • 9 实施历史

原文地址: https://doc.cgal.org/latest/Subdivision_method_3/index.html#Chapter_3D_Surface_Subdivision_Methods

细分方法递归地细化控制网格并生成逼近极限表面的点。 该包由四种流行的细分方法及其细化主机组成。 支持的细分方法包括 Catmull-Clark、Loop、Doo-Sabin 和 √3 细分。 它们各自的细化宿主是 Pqq、Ptq、Dqq 和 √ 3细化。 通过替换细化主机的几何计算,可以轻松扩展这些方法的变体。

在这里插入图片描述

1 介绍

细分方法是从任意多边形网格生成平滑表面的简单而强大的方法。 与基于样条的曲面(例如 NURBS)或其他基于数值的建模技术不同,细分方法的用户不需要细分方法的数学知识。 几何形状的自然直觉足以控制细分方法。

Subdivision_method_3 适用于 Polyhedron_3 和 Surface_mesh 类,因为它们是 MutableFaceGraph 概念的模型,其目标是易于使用和扩展。 Subdivision_method_3 不是一个类,而是一个命名空间,其中包含四种流行的细分方法及其细化函数。 其中包括 Catmull-Clark、Loop、Doo-Sabin 和 √ 3细分。 通过替换细化主机的几何计算,可以轻松扩展这些方法的变体。

2 细分法

在本章中,我们将解释细分方法的一些基础知识。 我们仅关注有助于您理解包设计的主题。 有关细分方法的详细信息可以在[6]中找到。 本节介绍的一些术语将在后面的章节中再次使用。 如果您只对使用特定的细分方法感兴趣, 一个快速示例:Catmull-Clark 细分提供了有关使用 Catmull-Clark 细分的快速教程。

细分方法递归地细化粗网格并生成更接近光滑表面的近似值。 粗网格可以具有任意形状,但必须是 二维流形。 在 二维 流形中,每个内部点都有一个与 2D 圆盘同胚的邻域。 非流形的细分方法已经开发出来,但在 Subdivision_method_3 中没有考虑。 该章节预告片展示了在 CAD 模型上进行 Catmull-Clark 细分的步骤。 通过四等分模式反复细化粗网格,并生成新的点以近似平滑的表面。

实践中使用了许多细化模式。 Subdivision_method_3 支持四种最流行的模式,每种模式都被 Catmull-Clark[1]、Loop[4]、Doo-Sabin[2] 和 √3 subdivision[3] 使用(下图中从左到右) 。 我们通过它们的拓扑特征而不是相关的细分方法来命名这些模式。 PQQ 表示原始四边形四边形。 PTQ 表示原三角形四等分。 DQQ 表示对偶四边形四等分。 √3表示三角剖分向细分面收敛的速度。

在这里插入图片描述

该图演示了 5 价顶点/面的 1-disk上的这四种细化模式。 细化的网格显示在源网格下方。 细化网格上的点是通过对源网格上的相邻点进行平均而生成的。 称为模板的图确定其点对细化点的位置有贡献的源邻域。 细化模式通常定义多个模板。 例如,PQQ 细化有一个顶点节点模板,它定义了输入顶点的 1 环; 边缘节点模板,定义输入边缘的 1 环; 以及一个面节点模板,它定义了一个输入面。 PQQ 细化的模板如下图所示。 顶行中的蓝色邻域表示红色细化节点的相应模板。

在这里插入图片描述

带有权重的模板称为几何模板。细分方法为每个模板定义一个几何掩模,并通过对掩模加权的源点进行平均来生成新的点。几何掩模是精心挑选的,以满足一定的表面光滑度和形状质量的要求。Catmull-Clark细分的几何掩模如下图所示。

在这里插入图片描述

此处显示的权重未标准化,n 是顶点的价数。 生成的点(红色)是通过加权点的总和来计算的。 例如,Catmull-Clark 面节点是通过对其模板上每个点的 1/4 求和来计算的。

模板可以具有无限数量的几何蒙版。 例如,PQQ 细化的面部节点可以通过每个模板节点的 1/5 而不是 1/4 的总和来计算。 尽管在 Subdivision_method_3 中具有任何类型的几何蒙版都是合法的,但结果曲面可能是奇数、不平滑或什至不存在。 [6] 解释了为质量细分表面设计掩模的细节。

3 一个简单的例子:Catmull-Clark 细分

假设您熟悉 Surface_mesh,您可以毫不费力地将 Subdivision_method_3 集成到您的程序中。

文件 Subdivision_method_3/CatmullClark_subdivision.cpp

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/boost/graph/graph_traits_Surface_mesh.h>
#include <CGAL/subdivision_method_3.h>
#include <CGAL/Timer.h>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <fstream>
typedef CGAL::Simple_cartesian<double>         Kernel;
typedef CGAL::Surface_mesh<Kernel::Point_3>    PolygonMesh;
using namespace std;
using namespace CGAL;
namespace params = CGAL::parameters;
int main(int argc, char** argv) {
  if (argc > 4) {
    cerr << "Usage: CatmullClark_subdivision [d] [filename_in] [filename_out] \n";
    cerr << "         d -- the depth of the subdivision (default: 1) \n";
    cerr << "         filename_in -- the input mesh (.off) (default: data/quint_tris.off) \n";
    cerr << "         filename_out -- the output mesh (.off) (default: result.off)" << endl;
    return 1;
  }
  int d = (argc > 1) ? boost::lexical_cast<int>(argv[1]) : 1;
  const std::string in_file = (argc > 2) ? argv[2] : CGAL::data_file_path("meshes/quint_tris.off");
  const char* out_file = (argc > 3) ? argv[3] : "result.off";
  PolygonMesh pmesh;
  std::ifstream in(in_file);
  if(in.fail()) {
    std::cerr << "Could not open input file " << in_file << std::endl;
    return 1;
  }
  in >> pmesh;
  Timer t;
  t.start();
  Subdivision_method_3::CatmullClark_subdivision(pmesh, params::number_of_iterations(d));
  std::cerr << "Done (" << t.time() << " s)" << std::endl;
  std::ofstream out(out_file);
  out << pmesh;
  return 0;
}

此示例演示了在 Surface_mesh 上使用 Catmull-Clark 细分方法。 只有一行值得详细解释:

Subdivision_method_3::CatmullClark_subdivision(pmesh, params::number_of_iterations(d));

Subdivision_method_3 指定细分函数的命名空间。 CatmullClark_subdivision(P, params::number_of_iterations(d)) 在 d 次细化迭代后计算多边形网格 pmesh 的 Catmull-Clark 细分曲面。 多边形网格 pmesh 通过引用传递,并通过细分函数进行修改(即细分)。

此示例展示如何使用 Subdivision_method_3 细分 Surface_mesh。 应用程序定义的多边形网格可以使用专门的内核和/或专门的内部容器。 应用程序定义的多边形网格与 Subdivision_method_3 一起使用有一个主要限制:内部容器中的基元(例如顶点、半边和面)是按顺序排序的(例如 std::vector 和 std::list)。 这意味着迭代器按照原语的创建/插入顺序遍历原语。

细化版主对这两个限制做了详细的解释。

4 Catmull-Clark 细化

Subdivision_method_3 旨在允许自定义细分方法。 本节介绍 Subdivision_method_3 中 Catmull-Clark 细分函数的实现。 该实现演示了将 PQQ 细化定制为 Catmull-Clark 细分。

当开发细分方法时,选择细化模式,然后开发一组几何掩模来定位新点。 实现细分方法需要三个关键组件:

  • 可以表示任意 二维流形的网格数据结构,
  • 细化网格数据结构的过程,
  • 以及计算新点的几何掩模。

E. Catmull 和 J. Clark 选择 PQQ 细化作为其细分方法,并开发了一组几何掩模来从控制网格生成(或更准确地说,近似)B 样条曲面。Subdivision_method_3提供了一个将 Catmull-Clark 细分方法的所有三个组成部分粘合在一起的函数。

template <class PolygonMesh, class Mask, class NamedParameters>
void PQQ(PolygonMesh& p, Mask mask, NamedParameters np)

PolygonMesh 必须是 Polyhedron_3、Surface_mesh 或 MutableFaceGraph 概念的任何其他模型的实例。 它是任意 二维 流形的通用网格数据结构。 PQQ() 细化控制网格 p,是一个细化主机,它使用策略类 Mask 作为其几何计算的一部分。 在细化过程中,PQQ() 通过与掩模配合来计算并分配新点。 为了实现Catmull-Clark细分,几何策略Mask必须实现Catmull-Clark细分的几何掩模。 迭代次数以及顶点图可以使用命名参数 np 来指定。

为了实现几何掩码,我们需要知道细化主机如何与其几何掩码进行通信。 PQQ 细化定义了三个模板,因此 Catmull-Clark 细分需要三个几何掩模。 以下类定义了用于 PQQ 细化的模板的接口。

template <class PolygonMesh>
class PQQMask_3 {
  void face_node(boost::graph_traits<PolygonMesh>::face_descriptor face, Point_3& pt);
  void edge_node(boost::graph_traits<PolygonMesh>::halfedge_descriptor edge, Point_3& pt);
  void vertex_node(boost::graph_traits<PolygonMesh>::vertex_descriptor vertex, Point_3& pt);
};

PQQMask_3 中的每个类函数根据图元描述符的邻域计算一个新点,并将新点分配给 Point_3& pt。

我们用 Catmull-Clark 细分的几何掩模来实现每个类函数。

template <class PolygonMesh>
class CatmullClark_mask_3 {
  typedef boost::graph_traits<PolygonMesh>::face_descriptor face_descriptor;
  typedef boost::property_traits<VertexPointMap>::value_type Point_3;
  Polygonmesh pmesh;
  VertexPointMap vpm;
  CatmullClark_mask_3(PolygonMesh &pmesh, VertexPointMap vpm)
  : pmesh(pmesh, vpm(vpm)
  {}
  void face_node(face_descriptor face, Point_3& pt) {
    int n = 0;
    Point_3 p(0,0,0);
    for(halfedge_descriptor hd : halfedges_around_face(face,pmesh)){
      p = p + get(vpm, (target(hd,pmesh)) - ORIGIN);
      ++n;
    }
    pt = ORIGIN + (p - ORIGIN)/FT(n);
  }
};
  void edge_node(halfedge_descriptor edge, Point_3& pt) {
    Point_3 p1 = get(vpm,target(edge, pmesh));
    Point_3 p2 = get(vpm,source(edge, pmesh));
    Point_3 f1, f2;
    face_node(face(edge,pmesh), f1);
    face_node(face(opposite(edge,pmesh),pmesh), f2);
    pt = Point_3((p1[0]+p2[0]+f1[0]+f2[0])/4,
                 (p1[1]+p2[1]+f1[1]+f2[1])/4,
                 (p1[2]+p2[2]+f1[2]+f2[2])/4 );
  }
  void vertex_node(vertex_descriptor vertex, Point_3& pt) {
    Halfedge_around_target_circulator<PolygonMesh> vcir(vertex,pmesh);
    typename boost::graph_traits<PolygonMesh>::degree_size_type n = degree(vertex,pmesh);
    FT Q[] = {0.0, 0.0, 0.0}, R[] = {0.0, 0.0, 0.0};
    Point_3 S = get(vpm,vertex);
    Point_3 q;
    for (int i = 0; i < n; i++, ++vcir) {
      Point_3 p2 = get(vpm, target(opposite(*vcir,pmesh),pmesh));
      R[0] += (S[0]+p2[0])/2;
      R[1] += (S[1]+p2[1])/2;
      R[2] += (S[2]+p2[2])/2;
      face_node(face(*vcir,pmesh), q);
      Q[0] += q[0];
      Q[1] += q[1];
      Q[2] += q[2];
    }
    R[0] /= n;    R[1] /= n;    R[2] /= n;
    Q[0] /= n;    Q[1] /= n;    Q[2] /= n;
    pt = Point_3((Q[0] + 2*R[0] + S[0]*(n-3))/n,
                 (Q[1] + 2*R[1] + S[1]*(n-3))/n,
                 (Q[2] + 2*R[2] + S[2]*(n-3))/n );
  }
};

为了调用 Catmull-Clark 细分方法,我们使用刚刚定义的 Catmull-Clark 掩码调用 PQQ()。

PQQ(pmesh, CatmullClark_mask_3(pmesh), params::number_of_iterations(depth));

Loop、Doo-Sabin 和 √3 细分使用类似的过程来实现:选择细化主机并实施几何策略。 开发自己的细分方法的关键是实现细化主体和几何策略的正确组合。 接下来的两节将对此进行解释。

5 Refinement Host

细化主机是多边形网格类和几何掩模类的模板函数。 它细化输入的多边形网格,并通过几何蒙版计算新点。 Subdivision_method_3 支持四种细化主机:原始四边形四等分 (PQQ)、原始三角形四等分 (PTQ)、对偶四边形四等分 (DQQ) 和 √3 三角剖分。 它们分别由 Catmull-Clark、Loop、Doo-Sabin 和 √3 细分使用。

在这里插入图片描述

namespace Subdivision_method_3 {
template <class PolygonMesh, class Mask, class NamedParameters>
void PQQ(PolygonMesh& pmesh, Mask mask, NamedParameters np);
template <class PolygonMesh, class Mask, class NamedParameters>
void PTQ(PolygonMesh& pmesh, Mask mask, NamedParameters np);
template <class PolygonMesh,  class Mask, class NamedParameters>
void DQQ(PolygonMesh& pmesh, Mask mask, NamedParameters np)
template <class PolygonMesh,  class Mask, class NamedParameters>
void Sqrt3(PolygonMesh& pmesh, Mask mask, NamedParameters np)
}

网格类必须是MutableFaceGraph的模型,并且必须是三角形网格或多边形网格,掩模是实现细分方法的几何掩模的策略类。

细化主机细化输入多边形网格,维护模板(即控制网格和细化网格之间的映射),并调用几何蒙版来计算新点。 在 Subdivision_method_3 中,细化被实现为一系列连接操作(主要是欧拉操作)。 连接操作的顺序在维护模板时起着关键作用。 通过将源子网格的顺序与细化顶点相匹配,基元中不需要任何标志来注册模板。 它避免了细化主机对多边形网格类的数据依赖。 为了使排序技巧发挥作用,多边形网格类必须有一个顺序容器(例如向量或链表)作为内部存储。 顺序容器保证多边形网格的迭代器始终按照图元的插入顺序遍历图元。 非顺序结构(例如树或映射)不提供所需的排序,因此不能与 Subdivision_method_3 一起使用。

尽管 Subdivision_method_3 不需要标志来支持细化和模板,但它仍然需要知道如何计算和存储几何数据(即点)。 Subdivision_method_3 的类具有作为可选模板参数的顶点属性映射,该映射提供顶点和点之间的映射。

细化主机 PQQ 和 DQQ 在一般多边形网格上工作,PTQ 和 Sqrt3 在三角多边形网格上工作。 PTQ 和 Sqrt3 在非三角多边形网格上的结果未定义。 Subdivision_method_3在细化之前不验证网格特性的前提条件。

有关细化实现的详细信息,感兴趣的用户应参考[5]。

6 Geometry Policy

几何策略定义了一组几何掩码。 每个几何掩模都被实现为计算细分表面的新点的成员函数。

每个几何掩模接收控制网格的图元描述符(例如halfedge_descriptor),并将Point_3返回到细分的顶点。 该函数收集基元描述符的顶点邻居(即模板上的节点),并根据邻居和掩码(即模板权重)计算新点。

在这里插入图片描述

该图显示了 Catmull-Clark 细分的几何掩模。 此处显示的权重未标准化,n 是顶点的价数。 新点是通过模板上加权点的总和来计算的。 以下代码显示了面节点的几何掩模的实现。 Catmull-Clark 几何策略的完整列表位于 Catmull-Clark 细分部分。

template <class PolygonMesh, class VertexPointMap>
class CatmullClark_mask_3 {
  typedef boost::graph_traits<PolygonMesh>::face_descriptor face_descriptor;
  typedef boost::property_traits<VertexPointMap>::value_type Point_3;
  CatmullClark_mask_3(PolygonMesh &pmesh, VertexPointMap vpm);
  void face_node(face_descriptor face, Point_3& pt) {
    int n = 0;
    Point_3 p(0,0,0);
    for(halfedge_descriptor hd : halfedges_around_face(face,pmesh)){
      p = p + get(vpm, (target(hd,pmesh)) - ORIGIN);
      ++n;
    }
    pt = ORIGIN + (p - ORIGIN)/FT(n);
  }
};

在此示例中,计算基于 Point_3 是 CGAL::Point_3 的假设。 这是一个假设,但不是限制。 您可以使用任何点类,只要它在多边形网格中定义为 Point_3 即可。 您可能需要修改几何策略以支持特殊点的计算和分配。 这种扩展在图形应用程序中并不罕见。 例如,您可能想要细分细分曲面的纹理坐标。

Catmull-Clark 细分的细化主机需要三个用于没有开放边界的多边形网格的几何蒙版:顶点节点蒙版、边缘节点蒙版和面节点蒙版。 为了支持具有边界的多边形网格,还需要边界节点掩码。 下面列出了 Catmull-Clark 细分的边界节点掩码,其中 ept 返回分割边的新点,vpt 返回边所指向的顶点上的新点。

void border_node(halfedge_descriptor edge, Point_3& ept, Point_3& vpt) {
  Point_3 ep1 = get(vpm, target(edge, pmesh));
  Point_3 ep2 = get(vpm, target(opposite(edge, pmesh), pmesh));
  ept = Point_3((ep1[0]+ep2[0])/2, (ep1[1]+ep2[1])/2, (ep1[2]+ep2[2])/2);
  Halfedge_around_target_circulator<Poly> vcir(edge, pmesh);
  Point_3 vp1  = get(vpm,target(opposite(*vcir, pmesh ), pmesh));
  Point_3 vp0  = get(vpm, target(*vcir, pmesh));
  --vcir;
  Point_3 vp_1 = get(vpm, target(opposite(*vcir, pmesh), pmesh));
  vpt = Point_3((vp_1[0] + 6*vp0[0] + vp1[0])/8,
                (vp_1[1] + 6*vp0[1] + vp1[1])/8,
                (vp_1[2] + 6*vp0[2] + vp1[2])/8 );
}

下面列出了所有四个细化主机的掩码接口。 DQQMask_3 没有边界节点模板,因为 DQQ 细化的细化主机不支持当前版本中的全局边界。 这可能会在未来的版本中发生变化。

template <class PolygonMesh>
class PQQMask_3 {
  void face_node(boost::graph_traits<PolygonMesh>::face_descriptor, Point_3&);
  void edge_node(boost::graph_traits<PolygonMesh>::halfedge_descriptor, Point_3&);
  void vertex_node(boost::graph_traits<PolygonMesh>::vertex_descriptor, Point_3&);
  void border_node(boost::graph_traits<PolygonMesh>::halfedge_descriptor, Point_3&, Point_3&);
};
template <class PolygonMesh>
class PTQMask_3 {
  void edge_node(boost::graph_traits<PolygonMesh>::halfedge_descriptor, Point_3&);
  void vertex_node(boost::graph_traits<PolygonMesh>::vertex_descriptor, Point_3&);
  void border_node(boost::graph_traits<PolygonMesh>::halfedge_descriptor, Point_3&, Point_3_&);
};
template <class PolygonMesh>
class DQQMask_3 {
public:
  void corner_node(boost::graph_traits<PolygonMesh>::halfedge_descriptor edge, Point_3& pt);
};
template <class PolygonMesh>
class Sqrt3Mask_3 {
public:
  void vertex_node(boost::graph_traits<PolygonMesh>::vertex_descriptor vertex, Point_3& pt);
};

Catmull Clark mask_3、Loop_mask_3、DooSabin_mask_3 和 Sqrt3_mask_3 的源代码是学习这些模板接口的最佳来源。

7 四种细分方法

Subdivision_method_3 通过专门化各自的细化主机来支持 Catmull-Clark、Loop、Doo-Sabin 和 √3 细分。 它们被设计用于任何 MutableFaceGraph 模型,例如 Polyhedron_3 和 Surface_mesh。 如果您的应用程序使用具有专门几何内核的多边形网格,则需要使用基于该内核的几何策略来专门化细化主机。

namespace Subdivision_method_3 {
  template <class PolygonMesh, class NamedParameters>
  void CatmullClark_subdivision(PolygonMesh& pmesh, NamedParameters np) {
    PQQ(pmesh, CatmullClark_mask_3<PolygonMesh>(pmesh), np);
  }
  template <class PolygonMesh, class NamedParameters>
  void Loop_subdivision(PolygonMesh& pmesh, NamedParameters np) {
    PTQ(pmesh, Loop_mask_3<PolygonMesh>(pmesh) , np);
  }
  template <class PolygonMesh, class NamedParameters>
  void DooSabin_subdivision(PolygonMesh& pmesh, NamedParameters np) {
    DQQ(pmesh, DooSabin_mask_3<PolygonMesh>(pmesh), np);
  }
  template <class PolygonMesh, class NamedParameters>
  void Sqrt3_subdivision(PolygonMesh& pmesh, NamedParameters np) {
    Sqrt3(pmesh, Sqrt3_mask_3<PolygonMesh>(pmesh), np);
  }
}

8 示例:自定义细分方法

Subdivision_method_3 支持在 Polyhedron_3 上使用具有笛卡尔坐标的点的四种实用细分方法。 通过具有自定义几何掩模的细化主机的专业化,可以支持更多细分方法。 以下示例开发了一种细分方法,生成改进的循环细分曲面。

文件 Subdivision_method_3/Customized_subdivision.cpp

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/boost/graph/graph_traits_Surface_mesh.h>
#include <CGAL/subdivision_method_3.h>
#include <CGAL/Timer.h>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <fstream>
typedef CGAL::Simple_cartesian<double>      Kernel;
typedef CGAL::Surface_mesh<Kernel::Point_3> PolygonMesh;
using namespace std;
using namespace CGAL;
namespace params = CGAL::parameters;
// ======================================================================
template <class Poly>
class WLoop_mask_3 {
  typedef Poly                                         PolygonMesh;
  typedef typename boost::graph_traits<PolygonMesh>::vertex_descriptor   vertex_descriptor;
  typedef typename boost::graph_traits<PolygonMesh>::halfedge_descriptor halfedge_descriptor;
  typedef typename boost::property_map<PolygonMesh, vertex_point_t>::type Vertex_pmap;
  typedef typename boost::property_traits<Vertex_pmap>::value_type Point;
  typedef typename boost::property_traits<Vertex_pmap>::reference Point_ref;
  PolygonMesh& pmesh;
  Vertex_pmap vpm;
public:
  WLoop_mask_3(PolygonMesh& pmesh)
    : pmesh(pmesh), vpm(get(CGAL::vertex_point, pmesh))
  {}
  void edge_node(halfedge_descriptor hd, Point& pt) {
    Point_ref p1 = get(vpm, target(hd,pmesh));
    Point_ref p2 = get(vpm, target(opposite(hd,pmesh),pmesh));
    Point_ref f1 = get(vpm, target(next(hd,pmesh),pmesh));
    Point_ref f2 = get(vpm, target(next(opposite(hd,pmesh),pmesh),pmesh));
    pt = Point((3*(p1[0]+p2[0])+f1[0]+f2[0])/8,
               (3*(p1[1]+p2[1])+f1[1]+f2[1])/8,
               (3*(p1[2]+p2[2])+f1[2]+f2[2])/8 );
  }
  void vertex_node(vertex_descriptor vd, Point& pt) {
    double R[] = {0.0, 0.0, 0.0};
    Point_ref S = get(vpm,vd);
    std::size_t n = 0;
    for(halfedge_descriptor hd : halfedges_around_target(vd, pmesh)){
      ++n;
      Point_ref p = get(vpm, target(opposite(hd,pmesh),pmesh));
      R[0] += p[0];         R[1] += p[1];         R[2] += p[2];
    }
    if (n == 6) {
      pt = Point((10*S[0]+R[0])/16, (10*S[1]+R[1])/16, (10*S[2]+R[2])/16);
    } else if (n == 3) {
      double B = (5.0/8.0 - std::sqrt(3+2*std::cos(6.283/n))/64.0)/n;
      double A = 1-n*B;
      pt = Point((A*S[0]+B*R[0]), (A*S[1]+B*R[1]), (A*S[2]+B*R[2]));
    } else {
      double B = 3.0/8.0/n;
      double A = 1-n*B;
      pt = Point((A*S[0]+B*R[0]), (A*S[1]+B*R[1]), (A*S[2]+B*R[2]));
    }
  }
  void border_node(halfedge_descriptor hd, Point& ept, Point& vpt) {
    Point_ref ep1 = get(vpm, target(hd,pmesh));
    Point_ref ep2 = get(vpm, target(opposite(hd,pmesh),pmesh));
    ept = Point((ep1[0]+ep2[0])/2, (ep1[1]+ep2[1])/2, (ep1[2]+ep2[2])/2);
    Halfedge_around_target_circulator<Poly> vcir(hd,pmesh);
    Point_ref vp1  = get(vpm, target(opposite(*vcir,pmesh),pmesh));
    Point_ref vp0  = get(vpm, target(*vcir,pmesh));
    --vcir;
    Point_ref vp_1 = get(vpm,target(opposite(*vcir,pmesh),pmesh));
    vpt = Point((vp_1[0] + 6*vp0[0] + vp1[0])/8,
                (vp_1[1] + 6*vp0[1] + vp1[1])/8,
                (vp_1[2] + 6*vp0[2] + vp1[2])/8 );
  }
};
int main(int argc, char **argv) {
  if (argc > 4) {
    cerr << "Usage: Customized_subdivision [d] [filename_in] [filename_out] \n";
    cerr << "         d -- the depth of the subdivision (default: 1) \n";
    cerr << "         filename_in -- the input mesh (.off) (default: data/quint_tris.off) \n";
    cerr << "         filename_out -- the output mesh (.off) (default: result.off)" << endl;
    return 1;
  }
  int d = (argc > 1) ? boost::lexical_cast<int>(argv[1]) : 1;
  const std::string in_file = (argc > 2) ? argv[2] : CGAL::data_file_path("meshes/quint_tris.off");
  const char* out_file = (argc > 3) ? argv[3] : "result.off";
  PolygonMesh pmesh;
  std::ifstream in(in_file);
  if(in.fail()) {
    std::cerr << "Could not open input file " << in_file << std::endl;
    return 1;
  }
  in >> pmesh;
  Timer t;
  t.start();
  Subdivision_method_3::PTQ(pmesh, WLoop_mask_3<PolygonMesh>(pmesh), params::number_of_iterations(d));
  std::cerr << "Done (" << t.time() << " s)" << std::endl;
  std::ofstream out(out_file);
  out << pmesh;
  return 0;
}

几何掩模生成的点在语义上需要收敛到光滑的表面。 这是细分曲面理论提出的要求。 Subdivision_method_3 不强制执行此要求,也不会验证细分网格的平滑度。 Subdivision_method_3保证了细分网格的拓扑属性。 属-n 2-流形确保被细分为属-n 2-流形。 但是,当专门处理设计不当的几何蒙版时,Subdivision_method_3 可能会生成奇怪的、不平滑的或什至不存在的表面。

9 实施历史

该软件包最初由 Le-Jeng Andy Shiue 开发。 对于 CGAL 4.11,它被 Andreas Fabri 和 Mael Rouxel-Labbé 推广到适用于任何 MutableFaceGraph 模型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/303397.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

美创科技葛宏彬:夯实安全基础,对医疗数据风险“逐个击破”

导读 解决医疗机构“临床业务数据合规流动”与“重要数据安全防护”两大难题。 2023年11月11日&#xff0c;在2023年南湖HIT论坛上&#xff0c;HIT专家网联合杭州美创科技股份有限公司&#xff08;以下简称美创科技&#xff09;发布《医疗数据安全风险分析及防范实践》白皮书…

小程序学习基础(页面加载)

打开首页&#xff0c;然后点击第一个按钮进去心得页面 进入心得页面以后 第一个模块是轮播图用的是swiper组件&#xff0c;然后就是四个按钮绑定点击事件&#xff0c;最后就是下拉刷新&#xff0c;下拉滚动&#xff0c;上拉加载。代码顺序wxml,js,wcss,json。 <!--pages/o…

Python——python编译器安装教程

1.前往python官网下载安装程序 python官网 python编译器安装程序下载网站 2.找到自己需要的版本&#xff0c;下载对应的安装程序&#xff0c;运行程序 打开安装包&#xff0c;切记要勾选add python 3.9 to PATH 可选择自动安装&#xff08;Install Now&#xff09;或点击自定义…

Intellij-idea 如何编译maven工程

在IntelliJ IDEA中编译Maven工程的过程如下所示&#xff1a; 打开IntelliJ IDEA并导入Maven工程。选择"File"&#xff08;文件&#xff09;菜单&#xff0c;然后选择"Open"&#xff08;打开&#xff09;或者"Open Project"&#xff08;打开项目…

JVM工作原理与实战(十):类加载器-Java类加载器

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、介绍 二、扩展类加载器 三、通过扩展类加载器去加载用户jar包 1.放入/jre/lib/ext下进行扩展 2.使用参数进行扩展 四、应用程序类加载器 总结 前言 ​JVM作为Java程序的运行…

WPF常用控件-Window

常用属性 这里重点记录一些关键且容易忘记的属性&#xff0c;那些很常用的如Title啥的就不在这里一一说明了。 任务栏按钮 ShowInTaskbar&#xff1a;是否在任务栏中显示应用按钮&#xff0c;默认为True。 层级 Topmost&#xff1a;应用是否始终在所有应用的最上层&#x…

MongoDB分片集群架构详解

分片简介 分片&#xff08;shard&#xff09;是指在将数据进行水平切分之后&#xff0c;将其存储到多个不同的服务器节点上的一种扩展方式。分片在概念上非常类似于应用开发中的“水平分表”。不同的点在于&#xff0c;MongoDB 本身就自带了分片管理的能力&#xff0c;对于开发…

Tensorflow2.0笔记 - 基本数据类型,数据类型转换

【TensorFlow2.0】(1) tensor数据类型&#xff0c;类型转换_tensorflow tensor转int-CSDN博客文章浏览阅读1.5w次&#xff0c;点赞8次&#xff0c;收藏28次。各位同学好&#xff0c;今天和大家分享一下TensorFlow2.0中的tensor数据类型&#xff0c;以及各种类型之间的相互转换方…

【OpenVINO 】在 MacOS 上编译 OpenVINO C++ 项目

前言 英特尔公司发行的模型部署工具OpenVINO™模型部署套件&#xff0c;可以实现在不同系统环境下运行&#xff0c;且发布的OpenVINO™ 2023最新版目前已经支持MacOS系统并同时支持在苹果M系列芯片上部署模型。在该项目中&#xff0c;我们将向大家展示如何在MacOS系统、M2芯片的…

③使用Redis缓存,并增强数据一致性。

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 使用Redis缓存&#xff0c;并增强数据一致性。…

1. 认识SPSS

使用的是IBM SPSS statistics 25&#xff0c;参考教材《统计分析与SPSS的应用》 一、安装和启动 具体安装过程是参考spss下载以及安装详细教程这篇文章&#xff0c;下载安装包然后按他的步骤获取用户许可证即可。 二、主要窗口 数据编辑器窗口data editor 是SPSS的主程序窗…

UV映射技巧和窍门

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 有一个鲜为人知的主题是纹理映射。这始终是 3D 建模师想要处理的最后…

.NET 6中如何使用Redis

1、安装redis Redis在windows平台上不受官方支持&#xff0c;所以想要在window安装Redis就必须去下载windows提供的安装包。安装地址&#xff1a;https://github.com/tporadowski/redis/releases 2、在NueGet安装包 3、在appsettings.json文件里面添加Redis相关配置信息 &quo…

第11章 GUI Page480~486 步骤二十七 “脏数据”与“新文档”状态维护

wxMyPainterFrame类定义中声明新的成员&#xff1a; 增加一个全局变量&#xff0c;初始化新成员&#xff1a; 先实现TrySaveFile() SaveFile()暂时为空实现 增加两个新的私有成员方法&#xff1a; wxMyPainterFrame类中&#xff0c;修改了“_items”的几个地方 ① 鼠标抬起时…

uniapp中使用tmt-calendar字体的颜色如何修改

tmt-calendar这个插件市场下载的组件默认的眼色为深蓝&#xff0c;如下图 然后能够动态修改这些颜色的参数也很限制&#xff0c;就想着从源代码上面去修改&#xff0c; 但是本人在项目的src/components这个目录下找了很久没有找到 又去node_modules目录下寻找也没有找到&#…

嵌入式——循环队列

循环队列 (Circular Queue) 是一种数据结构(或称环形队列、圆形队列)。它类似于普通队列,但是在循环队列中,当队列尾部到达数组的末尾时,它会从数组的开头重新开始。这种数据结构通常用于需要固定大小的队列,例如计算机内存中的缓冲区。循环队列可以通过数组或链表实现,…

【深度学习】优化器介绍

文章目录 前言一、梯度下降法&#xff08;Gradient Descent&#xff09;二、动量优化器&#xff08;Momentum&#xff09;三、自适应学习率优化器 前言 深度学习优化器的主要作用是通过调整模型的参数&#xff0c;使模型在训练数据上能够更好地拟合目标函数&#xff08;损失函…

Fiddler工具 — 9.命令行和状态栏

1、命令行 命令行在Fiddler的左下方的黑色窗口&#xff0c;也叫QuickExec&#xff0c;可以调用 Fiddler的内置命令。 这一系列内置的函数用于筛选和操作会话列表中的session&#xff08;会话&#xff09;。 虽然它不是很显眼&#xff0c;但用好它&#xff0c;会让你的工作效率…

python 函数中字典的修改会影响函数外字典的值

def modify_dict(d):d[key] new valueprint(函数中字典d的位置,id(d))# 创建一个字典 original_dict {key: old value} print(函数外字典的位置,id(original_dict))# 调用函数来修改字典 modify_dict(original_dict)# 输出原始字典的值&#xff0c;可以看到它已经被修改了 pr…

致远OA getAjaxDataServlet XXE漏洞复现(QVD-2023-30027)

0x01 产品简介 致远互联-OA 是数字化构建企业数字化协同运营中台,面向企业各种业务场景提供一站式大数据分析解决方案的协同办公软件。 0x02 漏洞概述 致远互联-OA getAjaxDataServlet 接口处存在XML实体注入漏洞,未经身份认证的攻击者可以利用此漏洞读取系统内部敏感文件…