Dandelion 2.0.0
A light-weight 3D builder for educational usage
载入中...
搜索中...
未找到
halfedge.h
浏览该文件的文档.
1#ifndef DANDELION_GEOMETRY_HALFEDGE_H
2#define DANDELION_GEOMETRY_HALFEDGE_H
3
4#include <cstddef>
5#include <memory>
6#include <optional>
7#include <variant>
8#include <tuple>
9#include <unordered_map>
10
11#include <Eigen/Core>
12#include <spdlog/spdlog.h>
13
15#include "../scene/object.h"
16
17/*!
18 * \file geometry/halfedge.h
19 * \ingroup geometry
20 * \~chinese
21 * \brief 半边网格所需各种类型的公共头文件。
22 *
23 * Dandelion 的几何处理算法都是基于半边网格的,所有几何处理需要的类型都在这个头文件中声明,
24 * 但在不同的源文件中定义。`Halfedge` / `Vertex` / `Edge` / `Face` 各自有一个源文件,
25 * 而 `HalfedgeMesh` 则被分为两部分:创建、同步和检查等功能在 *halfedge_mesh.cpp*
26 * 中实现,各类几何操作在 *meshedit.cpp* 中实现。
27 */
28
29struct Halfedge;
30struct Vertex;
31struct Edge;
32struct Face;
33
34/*!
35 * \ingroup geometry
36 * \~chinese
37 * \brief 半边网格中最关键的几何元素。
38 *
39 * 半边网格中,所有几何元素都通过半边相互连接。Halfedge 类维护了每条半边的起点、所属的边和面片、
40 * 在整个半边网格上的下一条(前一条、反向)半边,从而将所有的几何基本元素联系在一起。
41 */
42struct Halfedge : LinkedListNode<Halfedge>
43{
44 /*! \~chinese 仅供 `HalfedgeMesh::new_halfedge` 调用,其他任何情况下都不应该直接使用。 */
45 Halfedge(std::size_t halfedge_id);
46 /*! \~chinese 一次性设置半边的所有属性,各参数含义与同名属性一致。 */
47 void set_neighbors(
49 );
50 /*!
51 * \~chinese
52 * \brief 这条半边是否属于一个虚拟的边界面。
53 *
54 * 这个函数的返回值是 `face->on_boundary`,因此返回真时表示这条半边属于一个
55 * “虚假的”面(表示边界而不是真正的面片);而返回假时这条半边也有可能与 mesh
56 * 边界相邻,所以函数名为 `is_boundary` 而不是 `on_boundary` 。
57 */
58 bool is_boundary() const;
59 /*! \~chinese 半边的全局唯一 ID,不会与整个半边网格中任何其他元素重复。 */
60 const std::size_t id;
61 /*! \~chinese 下一条半边。 */
63 /*! \~chinese 上一条半边。 */
65 /*! \~chinese 反方向的半边。 */
67 /*! \~chinese 半边的起点(发出点)。 */
69 /*! \~chinese 半边所在的边。 */
71 /*! \~chinese 半边所在的面片。 */
73};
74
75/*!
76 * \ingroup geometry
77 * \~chinese
78 * \brief 半边网格中的顶点。
79 *
80 * 半边网格中,每个顶点只维护自身的坐标和某一条从自身发出的半边。
81 */
82struct Vertex : LinkedListNode<Vertex>
83{
84 /*! \~chinese 仅供 `HalfedgeMesh::new_vertex` 调用,其他任何情况下都不应该直接使用。 */
85 Vertex(std::size_t vertex_id);
86 /*! \~chinese 邻接的面片数量(不包括虚拟的边界面)。 */
87 size_t degree() const;
88 /*!
89 * \~chinese
90 * \brief \f$\mathcal{N}_1\f$ 邻域 (1-ring neighborhood) 中所有顶点坐标的算数平均值。
91 */
92 Eigen::Vector3f neighborhood_center() const;
93 /*! \~chinese 以面积为权重对邻接面片法向量求平均给出的顶点法向估计值。 */
94 Eigen::Vector3f normal() const;
95 /*! \~chinese 顶点的全局唯一 ID,不会与整个半边网格中任何其他元素重复。 */
96 const std::size_t id;
97 /*! \~chinese 从这一顶点发出的某条半边。 */
99 /*! \~chinese 顶点坐标。 */
100 Eigen::Vector3f pos;
101 /*! \~chinese 新建顶点的标识,在一些全局操作中用到。 */
102 bool is_new;
103 /*! \~chinese 在迭代式调整顶点坐标时保存迭代后的坐标(例如 Loop 细分时调整后的坐标)。 */
104 Eigen::Vector3f new_pos;
105};
106
107/*!
108 * \ingroup geometry
109 * \~chinese
110 * \brief 半边网格中的边。
111 *
112 * 半边网格中,每条边只维护属于自身的某一条半边。
113 */
114struct Edge : LinkedListNode<Edge>
115{
116 /*! \~chinese 仅供 `HalfedgeMesh::new_edge` 调用,其他任何情况下都不应该直接使用。 */
117 Edge(std::size_t edge_id);
118 /*! \~chinese 这条边是否在 mesh 边界上。 */
119 bool on_boundary() const;
120 /*! \~chinese 边的中点坐标。 */
121 Eigen::Vector3f center() const;
122 /*! \~chinese 边的长度。 */
123 float length() const;
124 /*! \~chinese 边的全局唯一 ID,不会与整个半边网格中任何其他元素重复。 */
125 const std::size_t id;
126 /*! \~chinese 沿着这条边的某一条半边。 */
128 /*! \~chinese 新建边的标识,在一些全局操作中用到。 */
129 bool is_new;
130 /*!
131 * \~chinese
132 * \brief 准备分裂这条边时分裂后新增顶点的坐标。
133 *
134 * `HalfedgeMesh::split_edge` 函数并不会使用这个坐标,而是直接用边中点作为新增顶点的位置。
135 * 这个属性仅供需要指定分裂后顶点位置的算法使用,例如 Loop 细分。
136 */
137 Eigen::Vector3f new_pos;
138};
139
140/*!
141 * \ingroup geometry
142 * \~chinese
143 * \brief 半边网格中的面片。
144 *
145 * 半边网格中,每个面片只维护属于自身的某一条半边。
146 */
147struct Face : LinkedListNode<Face>
148{
149 /*! \~chinese 仅供 `HalfedgeMesh::new_face` 调用,其他任何情况下都不应该直接使用。 */
150 Face(std::size_t face_id, bool is_boundary = false);
151 /*! \~chinese 返回面片法向量与面片面积的数量积。 */
152 Eigen::Vector3f area_weighted_normal();
153 /*! \~chinese 返回面片法向量(已经单位化)。 */
154 Eigen::Vector3f normal();
155 /*! \~chinese 返回面片中心(也是重心,顶点坐标的算数平均值)。 */
156 Eigen::Vector3f center() const;
157 /*! \~chinese 面片的全局唯一 ID,不会与整个半边网格中任何其他元素重复。 */
158 const std::size_t id;
159 /*! \~chinese 属于这个面片的某一条半边。 */
161 /*!
162 * \~chinese
163 * \brief 边界面标识。
164 *
165 * 如果不加处理地从一个不封闭 mesh 创建半边,位于 mesh 边界的边上将只会有一条半边,
166 * 从而让某些半边的反向指针 (`inv`) 为空。为了让半边网格在形式上统一,
167 * 创建半边网格时会将每条边界视作一个“环路”,并为这个“环路”创建一个虚拟的面。
168 */
169 const bool is_boundary;
170};
171
172/*!
173 * \ingroup geometry
174 * \~chinese
175 * \brief 半边网格的不合法情况。
176 *
177 * 在创建半边网格失败或验证其合法性失败时,用该枚举类代表失败原因。
178 * - NO_SELECTED_MESH 表示没有选中任何 mesh,半边网格没有数据源。
179 * - MULTIPLE_ORIENTED_EDGES 表示有多条重叠的半边(端点和方向都相同),该 mesh 不可定向。
180 * - NON_MANIFOLD_VERTEX 表示有非流形顶点。
181 * - INIFINITE_POSITION_VALUE 表示有的顶点坐标是无穷大。
182 * - INVALID_HALFEDGE_PERMUTATION 表示某条半边的指针空缺或指向了不正确的元素。
183 * - INVALID_VERTEX_CONNECTIVITY 表示某个顶点与半边的连接关系不正确
184 * - INVALID_EDGE_CONNECTIVITY 表示某条边与半边的连接关系不正确。
185 * = INVALID_FACE_CONNECTIVITY 表示某个面与半边的连接关系不正确。
186 * - ILL_FORMED_HALFEDGE_INVERSION 表示某条半边的反向关系不正确。
187 * - POOR_HALFEDGE_ACCESSIBILITY 表示某条半边不能通过其连接到的元素反向访问到。
188 *
189 * 更详细的说明见运行时输出的日志内容。
190 */
192{
193 NO_SELECTED_MESH,
194 MULTIPLE_ORIENTED_EDGES,
195 NON_MANIFOLD_VERTEX,
196 INIFINITE_POSITION_VALUE,
197 INVALID_HALFEDGE_PERMUTATION,
198 INVALID_VERTEX_CONNECTIVITY,
199 INVALID_EDGE_CONNECTIVITY,
200 INVALID_FACE_CONNECTIVITY,
201 ILL_FORMED_HALFEDGE_INVERSION,
202 POOR_HALFEDGE_ACCESSIBILITY
203};
204
205/*!
206 * \ingroup geometry
207 * \~chinese
208 * \brief 半边网格整体。
209 *
210 * 当 Dandelion 进入建模模式时,`Scene` 对象将用选中的 mesh 构造一个半边网格,
211 * 从而支持各种基于半边网格的几何算法。当几何操作完成后,需要将半边网格的几何信息
212 * (坐标、连接关系等)同步到原先的 mesh,这样才能显示操作带来的变化。
213 *
214 * 各种全局操作往往需要频繁增删几何元素,为了保证 \f$O(1)\f$ 的增删效率,
215 * 所有的几何元素都存储于双向链表 (`LinkedList`) 中。
216 */
218{
219public:
220
221 /*! \~chinese 指定一个 `Mesh` 作为参照,构造半边网格。 */
222 HalfedgeMesh(Object& object);
223 /*! \~chinese 全局只有一个半边网格实例,因此不允许复制构造。 */
224 HalfedgeMesh(HalfedgeMesh& other) = delete;
225 /*! \~chinese 析构时会调用 `clear_erasure_records` 释放所有已删除元素占据的内存。 */
227 /*! \~chinese 将当前半边网格的几何结构同步到数据源 mesh。 */
228 void sync();
229 /*! \~chinese 返回绘制半边时的起点和终点坐标。 */
230 static std::tuple<Eigen::Vector3f, Eigen::Vector3f> halfedge_arrow_endpoints(const Halfedge* h);
231 /*!
232 * \~chinese
233 * \brief 翻转一条边。
234 *
235 * 由于翻转边的过程中所有几何基本元素数量不变,可以在不增删元素的前提下实现这个函数。
236 * 在此情况下,返回的就是被传入的边。如果实现过程中删除了原有的边,
237 * 返回的指针可能与传入的不同。
238 *
239 * 但无论采用哪种实现方法,都不应该翻转 mesh 边界上的边。
240 * \returns 如果翻转成功,返回被翻转的边;反之返回 `std::nullopt` 。
241 */
242 std::optional<Edge*> flip_edge(Edge* e);
243 /*!
244 * \~chinese
245 * \brief 分裂一条边。
246 *
247 * 将传入的边分裂成两条边,分裂处新增一个顶点,并将此顶点与相对位置的两个顶点连接。
248 * 分裂操作要求指定边的两个邻接面都是三角形面。
249 * \returns 如果分裂成功,返回新增顶点;反之返回 `std::nullopt` 。
250 */
251 std::optional<Vertex*> split_edge(Edge* e);
252 /*!
253 * \~chinese
254 * \brief 坍缩一条边。
255 *
256 * 将传入的边坍缩成一个顶点,原先连接到两个端点上的其他边全部连接到这个顶点;
257 * 如果这条边的某个邻接面是三角形面,这个邻接面会被坍缩成一条边。
258 *
259 * 不加检查地坍缩一条边有可能破坏 mesh 的流形性质。假设边端点分别为 \f$v_1,v_2\f$,
260 * 此函数仅当
261 * \f[
262 * \mathcal{N}_1(v_1)\cap\mathcal{N}_1(v_2) = 2
263 * \f]
264 * 时才执行坍缩,以免破坏流行性质。
265 * \returns 如果坍缩成功,返回坍缩后的顶点;反之返回 `std::nullopt` 。
266 */
267 std::optional<Vertex*> collapse_edge(Edge* e);
268 /*!
269 * \~chinese
270 * \brief 执行一次 Loop 曲面细分。
271 *
272 * 该函数使用 `flip_edge` 和 `split_edge` 完成一次 Loop 曲面细分。注意,Loop
273 * 曲面细分只能细分三角网格。细分过程使用 `is_boundary` 和 `on_boundary`
274 * 等 API 来判断 mesh 边界并进行处理。
275 */
276 void loop_subdivide();
277 /*!
278 * \~chinese
279 * \brief 执行一次曲面简化。
280 *
281 * 该函数根据二次误差度量 (Quadric Error Metric, QEM) 确定损失最小的边,
282 * 再用 `collapse_edge` 坍缩它从而减少面数,直至面数减为简化前的 1/4
283 * 或找不到可以坍缩的边为止。
284 */
285 void simplify();
286 /*!
287 * \~chinese
288 * \brief 执行一次重网格化。
289 *
290 * 该函数采用各向同性重网格化策略调整 mesh 的边,尽可能让每个顶点的度都接近 6、
291 * 每个面片都接近正三角形。调整时它重复以下过程:
292 * 1. 分裂过长的边
293 * 2. 坍缩过短的边
294 * 3. 通过翻转边让顶点的度数更平均
295 * 4. 将顶点位置向它的 \f$\mathcal{N}_1\f$ 邻域平均值移动
296 *
297 * 各向同性重网格化只能应用于三角形网格。
298 */
299 void isotropic_remesh();
300 /*! \~chinese 所有半边。 */
302 /*! \~chinese 所有顶点。 */
304 /*! \~chinese 所有边。 */
306 /*! \~chinese 所有面片。 */
308 /*! \~chinese 将 `Mesh` 使用的顶点索引映射为半边网格中的顶点指针。 */
309 std::vector<Vertex*> v_pointers;
310 /*! \~chinese 半边网格被修改后置为 `true`,表示需要同步到参照 mesh。 */
312 /*! \~chinese 在创建半边网格时设置,如果创建正常则为 `std::nullopt`。 */
313 std::optional<HalfedgeMeshFailure> error_info;
314 /*! \~chinese 用于渲染半边的 `LineSet` 对象。 */
316
317private:
318
319 /*!
320 * \~chinese
321 * \brief 在曲面简化算法中用到的工具类。
322 *
323 * 基于 QEM 的简化算法需要实时获取损失最小的边,这个结构体用于记录一条边的坍缩代价、
324 * 坍缩后顶点的最佳位置,并放入优先队列中进行排序。
325 */
326 struct EdgeRecord
327 {
328 EdgeRecord() = default;
329 /*! \~chinese 根据两个端点的二次误差矩阵构造边的二次误差矩阵,并计算最佳坍缩位置。 */
330 EdgeRecord(std::unordered_map<Vertex*, Eigen::Matrix4f>& vertex_quadrics, Edge* e);
331 /*! \~chinese 这个记录对应的边。 */
333 /*! \~chinese 执行曲面简化算法时的最佳坍缩位置。 */
334 Eigen::Vector3f optimal_pos;
335 /*! \~chinese 执行曲面简化算法时坍缩这条边的代价(带来的误差)。 */
336 float cost;
337 };
338
339 /*! \~chinese 排序 `EdgeRecord` 所需的比较运算符重载。 */
340 friend bool operator<(const EdgeRecord& a, const EdgeRecord& b);
341 /*! \~chinese 创建一条半边。 */
343 /*! \~chinese 创建一个顶点。 */
345 /*! \~chinese 创建一条边。 */
346 Edge* new_edge();
347 /*! \~chinese 创建一个面片,可以是真实存在的面片也可以是代表边界的虚拟面片。 */
348 Face* new_face(bool is_boundary = false);
349 /*! \~chinese 重新生成所有半边对应的箭头,在更新绘制数据时使用。 */
351 /*! \~chinese 删除一条半边。 */
352 void erase(Halfedge* h);
353 /*! \~chinese 删除一个顶点。 */
354 void erase(Vertex* v);
355 /*! \~chinese 删除一条边。 */
356 void erase(Edge* e);
357 /*! \~chinese 删除一个面。 */
358 void erase(Face* f);
359 /*!
360 * \~chinese
361 * \brief 释放内存并清除已删除元素的记录。
362 *
363 * 这个函数执行 `delete` 释放内存,并清空各 `erased_[elements]` 容器中存储的删除记录。
364 */
366 /*!
367 * \~chinese
368 * \brief 检查半边网格的状态。
369 *
370 * 这个函数可以检查半边网格中的连接关系是否正确、指针是否悬垂,有助于及时发现错误。
371 * 错误信息会被输出到日志,并返回一个错误枚举值(参考 `HalfedgeMeshFailure` 类的说明)。
372 * \returns 如果发现错误,返回相应的错误枚举值;反之为 `std::nullopt`
373 */
374 std::optional<HalfedgeMeshFailure> validate();
375
376 /*! \~chinese 用于构造半边网格几何元素时分配新的唯一 ID。 */
377 static std::size_t next_available_id;
378 /*! \~chinese 数据源 mesh 对应的物体。 */
380 /*! \~chinese 数据源 mesh,用于构造半边网格,需要同步修改。 */
382 /*! \~chinese 已删除的半边。 */
383 std::unordered_map<size_t, Halfedge*> erased_halfedges;
384 /*! \~chinese 已删除的顶点。 */
385 std::unordered_map<size_t, Vertex*> erased_vertices;
386 /*! \~chinese 已删除的边。 */
387 std::unordered_map<size_t, Edge*> erased_edges;
388 /*! \~chinese 已删除的面。 */
389 std::unordered_map<size_t, Face*> erased_faces;
390
391 /*! \~chinese 将半边网格中的顶点指针映射为 `Mesh` 使用的顶点索引。 */
392 std::unordered_map<const Vertex*, size_t> v_indices;
393 /*! \~chinese 将半边网格的半边指针映射为 `LineSet` 中的箭头索引。 */
394 std::unordered_map<const Halfedge*, size_t> h_indices;
395 /*! \~chinese 日志记录器。 */
396 std::shared_ptr<spdlog::logger> logger;
397};
398
399#endif // DANDELION_GEOMETRY_HALFEDGE_H
std::optional< Vertex * > collapse_edge(Edge *e)
坍缩一条边。
定义 meshedit.cpp:274
Halfedge * new_halfedge()
定义 halfedge_mesh.cpp:331
HalfedgeMesh(HalfedgeMesh &other)=delete
void loop_subdivide()
执行一次 Loop 曲面细分。
定义 meshedit.cpp:412
Face * new_face(bool is_boundary=false)
定义 halfedge_mesh.cpp:352
friend bool operator<(const EdgeRecord &a, const EdgeRecord &b)
定义 meshedit.cpp:35
Object & object
定义 halfedge.h:379
std::shared_ptr< spdlog::logger > logger
定义 halfedge.h:396
std::optional< HalfedgeMeshFailure > error_info
定义 halfedge.h:313
void erase(Halfedge *h)
定义 halfedge_mesh.cpp:377
std::unordered_map< size_t, Face * > erased_faces
定义 halfedge.h:389
LinkedList< Halfedge > halfedges
定义 halfedge.h:301
void isotropic_remesh()
执行一次重网格化。
定义 meshedit.cpp:676
bool modified
定义 halfedge.h:311
Edge * new_edge()
定义 halfedge_mesh.cpp:345
Vertex * new_vertex()
定义 halfedge_mesh.cpp:338
LinkedList< Vertex > vertices
定义 halfedge.h:303
std::optional< HalfedgeMeshFailure > validate()
检查半边网格的状态。
定义 halfedge_mesh.cpp:417
static std::tuple< Eigen::Vector3f, Eigen::Vector3f > halfedge_arrow_endpoints(const Halfedge *h)
定义 halfedge_mesh.cpp:319
ArrowSet halfedge_arrows
定义 halfedge.h:315
std::vector< Vertex * > v_pointers
定义 halfedge.h:309
LinkedList< Face > faces
定义 halfedge.h:307
LinkedList< Edge > edges
定义 halfedge.h:305
void sync()
定义 halfedge_mesh.cpp:253
std::unordered_map< size_t, Halfedge * > erased_halfedges
定义 halfedge.h:383
std::unordered_map< size_t, Vertex * > erased_vertices
定义 halfedge.h:385
std::unordered_map< size_t, Edge * > erased_edges
定义 halfedge.h:387
Mesh & mesh
定义 halfedge.h:381
std::optional< Vertex * > split_edge(Edge *e)
分裂一条边。
定义 meshedit.cpp:104
void regenerate_halfedge_arrows()
定义 halfedge_mesh.cpp:359
std::unordered_map< const Vertex *, size_t > v_indices
定义 halfedge.h:392
std::unordered_map< const Halfedge *, size_t > h_indices
定义 halfedge.h:394
static std::size_t next_available_id
定义 halfedge.h:377
void clear_erasure_records()
释放内存并清除已删除元素的记录。
定义 halfedge_mesh.cpp:397
void simplify()
执行一次曲面简化。
定义 meshedit.cpp:535
std::optional< Edge * > flip_edge(Edge *e)
翻转一条边。
定义 meshedit.cpp:44
HalfedgeMesh(Object &object)
定义 halfedge_mesh.cpp:41
侵入式双链表。
定义 linked_list.hpp:44
表示物体的类。
定义 object.h:39
HalfedgeMeshFailure
半边网格的不合法情况。
定义 halfedge.h:192
包含物体的类。
用于渲染箭头的特殊线条集。
定义 mesh.hpp:125
半边网格中的边。
定义 halfedge.h:115
bool is_new
定义 halfedge.h:129
Eigen::Vector3f new_pos
准备分裂这条边时分裂后新增顶点的坐标。
定义 halfedge.h:137
float length() const
定义 edge.cpp:22
bool on_boundary() const
定义 edge.cpp:10
const std::size_t id
定义 halfedge.h:125
Halfedge * halfedge
定义 halfedge.h:127
Eigen::Vector3f center() const
定义 edge.cpp:15
Edge(std::size_t edge_id)
定义 edge.cpp:6
半边网格中的面片。
定义 halfedge.h:148
const std::size_t id
定义 halfedge.h:158
Halfedge * halfedge
定义 halfedge.h:160
Eigen::Vector3f center() const
定义 face.cpp:29
Face(std::size_t face_id, bool is_boundary=false)
定义 face.cpp:8
Eigen::Vector3f area_weighted_normal()
定义 face.cpp:13
const bool is_boundary
边界面标识。
定义 halfedge.h:169
Eigen::Vector3f normal()
定义 face.cpp:24
在曲面简化算法中用到的工具类。
定义 halfedge.h:327
EdgeRecord(std::unordered_map< Vertex *, Eigen::Matrix4f > &vertex_quadrics, Edge *e)
Edge * edge
定义 halfedge.h:332
Eigen::Vector3f optimal_pos
定义 halfedge.h:334
float cost
定义 halfedge.h:336
半边网格中最关键的几何元素。
定义 halfedge.h:43
Edge * edge
定义 halfedge.h:70
Halfedge * prev
定义 halfedge.h:64
Halfedge * next
定义 halfedge.h:62
Vertex * from
定义 halfedge.h:68
bool is_boundary() const
这条半边是否属于一个虚拟的边界面。
定义 halfedge.cpp:23
Halfedge * inv
定义 halfedge.h:66
Halfedge(std::size_t halfedge_id)
定义 halfedge.cpp:5
Face * face
定义 halfedge.h:72
void set_neighbors(Halfedge *next, Halfedge *prev, Halfedge *inv, Vertex *from, Edge *edge, Face *face)
定义 halfedge.cpp:11
const std::size_t id
定义 halfedge.h:60
用于同步渲染数据的三角形 Mesh 。
定义 mesh.hpp:28
半边网格中的顶点。
定义 halfedge.h:83
Eigen::Vector3f new_pos
定义 halfedge.h:104
Eigen::Vector3f neighborhood_center() const
邻域 (1-ring neighborhood) 中所有顶点坐标的算数平均值。
定义 vertex.cpp:24
bool is_new
定义 halfedge.h:102
size_t degree() const
定义 vertex.cpp:10
Eigen::Vector3f normal() const
定义 vertex.cpp:37
const std::size_t id
定义 halfedge.h:96
Halfedge * halfedge
定义 halfedge.h:98
Eigen::Vector3f pos
定义 halfedge.h:100
Vertex(std::size_t vertex_id)
定义 vertex.cpp:6