cgal教程 3D Alpha Wrapping

news2024/10/6 8:29:25

文章目录

  • 3D Alpha Wrapping (3D alpha 包裹)
  • 1 介绍
  • 2 方法
    • 2.1 算法
    • 2.2 保证
  • 3 接口
  • 4 选择参数
    • 4.1 alpha
    • 4.2 Offset
    • 4.3 关于“双面”包裹的注意事项
  • 5 性能
  • 6 例子

3D Alpha Wrapping (3D alpha 包裹)

原文地址: https://doc.cgal.org/latest/Alpha_wrap_3/index.html#Chapter_3D_Alpha_wrapping

该组件采用 3D 三角形网格、三角形汤或点集作为输入,并生成严格包含输入的有效三角表面网格(水密、无交集和 二维流形)。 该算法通过从输入的松散边界框开始收缩包裹并细化 3D Delaunay 三角剖分来进行。 两个用户定义的参数(alpha 和 offset)分别可以控制收缩包裹过程可以进入的空腔的最大尺寸,以及最终表面网格与输入的紧密度。 一旦组合起来,这些参数就提供了一种以输入的保真度换取输出的复杂性的方法。

在这里插入图片描述

1 介绍

几何建模和处理中的各种任务需要将3D对象表示为有效的表面网格,其中“有效”是指3D对象。指的是水密、无相交、可定向和 二维流形的网格。这种表示提供了内部/外部和测地线邻域的明确定义的概念。

3D 数据通常通过测量和重建来获取,由人类设计,或通过不完善的自动化过程生成。因此,它们可能会表现出各种各样的缺陷,包括间隙、缺失数据、自相交、简并性(例如零体积结构)和非流形特征。

鉴于可能存在的缺陷种类繁多,人们提出了许多方法和数据结构来修复特定缺陷,通常目的是保证修复的 3D 模型中的特定属性。可靠地修复所有类型的缺陷是众所周知的困难,并且通常是一个不适定问题,因为对于给定的带有缺陷的 3D 模型存在许多有效的解决方案。此外,输入模型可能过于复杂,具有不必要的几何细节、虚假拓扑结构、不重要的内部组件或过于精细的离散化。对于防撞、路径规划或模拟等应用,获取输入的近似值可能比修复输入更有意义。这里的近似是指能够滤除内部结构、精细细节和空腔以及将输入包裹在用户定义的偏移裕度内的方法。

给定输入 3D 几何形状,我们解决计算保守近似的问题,其中保守意味着保证输出严格包围输入。我们寻求无条件的鲁棒性,即输出网格应该有效(定向、二维流形且无自相交),即使对于具有许多缺陷和简并性的原始输入也是如此。默认输入是 3D 三角形汤,但通用接口为其他类型的有限 3D 图元(例如三角形汤和点集)敞开了大门。

在这里插入图片描述

在这里插入图片描述

2 方法

人们设计了许多方法将 3D 模型封装在一个体积内,这些方法具有运行时间和近似质量(即紧密度)之间的不同平衡。在最简单的情况下,轴对齐或定向的边界框显然满足一些所需的属性;然而,近似误差是不可控的并且通常非常大。计算输入的凸包也匹配一些所需的属性并提高结果的质量,尽管代价是增加运行时间。然而,近似值仍然很粗糙,特别是在有多个组件的情况下。

凸包实际上是 alpha 形状的特例 (Chapter_3D_Alpha_Shapes)。从数学上讲,α 形状是 Delaunay 三角剖分的子复形,单纯形是复形的一部分,具体取决于其最小(空)Delaunay 球的大小。直观上,构建 3D Alpha 形状可以被认为是用用户定义的半径 alpha 的空球来雕刻 3D 空间。 Alpha 形状产生可证明的、良好的形状分段线性近似[1],但是是在点集上定义的,而我们希望处理更一般的情况输入数据,例如三角汤。即使在对三角形汤进行采样之后,阿尔法形状也不能保证对于任何阿尔法都是保守的。最后,内部结构也被雕刻在体积内,而不是被过滤掉。

受 alpha 形状的启发,我们用收缩包裹替换上述雕刻概念收缩包裹:我们迭代地构建 3D Delaunay 三角剖分的子复形从包围输入的简单 3D Delaunay 三角剖分开始,然后迭代删除位于复合体边界上的合格四面体。此外,随着收缩的进行,底层的三角测量——以及随之而来的复杂——也被细化。因此,我们不是像 alpha 形状那样从输入数据的凸包进行雕刻,而是通过类似 Delaunay 细化的算法构建一个全新的网格。细化算法在偏移体积的边界上插入斯坦纳点,偏移体积定义为输入的无符号距离场的水平集。

此过程既可以防止在输出中创建内部结构,又可以避免多余的计算。此外,将网格结构与输入的几何和离散化分离有几个优点:(1)底层数据不限于特定格式(三角形汤、多边形汤、点云等),因为所有这些都需要正在回答三个基本几何查询:(a) 点与输入之间的距离,(b) 查询点在输入上的投影,© 四面体与输入之间的相交测试,以及 (2)用户可以更自由地以输入的紧密度换取最终的网格复杂性,因为在输入的大偏移量上构造保守近似需要更少的网格元素。

2.1 算法

初始化。该算法通过将松散边界框的八个角顶点插入 3D Delaunay 三角剖分来初始化。在 CGAL 的 3D Delaunay 三角剖分中,所有三角形面都与两个四面体单元相邻。 Delaunay 三角剖分边界的每个小面(与三角剖分顶点的凸包的一个小面重合)都与所谓的 无穷大 相邻四面体单元,一个连接到所谓的无限顶点的抽象单元,以确保上述的双面邻接。最初,所有无限单元都标记为外部,所有有限四面体单元都标记为内部。

收缩包装。收缩包裹算法通过从外到内遍历 Delaunay 三角剖分的单元,从一个单元到其相邻单元进行泛洪填充,并尽可能将相邻单元标记为外部(术语“可能”将在后面指定)。洪水填充是通过 Delaunay 三角形面的优先级队列实现的,该优先级队列表示面的两个相邻单元之间从外到内的遍历。这些三角形面在下文中称为

给定一个外部单元及其相邻的内部单元,如果满足以下条件,则公共面(即门)被称为 alpha 可遍历它的外接圆半径大于用户定义的参数 alpha,其中外接圆半径是指相关三角形的德劳内球的半径。直观上,小于 alpha 的空腔是不可访问的,因为它们的门不可 alpha 穿过。

优先级队列由凸包上的 alpha 可遍历门初始化,仅包含 alpha 可遍历门,并按门外接圆半径的降序排序。遍历可以被视为一个连续的过程,沿着门的双 Voronoi 边缘前进,并用一束空球包围着门。

在这里插入图片描述

图 62.3(左)铅笔画的空心圆(蓝色)外接 2D Delaunay 三角剖分(黑色)中的 Delaunay 边(绿色)。 从顶部三角形外心 c1 到底部三角形外心 c2,由 e(红色虚线)表示的对偶 Voronoi 边是没有 Delaunay 顶点的最大圆的中心迹。 (右)与左示例相对应的图表。 x 轴对应于位于 Voronoi 边 e(从 c1 到 c2)上的空圆中心的位置。 y 轴是对应空心圆的半径值。 在这种情况下,这支空心圆铅笔的最小半径位于绿色 Delaunay 边缘的中点。 在我们的算法中,当空圆的铅笔的最小半径小于 alpha 时,门(绿色 Delaunay 边)被认为是不可 alpha 遍历的。

当通过 alpha 可遍历的面 f 从外部单元 co 遍历到内部单元 ci 时,将测试两个标准以防止包装过程与输入发生冲突:

(1) 我们检查 f 的双 Voronoi 边(即两个入射单元的外心之间的线段)与偏移曲面(定义为输入的无符号等值面的水平集)之间的交点。 如果存在一个或多个交点,则沿着从外向内定向的双 Voronoi 边的第一个交点将作为 Steiner 点插入到三角剖分中。

(2) 如果对偶 Voronoi 边不与偏移曲面相交,但相邻单元 ci 与输入相交,我们计算 ci 的外心在偏移曲面上的投影,并将其作为 Steiner 点插入三角剖分中(这会破坏 ci)。

在上述每次 Steiner 点插入之后,所有新的事件单元都被标记为内部,并且新的 alpha 可遍历门被推入优先级队列。

如果以上两个标准都不满足,则遍历相邻小区 ci 并将其标记为外部。 将内部与外部单元分开的 ci 的 Alpha 可遍历方面被作为新门推入优先级队列。

一旦队列清空(由于插入新的斯坦纳点,面(及其外接半径)变得更小,这一过程就得到保证)构造阶段终止。 输出三角形表面网格是从 Delaunay 三角剖分中提取的,作为将内部单元与外部单元分开的面集。

下图以二维方式描述了该算法的步骤。

在这里插入图片描述

图 62.4 二维收缩包裹算法的步骤。该算法通过将输入(红色)的松散边界框的角插入 Delaunay 三角剖分中来初始化,并且所有有限三角形都标记在内部(灰色)。从队列中弹出的当前门(绿色边缘)是可 alpha 遍历的。当与门相邻的三角形不与输入相交时,它会被标记在外面,并且新的 alpha 可遍历门会被推送到队列中。当相邻三角形与输入相交时,将计算一个新的斯坦纳点(大绿色圆盘)并将其插入到三角剖分中,所有相邻三角形都在内部标记,新的 alpha 可遍历门被推入队列,并恢复遍历。灰色边缘描绘了 Delaunay 三角剖分。蓝色边缘描绘了 Voronoi 图。粉色圆圈描绘了半径为 alpha 的空圆。输出边(深蓝色)将内部三角形与外部三角形分开。

2.2 保证

该算法被证明可以终止并生成严格包围输入数据的 2 流形三角表面网格。 证明的关键要素是我们从外到内换行,并且绝不允许在内部标记与输入相交的单元格。 此外,导致三角测量细化的两个标准插入斯坦纳点,保证破坏需要细化的单元并减少相邻面的圆周半径。

由于主要的细化标准是在双 Voronoi 边与输入偏移之间插入交集,或者将 Voronoi 顶点投影到输入偏移上,因此该算法与基于 Delaunay 滤波和 细化(参见 Chapter_3D_Mesh_Generation)。

3 接口

我们的算法将一组 3D 三角形作为输入,以三角形汤或三角形表面网格的形式提供,以及两个用户定义的标量参数:alpha 和偏移值。 它通过从输入的松散边界框开始收缩包装和细化 3D Delaunay 三角剖分来进行。 参数 alpha 指的是在缠绕过程中无法穿过的空腔或孔的大小,因此指的是最终的细节级别,因为 alpha 的作用类似于常见 Delaunay 细化算法 (Chapter_3D_Mesh_Generation) 中的尺寸字段。 参数偏移量是指细化三角剖分的顶点与输入之间的距离,因此较大的偏移量会转化为输入的松散包围。 第二个参数提供了一种控制紧密性和复杂性之间权衡的方法。

该组件的主要入口点是生成 alpha 换行的全局函数 CGAL::alpha_wrap_3(); 该函数将多边形汤或多边形网格作为输入。 输入连通性没有先决条件,因此它可以采用任意三角形汤,具有岛屿、自相交或重叠,以及组合或几何简并性。

底层特征类必须是内核概念的模型。 它应该使用浮点数类型,因为不精确性是该算法固有的,因为偏移表面上的新顶点没有闭合形式描述。

输出是一个三角形表面网格,其类型由用户选择,但必须是 MutableFaceGraph 概念的模型。

4 选择参数

算法的两个参数会影响输出网格的详细程度和复杂性。

4.1 alpha

主要参数 alpha 控制 Delaunay 面在收缩包裹过程中是否可遍历。 Alpha 的主要目的是控制包裹过程中使用的空球的大小,从而确定哪些特征将出现在输出中:事实上,如果一个面的外接圆半径大于 alpha,则它是可 alpha 遍历的; 因此,该算法只能通过直径大于 alpha 的海峡或孔洞进行收缩包裹。 第二个不太直接的结果是,只要面的外接半径大于 alpha,单元内的事件就会被访问并可能被细化。 因此,当算法终止时,所有面的外接半径均小于 alpha。 因此,该参数的行为也类似于输出的三角形面上的大小标准。

在这里插入图片描述

图 62.5 alpha 参数对输出的影响。 (左)通过原始点云表面重建生成的输入三角形网格具有许多非流形边和顶点、多余的几何细节和虚假拓扑结构。 (右)该组件保守地近似输入,并根据 alpha 参数生成具有不同复杂度和输入保真度的有效网格。 α 值越小,收缩包装过程进入型腔的深度就越深。 alpha 参数从左到右递减,分别为输入边界框最长对角线的 1/50、1/100 和 1/300。 大的 alpha 将产生不太复杂的输出,但不太忠实于输入。

4.2 Offset

第二个参数是偏移距离,它控制与输入的距离,从而控制输出网格顶点所在的偏移等值面的定义。 该参数控制结果的紧密度,这反过来又会产生一些后果。 首先,将顶点定位在远离输入的位置使算法能够生成不太复杂的网格,尤其是在凸区域。 这种行为的一个简单例子是一个非常密集的球体网格,对于该球体来说,尽可能紧密的包络也将非常密集。 其次,等值面距离输入越远,通过第一个标准插入的新点就越多(即通过与双 Voronoi 边相交,请参见截面算法); 因此,输出质量在三角形元素的角度方面得到改善。 最后,根据 alpha 参数的值,大的偏移量也可以提供破坏功能。 然而,使用较小的偏移参数往往会更好地保留锐利特征,因为投影施泰纳点往往会投影到凸形锐利特征上。

在这里插入图片描述

图 62.6 偏移参数对输出的影响。 (左)通过在参数空间中对 NURBS CAD 模型进行网格划分而生成的输入网格。 (右)偏移量越小,样本点距离输入最近。 偏移参数从左到右递减,分别为输入边界框最长对角线的 1/50、1/200 和 1/1000。 对于所有细节级别,alpha 参数等于输入边界框最长对角线的 1/50。 较大的偏移量将产生不太复杂且三角形质量更好的输出。 然而,当偏移参数较小时,清晰的特征(红色边缘)会得到很好的保留。

在这里插入图片描述

图 62.7 斯坦纳点。 投影施泰纳点(绿色)是通过将三角形外心投影到偏移量上来计算的。 交点 Steiner 点(蓝色)被计算为 Voronoi 边缘和偏移之间的第一个交点。 (左)当偏移参数较小时,算法会产生更多的投影斯坦纳点,这往往会改善凸锐特征的保留。 (右)当偏移参数较大时,算法会产生更多的斯坦纳交点,这往往会在 3D 中生成角度质量更好的三角形。

默认情况下,我们建议将offset参数设置为alpha的一小部分,这样alpha就成为控制最终细节层次的主要参数。

下图说明了这两个参数的影响。

在这里插入图片描述

图 62.8 自行车模型上的不同 alpha 和偏移值(533,000 个三角形)。 x轴表示等于输入边界框最长对角线的1/5000、1/2000、1/500、1/200、1/50、1/20和1/5的偏移值,从左到右 正确的。 y 轴表示从下到上等于输入边界框最长对角线的 1/300、1/100、1/50、1/20 和 1/5 的 alpha 值。 每个细节级别下方的数字代表其三角形的数量。 根据 alpha 值,偏移量太小或太大将产生具有更高复杂性的输出网格。 对于每个 alpha,复杂度较低的模型可以用作从近距离到远距离的碰撞检测的尺度空间表示。

4.3 关于“双面”包裹的注意事项

偏移参数对于我们的方法至关重要,因为它保证输出是闭合的 2 流形表面网格。 事实上,即使输入是零体积结构(例如单个 3D 三角形),输出包裹也是包围所述三角形的薄体积(图 62.2)。

用户应该记住,环绕算法无法确定它是作用于无符号距离场的内部还是外部,因此在输入和 alpha 值有空洞的情况下会产生两侧环绕 小于孔的尺寸。

在这里插入图片描述

图 62.9 两侧包裹。 (左)以 2D 形式包裹兔子,并减小 alpha 值。 (右)以 3D 方式包裹充满缺陷的兔子。 最右边的一列描绘了内部的剪辑可视化。 当 alpha 相对于孔的直径足够小时,算法会生成两侧包裹。

5 性能

下图绘制了 Thingi10k 数据集上包裹算法的计算时间,以及输出三角形网格的复杂度。

在这里插入图片描述

图 62.9 Thingi10k 数据集上不同 alpha 值的执行时间和输出复杂度。 Alpha 从边界框对角线长度的 1/20 增加到 1/200。 x 轴表示输出包裹网格的复杂性(以三角形面的数量表示)。 y 轴表示总计算时间(以秒为单位)。 点的颜色和直径代表输入三角形汤中的面数,范围从 10(绿色)到 3154000(蓝色)。

6 例子

下面是一个输入三角形网格的示例,其中 alpha 设置为边界框最长对角边长度的 1/20,偏移量设置为 alpha 的 1/30(即边界框对角边长度的 1/600)。

文件 Alpha_wrap_3/triangle_mesh_wrap.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/alpha_wrap_3.h>
#include <CGAL/Polygon_mesh_processing/bbox.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
#include <CGAL/Real_timer.h>
#include <iostream>
#include <string>
namespace PMP = CGAL::Polygon_mesh_processing;
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Point_3 = K::Point_3;
using Mesh = CGAL::Surface_mesh<Point_3>;
int main(int argc, char** argv)
{
  // Read the input
  const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/armadillo.off");
  std::cout << "Reading " << filename << "..." << std::endl;
  Mesh mesh;
  if(!PMP::IO::read_polygon_mesh(filename, mesh) || is_empty(mesh) || !is_triangle_mesh(mesh))
  {
    std::cerr << "Invalid input." << std::endl;
    return EXIT_FAILURE;
  }
  std::cout << "Input: " << num_vertices(mesh) << " vertices, " << num_faces(mesh) << " faces" << std::endl;
  // Compute the alpha and offset values
  const double relative_alpha = (argc > 2) ? std::stod(argv[2]) : 20.;
  const double relative_offset = (argc > 3) ? std::stod(argv[3]) : 600.;
  CGAL::Bbox_3 bbox = CGAL::Polygon_mesh_processing::bbox(mesh);
  const double diag_length = std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) +
                                       CGAL::square(bbox.ymax() - bbox.ymin()) +
                                       CGAL::square(bbox.zmax() - bbox.zmin()));
  const double alpha = diag_length / relative_alpha;
  const double offset = diag_length / relative_offset;
  std::cout << "alpha: " << alpha << ", offset: " << offset << std::endl;
  // Construct the wrap
  CGAL::Real_timer t;
  t.start();
  Mesh wrap;
  CGAL::alpha_wrap_3(mesh, alpha, offset, wrap);
  t.stop();
  std::cout << "Result: " << num_vertices(wrap) << " vertices, " << num_faces(wrap) << " faces" << std::endl;
  std::cout << "Took " << t.time() << " s." << std::endl;
  // Save the result
  std::string input_name = std::string(filename);
  input_name = input_name.substr(input_name.find_last_of("/") + 1, input_name.length() - 1);
  input_name = input_name.substr(0, input_name.find_last_of("."));
  std::string output_name = input_name
                            + "_" + std::to_string(static_cast<int>(relative_alpha))
                            + "_" + std::to_string(static_cast<int>(relative_offset)) + ".off";
  std::cout << "Writing to " << output_name << std::endl;
  CGAL::IO::write_polygon_mesh(output_name, wrap, CGAL::parameters::stream_precision(17));
  return EXIT_SUCCESS;
}

由于非流形或方向不兼容,某些三角形汤可能无法表示为网格。 尽管如此,这样的三角形汤仍然是包装算法的有效输入,如下例所示。

文件 Alpha_wrap_3/triangle_soup_wrap.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/alpha_wrap_3.h>
#include <CGAL/Polygon_mesh_processing/bbox.h>
#include <CGAL/IO/polygon_soup_io.h>
#include <CGAL/Real_timer.h>
#include <array>
#include <iostream>
#include <string>
#include <vector>
namespace AW3 = CGAL::Alpha_wraps_3;
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Point_3 = K::Point_3;
using Mesh = CGAL::Surface_mesh<Point_3>;
int main(int argc, char** argv)
{
  std::cout.precision(17);
  // Read the input
  const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/blobby-shuffled.off");
  std::cout << "Reading " << filename << "..." << std::endl;
  std::vector<Point_3> points;
  std::vector<std::array<std::size_t, 3> > faces;
  if(!CGAL::IO::read_polygon_soup(filename, points, faces) || faces.empty())
  {
    std::cerr << "Invalid input." << std::endl;
    return EXIT_FAILURE;
  }
  std::cout << "Input: " << points.size() << " points, " << faces.size() << " faces" << std::endl;
  // Compute the alpha and offset values
  const double relative_alpha = (argc > 2) ? std::stod(argv[2]) : 20.;
  const double relative_offset = (argc > 3) ? std::stod(argv[3]) : 600.;
  CGAL::Bbox_3 bbox;
  for(const Point_3& p : points)
    bbox += p.bbox();
  const double diag_length = std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) +
                                       CGAL::square(bbox.ymax() - bbox.ymin()) +
                                       CGAL::square(bbox.zmax() - bbox.zmin()));
  const double alpha = diag_length / relative_alpha;
  const double offset = diag_length / relative_offset;
  // Construct the wrap
  CGAL::Real_timer t;
  t.start();
  Mesh wrap;
  CGAL::alpha_wrap_3(points, faces, alpha, offset, wrap);
  t.stop();
  std::cout << "Result: " << num_vertices(wrap) << " vertices, " << num_faces(wrap) << " faces" << std::endl;
  std::cout << "Took " << t.time() << " s." << std::endl;
  // Save the result
  std::string input_name = std::string(filename);
  input_name = input_name.substr(input_name.find_last_of("/") + 1, input_name.length() - 1);
  input_name = input_name.substr(0, input_name.find_last_of("."));
  std::string output_name = input_name
                            + "_" + std::to_string(static_cast<int>(relative_alpha))
                            + "_" + std::to_string(static_cast<int>(relative_offset)) + ".off";
  std::cout << "Writing to " << output_name << std::endl;
  CGAL::IO::write_polygon_mesh(output_name, wrap, CGAL::parameters::stream_precision(17));
  return EXIT_SUCCESS;
}

这是一个点云的示例。

文件 Alpha_wrap_3/point_set_wrap.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/alpha_wrap_3.h>
#include <CGAL/IO/read_points.h>
#include <CGAL/Real_timer.h>
#include <iostream>
#include <string>
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Point_3 = K::Point_3;
using Point_container = std::vector<Point_3>;
using Mesh = CGAL::Surface_mesh<Point_3>;
int main(int argc, char** argv)
{
  // Read the input
  const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("points_3/oni.pwn");
  std::cout << "Reading " << filename << "..." << std::endl;
  Point_container points;
  if(!CGAL::IO::read_points(filename, std::back_inserter(points)) || points.empty())
  {
    std::cerr << "Invalid input." << std::endl;
    return EXIT_FAILURE;
  }
  std::cout << points.size() << " points" << std::endl;
  // Compute the alpha and offset values
  const double relative_alpha = (argc > 2) ? std::stod(argv[2]) : 10.;
  const double relative_offset = (argc > 3) ? std::stod(argv[3]) : 300.;
  CGAL::Bbox_3 bbox = CGAL::bbox_3(std::cbegin(points), std::cend(points));
  const double diag_length = std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) +
                                       CGAL::square(bbox.ymax() - bbox.ymin()) +
                                       CGAL::square(bbox.zmax() - bbox.zmin()));
  const double alpha = diag_length / relative_alpha;
  const double offset = diag_length / relative_offset;
  std::cout << "absolute alpha = " << alpha << " absolute offset = " << offset << std::endl;
  // Construct the wrap
  CGAL::Real_timer t;
  t.start();
  Mesh wrap;
  CGAL::alpha_wrap_3(points, alpha, offset, wrap);
  t.stop();
  std::cout << "Result: " << num_vertices(wrap) << " vertices, " << num_faces(wrap) << " faces" << std::endl;
  std::cout << "Took " << t.time() << " s." << std::endl;
  // Save the result
  std::string input_name = std::string(filename);
  input_name = input_name.substr(input_name.find_last_of("/") + 1, input_name.length() - 1);
  input_name = input_name.substr(0, input_name.find_last_of("."));
  std::string output_name = input_name + "_" + std::to_string(static_cast<int>(relative_alpha))
                            + "_" + std::to_string(static_cast<int>(relative_offset)) + ".off";
  std::cout << "Writing to " << output_name << std::endl;
  CGAL::IO::write_polygon_mesh(output_name, wrap, CGAL::parameters::stream_precision(17));
  return EXIT_SUCCESS;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux unzip解压多个文件

前情介绍 最近下载了imagenet1k数据集&#xff0c;令人难受的是这里边有很多的zip包&#xff0c;我总不能一个一个解压吧&#xff0c;这就太费时了&#xff0c;有点程序员思维很重要&#xff1a;批量解压。 解决办法 假设当前目录下有多个zip文件 需要 unzip *.zip …

【FunASR】Paraformer语音识别-中文-通用-16k-离线-large-onnx

模型亮点 模型文件: damo/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorchParaformer-large长音频模型集成VAD、ASR、标点与时间戳功能&#xff0c;可直接对时长为数小时音频进行识别&#xff0c;并输出带标点文字与时间戳&#xff1a; ASR模型…

apt-mark工具介绍(标记或取消标记软件包,防止特定软件包被自动更新或删除)

文章目录 apt-mark工具深度解析1. apt-mark概述1.1 apt-mark定义1.2 apt-mark作用 2. apt-mark常用命令2.1 标记软件包为手动安装2.2 标记软件包为自动安装2.3 阻止软件包更新2.4 允许软件包更新 3. 疑难技术点解析3.1 如何查看软件包的标记状态3.2 如何解决软件包依赖性问题 4…

IDEA新建jdk8 spring boot项目

今天新建spring boot项目发现JDK版本最低可选17。 但是目前用的最多的还是JDK8啊。 解决办法 Server URL中设置&#xff1a; https://start.aliyun.com/设置完成后&#xff0c;又可以愉快的用jdk8创建项目了。 参考 https://blog.csdn.net/imbzz/article/details/13469117…

【C++】POCO学习总结(十七):日志系统(级别、通道、格式化、记录流)

【C】郭老二博文之&#xff1a;C目录 1、Poco::Message 日志消息 1.1 说明 所有日志消息都在Poco::Message对象中存储和传输。 头文件&#xff1a;#include “Poco/Message.h” 一条消息包含如下内容&#xff1a;优先级、来源、一个文本、一个时间戳、进程和线程标识符、可选…

每天五分钟计算机视觉:Inception网络是由多个Inception模块构成

本文重点 inception从另一种角度来提升训练结果:能更高效的利用计算资源,在相同的计算量下能提取到更多的特征,从而提升训练结果。可以简单的理解为Inception 网络是由一个一个的Inception模块构建成的,我们来看一下。 Inception模块 如上就是Inception模块的 通过一个1…

【Android逆向】记录一次某某虚拟机的逆向

导语 学了一段时间的XPosed&#xff0c;发现XPosed真的好强&#xff0c;只要技术强&#xff0c;什么操作都能实现... 这次主要记录一下我对这款应用的逆向思路 apk检查 使用MT管理器检查apk的加壳情况 发现是某数字的免费版本 直接使用frida-dexdump 脱下来后备用 应用分…

【MYSQL】事务隔离级别、脏读、不可重复读、幻读

文章目录 介绍演示脏读不可重复读可重复读幻读 不可重复读和幻读的区别 参考 作者 Guide: 事务隔离级别 美团技术团队&#xff1a; Innodb中的事务隔离级别和锁的关系 介绍 SQL 标准定义了四个隔离级别&#xff1a; READ-UNCOMMITTED(读取未提交) &#xff1a;最低的隔离级别…

虚拟机启动 I/O error in “xfs_read_agi+0x95“

1.在选择系统界面按e 进入维护模式 2.找到ro把ro改成 rw init/sysroot/bin/sh 然后按Ctrlx 3.找到坏掉的分区&#xff0c;以nvme0n1p3为例进行修复 xfs_repair -d /dev/nvme0n1p3 4.init 6 重新启动 以下情况 先umount 再修复 则修复成功

SaaS行业分析

文章目录 什么是SaaS ?SaaS的标准定义什么是软件即服务&#xff1f;SaaS与传统软件的区别 &#xff1f; SaaS行业分析你知道最赚钱的行业是什么&#xff1f;互联网带给企业的变化 SaaS与PaaS、IaaS的区别&#xff1f;IaaS&#xff08;Infrastructure as a Service&#xff09;…

消除非受检警告

在Java中&#xff0c;有一些情况下编译器会生成非受检警告&#xff08;Unchecked Warnings&#xff09;。这些警告通常与泛型、类型转换或原始类型相关。消除这些警告可以提高代码的可读性和安全性。以下是一些常见的非受检警告以及如何消除它们的例子&#xff1a; 1. 泛型类型…

js传递json数据过大的解决方案

protobufjs 使用protobuf&#xff0c;定义如下结构 Person.protobuf syntax "proto3";message Person {string name 1;int32 age 2; }Person.thrift namespace java com.example.Personstruct Person {1: required string name,2: required i32 age }使用bench…

高压电气是什么

高压电气 电工电气百科 文章目录 高压电气前言一、高压电气是什么二、高压电气的类别三、高压电气的作用原理总结前言 高压电气在电力系统中起着重要的作用,它能够将电能有效地输送和分配到各个用户,为社会和工业生产提供稳定可靠的电力供应。然而,高压电气系统也需要注意安…

Jwt令牌过滤器的下发和拦截(创建在前面)

创建Jwt令牌的方法在前面&#xff1a; JWT令牌的作用和生成https://blog.csdn.net/m0_71149935/article/details/135002840?spm1001.2014.3001.5501令牌的下发&#xff1a; 说明&#xff1a; 只用在浏览器访问服务器的时候校验账户信息是否正确&#xff0c;正确就创建Jwt令…

网站服务器/域名/备案到底有什么关联?

​  在一个网站的组成中&#xff0c;网站服务器、域名、备案这几个要素是要被常提到的。在谈及三者关联之前&#xff0c;我们先了解下三者的各自概念。 域名&#xff1a;它是网站的唯一标识符&#xff0c;通俗理解来说就是用户在浏览器地址栏中输入的网址。一般来说&#xff…

Java开发工具积累(符合阿里巴巴手册规范)

文章目录 一、命名规约二、集合篇1. 栈、队列、双端队列2. List的升序倒序3. Map的升序降序4. 二维数组排序5. 集合之间的转换6. Map键值对遍历 三、并发篇1. 创建线程池2. ThreadLocal的使用 四、时间篇1. LocalDateTime的使用2. String、Date、LocalDateTime转换 五、控制块1…

[每周一更]-(第27期):HTTP压测工具之wrk

[补充完善往期内容] wrk是一款简单的HTTP压测工具,托管在Github上,https://github.com/wg/wrkwrk 的一个很好的特性就是能用很少的线程压出很大的并发量. 原因是它使用了一些操作系统特定的高性能 io 机制, 比如 select, epoll, kqueue 等. 其实它是复用了 redis 的 ae 异步事…

最新50万字312道Java经典面试题52道场景题总结(附答案PDF)

最近有很多粉丝问我&#xff0c;有什么方法能够快速提升自己&#xff0c;通过阿里、腾讯、字节跳动、京东等互联网大厂的面试&#xff0c;我觉得短时间提升自己最快的手段就是背面试题&#xff1b;花了3个月的时间将市面上所有的面试题整理总结成了一份50万字的300道Java高频面…

C#上位机与欧姆龙PLC的通信02----搭建仿真环境

在没有硬件的情况下&#xff0c;创建仿真环境非常必要&#xff0c;买硬件需要花大几K的大洋&#xff0c;这是不好的&#xff0c;对于学习培训者来说&#xff0c;有仿真环境就特别漂亮&#xff0c;现在CX-ONE已经集成了欧姆龙的编程软件&#xff0c;能够实现仿真PLC及编程&#…

java基础-1

byte&#xff1a;8位有符号二进制补码整数&#xff0c;占用1字节。 short&#xff1a;16位有符号二进制补码整数&#xff0c;占用2字节。 int&#xff1a;32位有符号二进制补码整数&#xff0c;占用4字节。 long&#xff1a;64位有符号二进制补码整数&#xff0c;占用8字节。…