CARLA
 
载入中...
搜索中...
未找到
Simplify.h
浏览该文件的文档.
1/////////////////////////////////////////////
2//
3// Mesh Simplification Tutorial
4//
5// (C) by Sven Forstmann in 2014
6//
7// License : MIT
8// http://opensource.org/licenses/MIT
9//
10// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
11//
12// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile
13/**
14 * @brief 标准输入输出流库
15 *
16 * 提供输入输出功能,用于在控制台打印信息或读取用户输入。
17 */
18#include <iostream>
19// 包含标准输入输出流库,用于输入输出操作
20 /**
21 * @brief 文件流库
22 *
23 * 提供文件读写功能,支持文本文件和二进制文件的操作。
24 */
25#include <fstream>
26// 包含文件流库,用于文件的读写
27 /**
28 * @brief 算法库
29 *
30 * 提供各种常用算法的实现,如排序、查找等。
31 */
32#include <algorithm>
33// 包含算法库,提供各种常用算法的实现
34 /**
35 * @brief 字符串操作函数库(C风格)
36 *
37 * 提供一系列用于处理C风格字符串的函数,如复制、比较等。
38 */
39#include <string.h>
40// 包含字符串操作函数库(C 风格)
41 /**
42 * @brief 标准输入输出库(C风格)
43 *
44 * 提供基本的输入输出功能,如打印、读取等,通常用于控制台应用程序。
45 */
46#include <stdio.h>
47// 包含标准输入输出库(C 风格)
48 /**
49 * @brief 标准库头文件(C风格)
50 *
51 * 提供内存分配、程序控制、环境访问等功能。
52 */
53#include <stdlib.h>
54// 包含标准库头文件(C 风格),提供内存分配等功能
55 /**
56 * @brief 关联数组(映射)库
57 *
58 * 提供了一种存储键值对的数据结构,可以快速地根据键查找对应的值。
59 */
60#include <map>
61// 包含关联数组(映射)库
62 /**
63 * @brief 动态数组库
64 *
65 * 提供了一种可以动态调整大小的数组数据结构,支持随机访问和高效的元素添加/删除。
66 */
67#include <vector>
68// 包含动态数组库
69 /**
70 * @brief 字符串库(C++风格)
71 *
72 * 提供了一种表示和操作字符串的类,比C风格的字符串提供了更多的功能和安全性。
73 */
74#include <string>
75// 包含字符串库(C++ 风格)
76 /**
77 * @brief 数学库
78 *
79 * 提供各种数学函数,如三角函数、对数函数、幂函数等。
80 */
81#include <math.h>
82// 包含数学库
83 /**
84 * @brief 浮点型数值限制库
85 *
86 * 定义了浮点型数值的一些极限值,如最小正数(FLT_EPSILON, DBL_EPSILON)等。
87 */
88#include <float.h>
89// 包含浮点型数值限制库
90 /**
91 * @def loopi
92 * @brief 定义一个循环宏,使用变量i从start_l迭代到end_l(不包括end_l)。
93 *
94 * @param start_l 循环起始值(包含)。
95 * @param end_l 循环结束值(不包含)。
96 // 定义一个循环宏,使用变量 i 从 start_l 迭代到 end_l(不包括 end_l)
97 */
98
99#define loopi(start_l, end_l) for (int i = start_l; i < end_l; ++i)
100 /**
101 * @def loopj
102 * @brief 定义一个循环宏,使用变量j从start_l迭代到end_l(不包括end_l)。
103 *
104 * @param start_l 循环起始值(包含)。
105 * @param end_l 循环结束值(不包含)。
106 */
107#define loopi(start_l, end_l) for (int i = start_l; i < end_l; ++i)
108#define loopj(start_l, end_l) for (int j = start_l; j < end_l; ++j)
109 /**
110 * @def loopk
111 * @brief 定义一个循环宏,使用变量k从start_l迭代到end_l(不包括end_l)。
112 *
113 * @param start_l 循环起始值(包含)。
114 * @param end_l 循环结束值(不包含)。
115 */
116#define loopk(start_l, end_l) for (int k = start_l; k < end_l; ++k)
117 /**
118 * @struct vector3
119 * @brief 一个简单的三维向量结构体,包含x、y、z三个坐标。
120 */
121// 定义一个简单的三维向量结构体
123{
124 double x, y, z;
125};
126/**
127 * @struct vec3f
128 * @brief 一个包含三维浮点数坐标的向量结构体,提供了一系列向量运算。
129 */
130
131// 定义一个包含更多功能的三维浮点数坐标的向量结构体
132struct vec3f
133{
134// 默认构造函数
135 double x, y, z;
136 /**
137 * @brief 默认构造函数。
138 */
139 inline vec3f(void) {}
140
141 /**
142 * @brief 拷贝构造函数,从另一个vector3对象构造。
143 *
144 * @param a 一个vector3对象。
145 */
146 inline vec3f(vector3 a)
147 {
148 x = a.x;
149 y = a.y;
150 z = a.z;
151 }
152 /**
153 * @brief 参数化构造函数,从三个浮点数构造。
154 *
155 * @param X x坐标。
156 * @param Y y坐标。
157 * @param Z z坐标。
158 */
159 inline vec3f(const double X, const double Y, const double Z)
160 {
161 x = X;
162 y = Y;
163 z = Z;
164 }
165
166 inline vec3f operator+(const vec3f &a) const
167 {
168 return vec3f(x + a.x, y + a.y, z + a.z);
169 }
170
171 inline vec3f operator+=(const vec3f &a) const
172 {
173 return vec3f(x + a.x, y + a.y, z + a.z);
174 }
175
176 inline vec3f operator*(const double a) const
177 {
178 return vec3f(x * a, y * a, z * a);
179 }
180
181 inline vec3f operator*(const vec3f a) const
182 {
183 return vec3f(x * a.x, y * a.y, z * a.z);
184 }
185
186 inline vec3f v3() const
187 {
188 return vec3f(x, y, z);
189 }
190 /**
191 * @brief 赋值运算符重载,从另一个vec3f对象赋值。
192 *
193 * @param a 另一个vec3f对象。
194 * @return 返回当前对象的引用。
195 */
196 inline vec3f operator=(const vector3 a)
197 {
198 x = a.x;
199 y = a.y;
200 z = a.z;
201 return *this;
202 }
203 /**
204 * @brief 赋值运算符重载,用于将另一个vec3f对象的值赋给当前对象。
205 *
206 * @param a 另一个vec3f对象,其值将被赋给当前对象。
207 * @return 返回当前对象的引用。
208 */
209 inline vec3f operator=(const vec3f a)
210 {
211 x = a.x;
212 y = a.y;
213 z = a.z;
214 return *this;
215 }
216 /**
217 * @brief 向量除法运算符重载,计算当前对象与另一个vec3f对象对应分量相除的结果。
218 *
219 * @param a 另一个vec3f对象,其对应分量将用作除数。
220 * @return 返回一个新vec3f对象,包含除法运算的结果。
221 */
222 inline vec3f operator/(const vec3f a) const
223 {
224 return vec3f(x / a.x, y / a.y, z / a.z);
225 }
226 /**
227 * @brief 向量减法运算符重载,计算当前对象与另一个vec3f对象对应分量相减的结果。
228 *
229 * @param a 另一个vec3f对象,其对应分量将从当前对象的对应分量中减去。
230 * @return 返回一个新vec3f对象,包含减法运算的结果。
231 */
232 inline vec3f operator-(const vec3f &a) const
233 {
234 return vec3f(x - a.x, y - a.y, z - a.z);
235 }
236 /**
237 * @brief 向量与标量除法运算符重载,计算当前对象与给定标量相除的结果。
238 *
239 * @param a 一个标量,将用作除数。
240 * @return 返回一个新vec3f对象,包含除法运算的结果。
241 */
242 inline vec3f operator/(const double a) const
243 {
244 return vec3f(x / a, y / a, z / a);
245 }
246 /**
247 * @brief 计算当前对象与另一个vec3f对象的点积。
248 *
249 * @param a 另一个vec3f对象。
250 * @return 返回点积的结果。
251 */
252 inline double dot(const vec3f &a) const
253 {
254 return a.x * x + a.y * y + a.z * z;
255 }
256 /**
257 * @brief 计算两个三维向量的叉积
258 *
259 * 计算两个三维向量a和b的叉积,并将结果存储在调用对象中。
260 *
261 * @param a 第一个三维向量
262 * @param b 第二个三维向量
263 * @return 调用对象的引用,其值被更新为叉积的结果
264 */
265 inline vec3f cross(const vec3f &a, const vec3f &b)
266 {
267 x = a.y * b.z - a.z * b.y;
268 y = a.z * b.x - a.x * b.z;
269 z = a.x * b.y - a.y * b.x;
270 return *this;
271 }
272 /**
273 * @brief 计算当前向量与另一个向量之间的角度
274 *
275 * 计算当前向量(*this)与向量v之间的夹角(弧度)。
276 *
277 * @param v 另一个三维向量
278 * @return 夹角(以弧度为单位)
279 */
280 inline double angle(const vec3f &v)
281 {
282 vec3f a = v, b = *this;
283 double dot = v.x * x + v.y * y + v.z * z;
284 double len = a.length() * b.length();
285 if (len == 0)
286 len = 0.00001f;// 避免除以零
287 double input = dot / len;
288 if (input < -1)
289 input = -1;// 防止acos参数超出范围
290 if (input > 1)
291 input = 1;
292 return (double)acos(input);
293 }
294 /**
295 * @brief 计算两个向量之间的角度,考虑第三个向量定义的平面
296 *
297 * 计算向量v和当前向量(*this)之间的夹角,但考虑由第三个向量w定义的平面。
298 * 如果当前向量v在由w和b定义的平面的法线方向上投影为正,则返回负角度。
299 *
300 * @param v 第一个三维向量
301 * @param w 用于定义平面的第三个三维向量
302 * @return 夹角(以弧度为单位)
303 */
304 inline double angle2(const vec3f &v, const vec3f &w)
305 {
306 vec3f a = v, b = *this;
307 double dot = a.x * b.x + a.y * b.y + a.z * b.z;
308 double len = a.length() * b.length();
309 if (len == 0)
310 len = 1;
311
312 vec3f plane;
313 plane.cross(b, w);// 计算b和w的叉积,得到平面的法向量
314
315 if (plane.x * a.x + plane.y * a.y + plane.z * a.z > 0)
316 return (double)-acos(dot / len);// 如果v在法线方向上的投影为正,返回负角度
317
318 return (double)acos(dot / len);
319 }
320 /**
321 * @brief 绕X轴旋转当前向量
322 *
323 * 将当前向量绕X轴旋转a弧度。
324 *
325 * @param a 旋转角度(以弧度为单位)
326 * @return 调用对象的引用,其值被更新为旋转后的结果
327 */
328 inline vec3f rot_x(double a)
329 {
330 double yy = cos(a) * y + sin(a) * z;
331 double zz = cos(a) * z - sin(a) * y;
332 y = yy;
333 z = zz;
334 return *this;
335 }
336 /**
337 * @brief 绕Y轴旋转当前向量
338 *
339 * 将当前向量绕Y轴旋转a弧度。
340 *
341 * @param a 旋转角度(以弧度为单位)
342 * @return 调用对象的引用,其值被更新为旋转后的结果
343 */
344 inline vec3f rot_y(double a)
345 {
346 double xx = cos(-a) * x + sin(-a) * z;
347 double zz = cos(-a) * z - sin(-a) * x;
348 x = xx;
349 z = zz;
350 return *this;
351 }
352 /**
353 * @brief 将当前向量的分量限制在最小值和最大值之间
354 *
355 * 将当前向量的x、y、z分量限制在min和max之间。
356 *
357 * @param min 最小值
358 * @param max 最大值
359 */
360 inline void clamp(double min, double max)
361 {
362 if (x < min)
363 x = min;
364 if (y < min)
365 y = min;
366 if (z < min)
367 z = min;
368 if (x > max)
369 x = max;
370 if (y > max)
371 y = max;
372 if (z > max)
373 z = max;
374 }
375 /**
376 * @brief 绕Z轴旋转当前向量
377 *
378 * 将当前向量绕Z轴旋转a弧度。
379 *
380 * @param a 旋转角度(以弧度为单位)
381 * @return 调用对象的引用,其值被更新为旋转后的结果
382 */
383 inline vec3f rot_z(double a)
384 {
385 double yy = cos(a) * y + sin(a) * x;
386 double xx = cos(a) * x - sin(a) * y;
387 y = yy;
388 x = xx;
389 return *this;
390 }
391 /**
392 * @brief 获取当前向量的相反向量
393 *
394 * 将当前向量的每个分量取反,并返回修改后的向量。
395 *
396 * @return 修改后的向量的引用。
397 */
398 inline vec3f invert()
399 {
400 x = -x;
401 y = -y;
402 z = -z;
403 return *this;
404 }
405 /**
406 * @brief 获取当前向量的分数部分
407 *
408 * 将当前向量的每个分量转换为小数部分(即去除整数部分)。
409 *
410 * @return 一个新的包含小数部分的向量。
411 */
412 inline vec3f frac()
413 {
414 return vec3f(
415 x - double(int(x)),
416 y - double(int(y)),
417 z - double(int(z)));
418 }
419 /**
420 * @brief 获取当前向量的整数部分
421 *
422 * 将当前向量的每个分量转换为整数部分(即去除小数部分)。
423 *
424 * @return 一个新的包含整数部分的向量。
425 */
426 inline vec3f integer()
427 {
428 return vec3f(
429 double(int(x)),
430 double(int(y)),
431 double(int(z)));
432 }
433 /**
434 * @brief 获取当前向量的长度
435 *
436 * 计算并返回当前向量的长度(欧几里得范数)。
437 *
438 * @return 向量的长度。
439 */
440 inline double length() const
441 {
442 return (double)sqrt(x * x + y * y + z * z);
443 }
444 /**
445 * @brief 将当前向量归一化
446 *
447 * 将当前向量缩放为其单位长度(即长度为1),或者指定的长度。
448 *
449 * @param desired_length 希望的长度,默认为1。
450 * @return 修改后的向量的引用。
451 */
452 inline vec3f normalize(double desired_length = 1)
453 {
454 double square = sqrt(x * x + y * y + z * z);
455 /*
456 if (square <= 0.00001f )
457 {
458 x=1;y=0;z=0;
459 return *this;
460 }*/
461 // double len = desired_length / square;
462 x /= square;
463 y /= square;
464 z /= square;
465
466 return *this;
467 }
468 /**
469 * @brief 静态方法:归一化一个向量
470 *
471 * 计算并返回一个向量的单位长度版本。
472 *
473 * @param a 要归一化的向量。
474 * @return 归一化后的向量。
475 */
477 /**
478 * @brief 静态方法:初始化随机数生成器
479 *
480 * 设置随机数生成的初始状态。
481 */
482 static void random_init();
483 /**
484 * @brief 静态方法:生成一个随机双精度浮点数
485 *
486 * 生成一个在[0, 1)范围内的随机双精度浮点数。
487 *
488 * @return 随机双精度浮点数。
489 */
490 static double random_double();
491 /**
492 * @brief 静态方法:生成一个随机三维向量
493 *
494 * 生成一个每个分量都在[0, 1)范围内的随机三维向量。
495 *
496 * @return 随机三维向量。
497 */
498 static vec3f random();
499 /**
500 * @brief 静态成员变量:随机数生成器使用的随机数
501 *
502 * 用于随机数生成的内部状态变量。
503 */
504 static int random_number;
505 /**
506 * @brief 生成一个[0, 1)范围内的随机双精度浮点数,基于给定的输入进行变换
507 *
508 * 使用一个复杂的线性同余生成器(LCG)算法,基于给定的输入a生成一个随机数。
509 *
510 * @param a 输入值,用于影响生成的随机数。
511 * @return [0, 1)范围内的随机双精度浮点数。
512 */
513 double random_double_01(double a)
514 {
515 double rnf = a * 14.434252 + a * 364.2343 + a * 4213.45352 + a * 2341.43255 + a * 254341.43535 + a * 223454341.3523534245 + 23453.423412;
516 int rni = ((int)rnf) % 100000;
517 return double(rni) / (100000.0f - 1.0f);
518 }
519 /**
520 * @brief 将当前向量的每个分量设置为[0, 1)范围内的随机数
521 *
522 * 使用random_double_01方法为每个分量生成一个随机数。
523 *
524 * @return 修改后的向量的引用。
525 */
527 {
528 x = (double)random_double_01(x);
529 y = (double)random_double_01(y);
530 z = (double)random_double_01(z);
531 return *this;
532 }
533};
534/**
535 * @brief 计算点p相对于三角形abc的重心坐标
536 *
537 * 给定三角形abc和点p,计算点p的重心坐标(u, v, w)。
538 * 重心坐标满足u + v + w = 1,并且表示点p相对于三角形abc的位置。
539 *
540 * @param p 需要计算重心坐标的点
541 * @param a 三角形的第一个顶点
542 * @param b 三角形的第二个顶点
543 * @param c 三角形的第三个顶点
544 * @return vec3f 包含重心坐标(u, v, w)的向量
545 */
546vec3f barycentric(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c)
547{
548 vec3f v0 = b - a;
549 vec3f v1 = c - a;
550 vec3f v2 = p - a;
551 double d00 = v0.dot(v0);
552 double d01 = v0.dot(v1);
553 double d11 = v1.dot(v1);
554 double d20 = v2.dot(v0);
555 double d21 = v2.dot(v1);
556 double denom = d00 * d11 - d01 * d01;
557 double v = (d11 * d20 - d01 * d21) / denom;
558 double w = (d00 * d21 - d01 * d20) / denom;
559 double u = 1.0 - v - w;
560 return vec3f(u, v, w);
561}
562/**
563 * @brief 根据重心坐标插值属性
564 *
565 * 给定三角形abc和点p的重心坐标,以及三角形abc三个顶点的属性attrs,
566 * 计算点p处的插值属性。
567 *
568 * @param p 需要计算插值属性的点
569 * @param a 三角形的第一个顶点
570 * @param b 三角形的第二个顶点
571 * @param c 三角形的第三个顶点
572 * @param attrs 包含三角形三个顶点属性的数组
573 * @return vec3f 插值后的属性向量
574 */
575vec3f interpolate(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c, const vec3f attrs[3])
576{
577 vec3f bary = barycentric(p, a, b, c);
578 vec3f out = vec3f(0, 0, 0);
579 out = out + attrs[0] * bary.x;
580 out = out + attrs[1] * bary.y;
581 out = out + attrs[2] * bary.z;
582 return out;
583}
584/**
585 * @brief 返回两个数中的较小值
586 *
587 * @param v1 第一个数
588 * @param v2 第二个数
589 * @return double 返回v1和v2中的较小值
590 */
591double min(double v1, double v2)
592{
593 return fmin(v1, v2);
594}
595/**
596 * @brief 对称矩阵类
597 *
598 * 表示一个4x4对称矩阵,只存储上三角矩阵的元素(包括对角线)。
599 */
601{
602
603public:
604 /**
605 * @brief 构造函数,使用默认值初始化所有元素
606 *
607 * @param c 所有元素的默认值
608 */
609 SymetricMatrix(double c = 0) { loopi(0, 10) m[i] = c; }
610 /**
611 * @brief 构造函数,使用给定的值初始化矩阵
612 *
613 * @param m11, m12, m13, m14 上三角矩阵的第一行元素
614 * @param m22, m23, m24 上三角矩阵的第二行元素(不包括m21,因为是对称矩阵)
615 * @param m33, m34 上三角矩阵的第三行元素(不包括m31, m32,因为是对称矩阵)
616 * @param m44 上三角矩阵的第四行第四列元素(不包括m41, m42, m43,因为是对称矩阵)
617 */
618 SymetricMatrix(double m11, double m12, double m13, double m14,
619 double m22, double m23, double m24,
620 double m33, double m34,
621 double m44)
622 {
623 m[0] = m11;
624 m[1] = m12;
625 m[2] = m13;
626 m[3] = m14;
627 m[4] = m22;
628 m[5] = m23;
629 m[6] = m24;
630 m[7] = m33;
631 m[8] = m34;
632 m[9] = m44;
633 }
634
635 /**
636 * @brief 使用平面方程的参数构造对称矩阵
637 *
638 * 给定平面方程ax + by + cz + d = 0的参数a, b, c, d,
639 * 构造一个表示该平面点积矩阵的对称矩阵。
640 *
641 * @param a 平面方程的参数a
642 * @param b 平面方程的参数b
643 * @param c 平面方程的参数c
644 * @param d 平面方程的参数d
645 */
646 SymetricMatrix(double a, double b, double c, double d)
647 {
648 m[0] = a * a;
649 m[1] = a * b;
650 m[2] = a * c;
651 m[3] = a * d;
652 m[4] = b * b;
653 m[5] = b * c;
654 m[6] = b * d;
655 m[7] = c * c;
656 m[8] = c * d;
657 m[9] = d * d;
658 }
659 /**
660 * @brief 访问矩阵元素
661 *
662 * @param c 要访问的元素索引
663 * @return double 返回指定索引处的矩阵元素值
664 */
665 double operator[](int c) const { return m[c]; }
666
667 /**
668 * @brief 计算矩阵的子行列式
669 *
670 * 给定子行列式的元素索引,计算该子行列式的值。
671 *
672 * @param a11, a12, a13 子行列式的第一行元素索引
673 * @param a21, a22, a23 子行列式的第二行元素索引
674 * @param a31, a32, a33 子行列式的第三行元素索引
675 * @return double 返回子行列式的值
676 */
677 double det(int a11, int a12, int a13,
678 int a21, int a22, int a23,
679 int a31, int a32, int a33)
680 {
681 double det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] + m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] - m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33];
682 return det;
683 }
684 /**
685 * @brief 矩阵加法运算符重载
686 *
687 * @param n 要相加的另一个对称矩阵
688 * @return SymetricMatrix 返回相加后的新对称矩阵
689 */
691 {
692 return SymetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3] + n[3],
693 m[4] + n[4], m[5] + n[5], m[6] + n[6],
694 m[7] + n[7], m[8] + n[8],
695 m[9] + n[9]);
696 }
697 /**
698 * @brief 矩阵加法赋值运算符重载
699 *
700 * @param n 要相加的另一个对称矩阵
701 * @return SymetricMatrix& 返回当前矩阵的引用,以便链式调用
702 */
704 {
705 m[0] += n[0];
706 m[1] += n[1];
707 m[2] += n[2];
708 m[3] += n[3];
709 m[4] += n[4];
710 m[5] += n[5];
711 m[6] += n[6];
712 m[7] += n[7];
713 m[8] += n[8];
714 m[9] += n[9];
715 return *this;
716 }
717 /**
718 * @brief 存储矩阵元素的数组
719 *
720 * 只存储上三角矩阵的元素(包括对角线),共10个元素。
721 */
722 double m[10];
723};
724///////////////////////////////////////////
725/**
726 * @namespace Simplify
727 *
728 * @brief 命名空间,用于简化网格模型的工具和数据结构。
729 */
730namespace Simplify
731{
732 /**
733 * @enum Attributes
734 *
735 * @brief 枚举类型,表示三角形的属性。
736 */
738 {
739 /**
740 * @brief 无属性。
741 */
743 /**
744 * @brief 普通属性。
745 */
747 /**
748 * @brief 包含纹理坐标属性。
749 */
751 /**
752 * @brief 包含颜色属性。
753 */
754 COLOR = 8
755 };
756 /**
757 * @struct Triangle
758 *
759 * @brief 表示三角形的数据结构。
760 */
761 struct Triangle
762 {
763 /**
764 * @brief 三角形的三个顶点索引。
765 */
766 int v[3];
767 /**
768 * @brief 误差值数组,用于简化算法。
769 */
770 double err[4];
771 /**
772 * @brief 是否被删除的标志、是否需要重新计算的标志和三角形的属性。
773 */
775 /**
776 * @brief 法向量。
777 */
779 /**
780 * @brief 三个顶点的纹理坐标。
781 */
783 /**
784 * @brief 材质索引。
785 */
787 };
788 /**
789 * @struct Vertex
790 *
791 * @brief 表示顶点的数据结构。
792 */
793 struct Vertex
794 {
795 /**
796 * @brief 顶点位置。
797 */
799 /**
800 * @brief 第一个引用此顶点的三角形的索引和引用此顶点的三角形数量。
801 */
803 /**
804 * @brief 对称矩阵,用于存储顶点信息。
805 */
807 /**
808 * @brief 是否为边界顶点的标志。
809 */
811 };
812 /**
813 * @struct Ref
814 *
815 * @brief 表示三角形和顶点之间的引用的数据结构。
816 */
817 struct Ref
818 {/**
819 * @brief 三角形的索引和三角形中引用的顶点索引。
820 */
822 };
823 /**
824 * @class SimplificationObject
825 *
826 * @brief 用于简化网格的类。
827 */
829 {
830 public:
831 /**
832 * @brief 存储三角形的向量。
833 */
834 std::vector<Triangle> triangles;
835 /**
836 * @brief 存储顶点的向量。
837 */
838 std::vector<Vertex> vertices;
839 /**
840 * @brief 存储引用的向量。
841 */
842 std::vector<Ref> refs;
843 /**
844 * @brief 材质库的名称。
845 */
846 std::string mtllib;
847 /**
848 * @brief 存储材质名称的向量。
849 */
850 std::vector<std::string> materials;
851
852 /**
853 * @brief 主简化函数。
854 *
855 * @param target_count 目标三角形数量。
856 * @param agressiveness 激进程度,用于增加阈值的锐度。5到8是较好的数值,更多迭代次数可以获得更高质量。
857 * @param verbose 是否输出详细信息。
858 */
859 void simplify_mesh(int target_count, double agressiveness = 7, bool verbose = false)
860 {
861 // 初始化
862 loopi(0, triangles.size())
863 {
864 triangles[i].deleted = 0;
865 }
866
867 // 主迭代循环
868 int deleted_triangles = 0;
869 std::vector<int> deleted0, deleted1;
870 int triangle_count = triangles.size();
871 // int iteration = 0;
872 // loop(iteration,0,100)
873 for (int iteration = 0; iteration < 100; iteration++)
874 {
875 if (triangle_count - deleted_triangles <= target_count)
876 break;
877
878 // 每隔一段时间更新网格
879 if (iteration % 5 == 0)
880 {
881 update_mesh(iteration);
882 }
883
884 // clear dirty flag
885 loopi(0, triangles.size()) triangles[i].dirty = 0;
886
887 // 计算当前迭代的误差阈值
888 // 以下数值对大多数模型有效,如果不适用,可以尝试调整以下三个参数
889 double threshold = 0.000000001 * pow(double(iteration + 3), agressiveness);
890
891 // 如果启用了详细输出,并且迭代次数是5的倍数,则打印当前状态
892 if ((verbose) && (iteration % 5 == 0))
893 {
894 printf("iteration %d - triangles %d threshold %g\n", iteration, triangle_count - deleted_triangles, threshold);
895 }
896
897 // 遍历所有三角形,移除误差较小的三角形并标记已删除的三角形
898 loopi(0, triangles.size())
899 {
900 Triangle &t = triangles[i];
901 if (t.err[3] > threshold)
902 continue;
903 if (t.deleted)
904 continue;
905 if (t.dirty)
906 continue;
907 // 遍历三角形的三条边
908 loopj(0, 3) if (t.err[j] < threshold)
909 {
910
911 int i0 = t.v[j];
912 Vertex &v0 = vertices[i0];
913 int i1 = t.v[(j + 1) % 3];
914 Vertex &v1 = vertices[i1];
915 // 如果任一顶点位于边界上,则跳过
916 if (v0.border || v1.border)
917 continue;
918
919 // 计算要合并到的顶点位置
920 vec3f p;
921 calculate_error(i0, i1, p);
922 // 临时存储顶点相关的三角形信息
923 deleted0.resize(v0.tcount);
924 deleted1.resize(v1.tcount);
925 // 如果合并后会导致翻转,则跳过
926 if (flipped(p, i0, i1, v0, v1, deleted0))
927 continue;
928
929 if (flipped(p, i1, i0, v1, v0, deleted1))
930 continue;
931 // 如果三角形具有纹理坐标,则更新纹理坐标
932 if ((t.attr & TEXCOORD) == TEXCOORD)
933 {
934 update_uvs(i0, v0, p, deleted0);
935 update_uvs(i0, v1, p, deleted1);
936 }
937
938 // 如果没有翻转,则移除边
939 v0.p = p;
940 v0.q = v1.q + v0.q;
941 // 更新三角形信息
942 int tstart = refs.size();
943
944 update_triangles(i0, v0, deleted0, deleted_triangles);
945 update_triangles(i0, v1, deleted1, deleted_triangles);
946
947 int tcount = refs.size() - tstart;
948 // 如果更新后的三角形数量小于等于原始数量,则节省内存
949 if (tcount <= v0.tcount)
950 {
951
952 if (tcount)
953 memcpy(&refs[v0.tstart], &refs[tstart], tcount * sizeof(Ref));
954 }
955 else
956 // 否则,追加新的三角形信息
957 v0.tstart = tstart;
958
959 v0.tcount = tcount;
960 // 跳出内层循环,继续下一个三角形的检查
961 break;
962 }
963 // 如果已达到目标三角形数量,则退出外层循环
964 if (triangle_count - deleted_triangles <= target_count)
965 break;
966 }
967 }
968 // 清理网格,移除所有已标记为删除的三角形
969 compact_mesh();
970 } // simplify_mesh()
971 /**
972 * @brief 无损简化网格
973 *
974 * 该函数通过迭代地移除误差低于给定阈值的三角形来简化网格,同时保持网格的无损性。
975 *
976 * @param verbose 是否打印详细信息,默认为false不打印
977 */
978 void simplify_mesh_lossless(bool verbose = false)
979 {
980 // 初始化
981 /// @todo 使用范围for循环替换宏定义的循环,以提高代码可读性和安全性
982 loopi(0, triangles.size()) triangles[i].deleted = 0;
983
984 // 主迭代循环
985 int deleted_triangles = 0;
986 std::vector<int> deleted0, deleted1;
987
988 // 迭代处理网格
989 for (int iteration = 0; iteration < 9999; iteration++)
990 {
991 // 不断更新网格
992 update_mesh(iteration);
993 // clear dirty flag
994 loopi(0, triangles.size()) triangles[i].dirty = 0;
995 // 移除误差低于阈值的边及其三角形
996 double threshold = DBL_EPSILON; // 1.0E-3 EPS;用于确定哪些三角形应该被移除
997 if (verbose)
998 {
999 printf("lossless iteration %d\n", iteration);
1000 }
1001
1002 // 遍历所有三角形,移除符合条件的三角形
1003 loopi(0, triangles.size())
1004 {
1005 Triangle &t = triangles[i];
1006 if (t.err[3] > threshold)
1007 continue;
1008 if (t.deleted)
1009 continue;
1010 if (t.dirty)
1011 continue;
1012
1013 loopj(0, 3) if (t.err[j] < threshold)
1014 {
1015 int i0 = t.v[j];
1016 Vertex &v0 = vertices[i0];
1017 int i1 = t.v[(j + 1) % 3];
1018 Vertex &v1 = vertices[i1];
1019
1020 // 边界检查,如果两个顶点不在同一边界上,则跳过
1021 if (v0.border != v1.border)
1022 continue;
1023
1024 // 计算要合并到的顶点位置
1025 vec3f p;
1026 calculate_error(i0, i1, p);
1027 // 临时存储顶点相关的三角形索引
1028 deleted0.resize(v0.tcount);
1029 deleted1.resize(v1.tcount);
1030
1031 // 如果合并后导致法线翻转,则跳过
1032 if (flipped(p, i0, i1, v0, v1, deleted0))
1033 continue;
1034 if (flipped(p, i1, i0, v1, v0, deleted1))
1035 continue;
1036 // 如果三角形有纹理坐标,则更新纹理坐标
1037 if ((t.attr & TEXCOORD) == TEXCOORD)
1038 {
1039 update_uvs(i0, v0, p, deleted0);
1040 update_uvs(i0, v1, p, deleted1);
1041 }
1042
1043 // 合并顶点并更新相关三角形
1044 v0.p = p;
1045 v0.q = v1.q + v0.q;
1046 int tstart = refs.size();
1047
1048 update_triangles(i0, v0, deleted0, deleted_triangles);
1049 update_triangles(i0, v1, deleted1, deleted_triangles);
1050
1051 int tcount = refs.size() - tstart;
1052 // 如果合并后的三角形数量小于等于原数量,则节省内存
1053 if (tcount <= v0.tcount)
1054 {
1055 if (tcount)
1056 memcpy(&refs[v0.tstart], &refs[tstart], tcount * sizeof(Ref));
1057 }
1058 else
1059 // 否则,追加新的三角形索引
1060 v0.tstart = tstart;
1061
1062 v0.tcount = tcount;
1063 break;// 找到一个可合并的边后跳出内层循环
1064 }
1065 }// 如果没有三角形被删除,则跳出循环
1066 if (deleted_triangles <= 0)
1067 break;
1068 deleted_triangles = 0;// 重置计数器,为下一轮迭代准备
1069 } // 迭代结束
1070 // 清理网格,移除所有被标记为删除的三角形
1071 compact_mesh();
1072 } // simplify_mesh_lossless()
1073
1074 /**
1075 * @brief 检查移除指定边后三角形是否会翻转
1076 *
1077 * 遍历与顶点v0相关联的所有三角形,检查如果移除边(p, v0) -> (p, v1)后,
1078 * 是否有三角形发生翻转。翻转的条件包括:
1079 * 1. 边(p, v0)相邻的两向量几乎共线(点积接近1或-1)。
1080 * 2. 三角形的法向量与由边(p, v0)相邻的两向量构成的平面的法向量差异较大。
1081 *
1082 * @param p 与v0和v1形成边的另一个端点
1083 * @param i0 顶点p在顶点数组中的索引
1084 * @param i1 顶点v1在顶点数组中的索引
1085 * @param v0 顶点v0的引用
1086 * @param v1 顶点v1的引用
1087 * @param deleted 用于标记被删除的三角形的数组
1088 * @return true 如果移除边后至少有一个三角形翻转,否则返回false
1089 */
1090 bool flipped(vec3f p, int i0, int i1, Vertex &v0, Vertex &v1, std::vector<int> &deleted)
1091 {
1092
1093 loopk(0, v0.tcount)
1094 {
1095 Triangle &t = triangles[refs[v0.tstart + k].tid];
1096 if (t.deleted)
1097 continue;
1098
1099 int s = refs[v0.tstart + k].tvertex;
1100 int id1 = t.v[(s + 1) % 3];
1101 int id2 = t.v[(s + 2) % 3];
1102
1103 if (id1 == i1 || id2 == i1) // delete ?
1104 {
1105
1106 deleted[k] = 1;
1107 continue;
1108 }
1109 vec3f d1 = vertices[id1].p - p;
1110 d1.normalize();
1111 vec3f d2 = vertices[id2].p - p;
1112 d2.normalize();
1113 if (fabs(d1.dot(d2)) > 0.999)
1114 return true;
1115 vec3f n;
1116 n.cross(d1, d2);
1117 n.normalize();
1118 deleted[k] = 0;
1119 if (n.dot(t.n) < 0.2)
1120 return true;
1121 }
1122 return false;
1123 }
1124
1125 /**
1126 * @brief 更新UV坐标
1127 *
1128 * 遍历与顶点v相关联的所有三角形,对于未被删除且当前顶点未被标记为删除的三角形,
1129 * 更新其UV坐标。新的UV坐标通过插值计算得到。
1130 *
1131 * @param i0 顶点p在顶点数组中的索引
1132 * @param v 顶点v的引用
1133 * @param p 与v形成边的另一个端点
1134 * @param deleted 用于标记被删除的三角形的数组
1135 */
1136 void update_uvs(int i0, const Vertex &v, const vec3f &p, std::vector<int> &deleted)
1137 {
1138 loopk(0, v.tcount)
1139 {
1140 Ref &r = refs[v.tstart + k];
1141 Triangle &t = triangles[r.tid];
1142 if (t.deleted)
1143 continue;
1144 if (deleted[k])
1145 continue;
1146 vec3f p1 = vertices[t.v[0]].p;
1147 vec3f p2 = vertices[t.v[1]].p;
1148 vec3f p3 = vertices[t.v[2]].p;
1149 t.uvs[r.tvertex] = interpolate(p, p1, p2, p3, t.uvs);
1150 }
1151 }
1152
1153 /**
1154 * @brief 更新三角形连接和边误差
1155 *
1156 * 遍历与顶点v相关联的所有三角形,更新其顶点连接和边误差。如果三角形被标记为删除,
1157 * 则将其标记为已删除并增加已删除三角形的计数。否则,更新三角形的顶点连接,
1158 * 并重新计算其误差。
1159 *
1160 * @param i0 顶点p在顶点数组中的索引
1161 * @param v 顶点v的引用
1162 * @param deleted 用于标记被删除的三角形的数组
1163 * @param deleted_triangles 已删除三角形的计数,通过引用传递以便更新
1164 */
1165 void update_triangles(int i0, Vertex &v, std::vector<int> &deleted, int &deleted_triangles)
1166 {
1167 vec3f p;
1168 loopk(0, v.tcount)// 遍历与顶点v相关联的所有三角形
1169 {
1170 Ref &r = refs[v.tstart + k];// 获取三角形r的引用
1171 Triangle &t = triangles[r.tid];// 获取该三角形t
1172 if (t.deleted)// 如果三角形已经标记为删除,跳过
1173 continue;
1174 if (deleted[k]) // 如果当前三角形被标记为删除
1175 {
1176 t.deleted = 1;// 将该三角形标记为删除
1177 deleted_triangles++;// 增加删除的三角形计数
1178 continue;
1179 } // 如果三角形未被删除,更新三角形的顶点索引
1180 t.v[r.tvertex] = i0;// 用新顶点索引替换
1181 t.dirty = 1;// 标记三角形为错误·,需要重新计算
1182 // 计算三角形的误差值
1183 t.err[0] = calculate_error(t.v[0], t.v[1], p);
1184 t.err[1] = calculate_error(t.v[1], t.v[2], p);
1185 t.err[2] = calculate_error(t.v[2], t.v[0], p);
1186 t.err[3] = min(t.err[0], min(t.err[1], t.err[2]));// 获取最小的误差
1187 refs.push_back(r);// 将更新后的引用加入引用列表
1188 }
1189 }
1190
1191 /**
1192 * @brief 更新网格:压缩三角形,并建立引用列表
1193 *
1194 * 该函数在指定的迭代次数大于0时,会压缩三角形数组,移除被标记为删除的三角形。
1195 * 接着,它会初始化顶点的三角形起始索引和三角形计数,然后遍历三角形数组来更新这些值。
1196 * 最后,它会构建一个引用列表,该列表将每个顶点与其相关联的三角形以及三角形中的顶点索引相关联。
1197 *
1198 *
1199 * @param iteration 当前迭代次数,用于判断是否进行三角形的压缩
1200 */
1201 void update_mesh(int iteration)
1202 {// 如果迭代次数大于0,则压缩三角形数组
1203 if (iteration > 0)
1204 {
1205 int dst = 0;// 目标索引,用于记录非删除三角形的位置
1206 loopi(0, triangles.size()) if (!triangles[i].deleted)// 遍历三角形数组
1207 {
1208 triangles[dst++] = triangles[i];// 将非删除三角形移动到数组前面
1209 }
1210 triangles.resize(dst);// 调整三角形数组大小,移除被删除的三角形
1211 }
1212 //
1213
1214 // 初始化顶点的三角形起始索引和三角形计数
1215 loopi(0, vertices.size())
1216 {
1217 vertices[i].tstart = 0;// 初始化三角形的起始索引为0
1218 vertices[i].tcount = 0;// 初始化与该顶点相关联的三角形数量为0
1219 }
1220 // 更新顶点的三角形计数
1221 loopi(0, triangles.size())
1222 {
1223 Triangle &t = triangles[i];// 引用当前三角形
1224 loopj(0, 3) vertices[t.v[j]].tcount++;// 遍历三角形的三个顶点,增加与该顶点相关联的三角形数量
1225 }
1226 int tstart = 0;// 用于计算每个顶点的三角形起始索引
1227 loopi(0, vertices.size())
1228 {
1229 Vertex &v = vertices[i];// 引用当前顶点
1230 v.tstart = tstart;// 设置该顶点的三角形起始索引
1231 tstart += v.tcount;// 更新下一个顶点的三角形起始索引
1232 v.tcount = 0;// 重置顶点的三角形计数(为构建引用列表做准备)
1233 }
1234
1235 // Write References
1236 refs.resize(triangles.size() * 3); // 每个三角形有三个顶点,因此引用列表的大小是三角形数的三倍
1237 loopi(0, triangles.size())
1238 {
1239 Triangle &t = triangles[i];
1240 loopj(0, 3)
1241 {
1242 Vertex &v = vertices[t.v[j]];
1243 refs[v.tstart + v.tcount].tid = i;// 记录三角形ID
1244 refs[v.tstart + v.tcount].tvertex = j;// 记录该顶点在三角形中的位置
1245 v.tcount++;// 更新顶点的计数
1246 }
1247 }
1248
1249 // Init Quadrics by Plane & Edge Errors
1250 //
1251 // required at the beginning ( iteration == 0 )
1252 // recomputing during the simplification is not required,
1253 // but mostly improves the result for closed meshes
1254 //
1255 if (iteration == 0)
1256 { // 标记边界顶点
1257 // Identify boundary : vertices[].border=0,1
1258
1259 std::vector<int> vcount, vids;
1260
1261 loopi(0, vertices.size())
1262 vertices[i]
1263 .border = 0; // 初始化边界标志
1264
1265 loopi(0, vertices.size())
1266 {
1267 Vertex &v = vertices[i];
1268 vcount.clear();
1269 vids.clear();
1270 loopj(0, v.tcount)
1271 {
1272 int k = refs[v.tstart + j].tid;// 获取三角形ID
1273 Triangle &t = triangles[k];
1274 loopk(0, 3)
1275 {
1276 int ofs = 0, id = t.v[k];// 获取三角形中的顶点ID
1277 while (ofs < vcount.size())// 检查该顶点是否已在vcount中,如果没有,则添加
1278 {
1279 if (vids[ofs] == id)
1280 break;
1281 ofs++;
1282 }
1283 if (ofs == vcount.size())
1284 {
1285 vcount.push_back(1);
1286 vids.push_back(id);
1287 }
1288 else
1289 vcount[ofs]++;
1290 }
1291 } // 标记仅与一个三角形相邻的顶点为边界顶点
1292 loopj(0, vcount.size()) if (vcount[j] == 1)
1293 vertices[vids[j]]
1294 .border = 1;
1295 }
1296 // initialize errors
1297 loopi(0, vertices.size())
1298 vertices[i]
1299 .q = SymetricMatrix(0.0);
1300 // 计算三角形法向量和误差矩阵
1301 loopi(0, triangles.size())
1302 {
1303 Triangle &t = triangles[i];
1304 vec3f n, p[3];
1305 loopj(0, 3) p[j] = vertices[t.v[j]].p;
1306 n.cross(p[1] - p[0], p[2] - p[0]);
1307 n.normalize();
1308 t.n = n;
1309 loopj(0, 3) vertices[t.v[j]].q =
1310 vertices[t.v[j]].q + SymetricMatrix(n.x, n.y, n.z, -n.dot(p[0]));
1311 } // 计算每条边的误差
1312
1313 loopi(0, triangles.size())
1314 {
1315 // Calc Edge Error
1316 Triangle &t = triangles[i];
1317 vec3f p;
1318 loopj(0, 3) t.err[j] = calculate_error(t.v[j], t.v[(j + 1) % 3], p);
1319 t.err[3] = min(t.err[0], min(t.err[1], t.err[2])); // 获取最小误差
1320 }
1321 }
1322 }
1323
1324 // Finally compact mesh before exiting
1325
1327 {
1328 int dst = 0;
1329 loopi(0, vertices.size())
1330 {
1331 vertices[i].tcount = 0;// 重置顶点的三角形计数
1332 }// 压缩三角形和顶点
1333 loopi(0, triangles.size()) if (!triangles[i].deleted)
1334 {
1335 Triangle &t = triangles[i];
1336 triangles[dst++] = t;// 将非删除三角形压缩到数组前面
1337 loopj(0, 3) vertices[t.v[j]].tcount = 1;// 更新顶点的三角形计数
1338 }
1339 triangles.resize(dst);// 调整三角形数组大小
1340 dst = 0;
1341 loopi(0, vertices.size()) if (vertices[i].tcount)
1342 {
1343 vertices[i].tstart = dst;// 更新顶点的起始索引
1344 vertices[dst].p = vertices[i].p;// 将顶点坐标复制到新的位置
1345 dst++;
1346 }
1347 // 更新三角形的顶点索引
1348 loopi(0, triangles.size())
1349 {
1350 Triangle &t = triangles[i];
1351 loopj(0, 3) t.v[j] = vertices[t.v[j]].tstart;// 更新三角形的顶点索引
1352 }
1353 vertices.resize(dst);
1354 }
1355
1356 // Error between vertex and Quadric
1357 // 计算顶点的误差
1358 double vertex_error(SymetricMatrix q, double x, double y, double z)
1359 {
1360 return q[0] * x * x + 2 * q[1] * x * y + 2 * q[2] * x * z + 2 * q[3] * x + q[4] * y * y + 2 * q[5] * y * z + 2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9];
1361 }
1362// 计算并返回一个顶点 (x, y, z) 处的误差,误差公式根据对称矩阵 q 给定
1363 // 计算一个边缘的误差
1364 double calculate_error(int id_v1, int id_v2, vec3f &p_result)
1365 {
1366 // 计算两顶点 (id_v1 和 id_v2) 之间的误差并返回最小误差。
1367 // p_result 存储计算出的最优点。
1368 // 获取两个顶点的对称矩阵并求和
1369 SymetricMatrix q = vertices[id_v1].q + vertices[id_v2].q;
1370 // 判断是否为边界顶点
1371 bool border = vertices[id_v1].border & vertices[id_v2].border;
1372 double error = 0;
1373 double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);// 计算矩阵 q 的行列式
1374 if (det != 0 && !border)
1375 {// 如果行列式不为 0 并且不在边界,则计算最优插值顶点 p_result
1376 // q_delta 的逆可用
1377
1378 p_result.x = -1 / det * (q.det(1, 2, 3, 4, 5, 6, 5, 7, 8)); // vx = A41/det(q_delta)
1379 p_result.y = 1 / det * (q.det(0, 2, 3, 1, 5, 6, 2, 7, 8)); // vy = A42/det(q_delta)
1380 p_result.z = -1 / det * (q.det(0, 1, 3, 1, 4, 6, 2, 5, 8)); // vz = A43/det(q_delta)
1381 // 计算此插值顶点的误差
1382 error = vertex_error(q, p_result.x, p_result.y, p_result.z);
1383 }
1384 else
1385 {// 如果行列式为 0(不可逆),则选择最小误差的点
1386 vec3f p1 = vertices[id_v1].p;// 获取第一个顶点的位置
1387 vec3f p2 = vertices[id_v2].p; // 获取第二个顶点的位置
1388 vec3f p3 = (p1 + p2) / 2; // 计算两点的中点
1389 // 计算每个顶点的误差
1390 double error1 = vertex_error(q, p1.x, p1.y, p1.z);
1391 double error2 = vertex_error(q, p2.x, p2.y, p2.z);
1392 double error3 = vertex_error(q, p3.x, p3.y, p3.z);
1393 // 选择最小的误差,并将结果存储到 p_result
1394 error = min(error1, min(error2, error3));
1395 if (error1 == error)
1396 p_result = p1;
1397 if (error2 == error)
1398 p_result = p2;
1399 if (error3 == error)
1400 p_result = p3;
1401 }
1402 return error;
1403 }
1404// 修剪字符串两端的空格
1405 char *trimwhitespace(char *str)
1406 {
1407 char *end;
1408 // 修剪前导空格
1409 while (isspace((unsigned char)*str))
1410 str++;
1411
1412 if (*str == 0) // 如果全部为空格
1413 return str;
1414// 解释:判断传入的字符串指针所指向的字符是否为 '\0'(字符串结束标志,在这里表示字符串为空或者全是空格的情况),
1415// 如果是,则直接返回该字符串指针,意味着不需要进行后续处理,直接将原字符串返回。
1416 // 修剪尾部空格
1417 end = str + strlen(str) - 1;
1418// 解释:先获取字符串的末尾指针位置,通过将字符串指针 `str` 加上字符串长度(`strlen(str)`)再减去 1,得到指向字符串最后一个字符的指针,
1419// 这里减 1 是因为字符串长度是从 1 开始计数,而指针指向的是字符在内存中的地址,要指向最后一个实际字符需要减去 1。
1420 while (end > str && isspace((unsigned char)*end))
1421 end--;
1422// 解释:从字符串末尾开始向前遍历,只要当前指针 `end` 大于起始指针 `str`(表示还没遍历到字符串开头)并且当前指针所指向的字符是空格(通过 `isspace` 函数判断,
1423// 将字符强制转换为 `unsigned char` 类型是为了符合函数参数要求),就将指针 `end` 向前移动一位(即 `end--`),目的是找到最后一个非空格字符的位置,从而去除尾部空格。
1424 // 写入新的终止符
1425 *(end + 1) = 0;
1426
1427 return str;
1428 }
1429// 解释:返回处理后的字符串指针,这个指针指向的字符串已经去除了尾部空格,可用于后续需要使用该字符串的地方。
1430 // 加载 OBJ 文件
1431 void load_obj(const char *filename, bool process_uv = false)
1432 {
1433
1434 vertices.clear();
1435 triangles.clear();
1436// 解释:清空存储顶点数据的 `vertices` 容器和存储三角形数据的 `triangles` 容器,通常这两个容器可能是 `vector` 等类型,
1437 // 这样做是为了在加载新的 `OBJ` 文件前清除之前可能存在的数据,避免数据混淆。
1438 // printf ( "Loading Objects %s ... \n",filename);
1439 FILE *fn;
1440 if (filename == NULL)
1441 return;
1442 if ((char)filename[0] == 0)
1443 return;
1444 if ((fn = fopen(filename, "rb")) == NULL)
1445 {
1446 printf("File %s not found!\n", filename);
1447 return;
1448 }
1449// 解释:尝试以二进制只读模式("rb")打开指定的文件,如果打开文件失败(`fopen` 函数返回 `NULL`),则输出提示文件未找到的信息,并直接返回,
1450 // 无法进行后续从文件读取数据的操作。
1451 char line[1000];
1452 memset(line, 0, 1000);
1453// 解释:创建一个长度为 1000 的字符数组 `line`,用于逐行读取文件中的内容,并通过 `memset` 函数将数组的所有元素初始化为 '\0',
1454 // 确保数组内容初始为空,避免出现未初始化的垃圾数据影响后续操作。
1455 int vertex_cnt = 0;
1456 int material = -1;
1457 std::map<std::string, int> material_map;
1458 std::vector<vec3f> uvs;
1459 std::vector<std::vector<int>> uvMap;
1460// 解释:初始化一些变量和容器,`vertex_cnt` 可能用于记录顶点的数量计数,`material` 用于表示当前使用的材质编号(初始化为 -1 表示未确定),
1461 // `material_map` 是一个 `map` 容器,用于将材质名称映射到对应的编号,方便后续查找和管理材质信息;`uvs` 是一个存储纹理坐标的向量容器,
1462 // `uvMap` 是一个二维向量容器,可能用于存储纹理坐标与三角形顶点之间的映射关系等相关信息。
1463 // 逐行读取文件内容
1464 while (fgets(line, 1000, fn) != NULL)
1465 {
1466 Vertex v;
1467 vec3f uv;
1468 // 解释:在每次循环中,创建一个 `Vertex` 类型的对象 `v`(`Vertex` 类型应该是自定义的顶点结构体等类似类型)和一个 `vec3f` 类型的纹理坐标对象 `uv`,
1469 // 用于临时存储从文件中解析出的顶点和纹理坐标信息。
1470
1471 // 解析材质库
1472 if (strncmp(line, "mtllib", 6) == 0)
1473 {
1474 mtllib = trimwhitespace(&line[7]);
1475 }
1476// 解释:判断当前读取的行内容是否以 "mtllib" 开头(通过 `strncmp` 函数比较前 6 个字符),如果是,表示这一行是材质库相关的信息,
1477 // 调用 `trimwhitespace` 函数(前面定义的用于去除空格的函数)处理该行从第 7 个字符开始的内容(即材质库文件名部分),去除可能存在的空格后赋值给 `mtllib` 变量,
1478 // `mtllib` 变量应该是用于存储材质库文件名的。
1479
1480 if (strncmp(line, "usemtl", 6) == 0)
1481 {
1482 std::string usemtl = trimwhitespace(&line[7]);
1483 if (material_map.find(usemtl) == material_map.end())
1484 {
1485 material_map[usemtl] = materials.size();
1486 materials.push_back(usemtl);
1487 }
1488 material = material_map[usemtl];
1489 }
1490// 解释:判断当前行是否以 "usemtl" 开头,如果是,表示这一行指定了使用的材质名称。先调用 `trimwhitespace` 函数处理从第 7 个字符开始的材质名称部分,
1491 // 得到去除空格后的材质名称字符串,然后在 `material_map` 中查找该材质名称是否已经存在映射关系,如果不存在,则将该材质名称添加到 `material_map` 中,
1492 // 其编号为 `materials` 容器的当前大小(即新的材质编号),同时将材质名称也添加到 `materials` 容器中(`materials` 可能用于存储所有出现过的材质名称列表),
1493 // 最后通过 `material_map` 获取该材质名称对应的编号并赋值给 `material` 变量,用于记录当前使用的材质编号。
1494 // 解析纹理坐标
1495 if (line[0] == 'v' && line[1] == 't')
1496 {
1497 if (line[2] == ' ')
1498 if (sscanf(line, "vt %lf %lf",
1499 &uv.x, &uv.y) == 2)
1500 {
1501 uv.z = 0;
1502 uvs.push_back(uv);
1503 }
1504 else if (sscanf(line, "vt %lf %lf %lf",
1505 &uv.x, &uv.y, &uv.z) == 3)
1506 {
1507 uvs.push_back(uv);
1508 }
1509 }
1510// 解释:判断当前行是否以 "v" 开头且第二个字符是 "t",表示这一行是纹理坐标信息。如果第三个字符是空格,说明格式符合简单的二维纹理坐标格式,
1511 // 尝试通过 `sscanf` 函数按照 "vt %lf %lf" 的格式从该行提取两个浮点数(分别赋值给 `uv` 对象的 `x` 和 `y` 成员变量),如果成功提取到两个值(`sscanf` 返回 2),
1512 // 则将 `uv` 对象的 `z` 成员变量设置为 0,并将该 `uv` 对象添加到 `uvs` 容器中;如果按照二维格式提取失败,但按照 "vt %lf %lf %lf" 格式能成功提取三个浮点数(`sscanf` 返回 3),
1513 // 则直接将 `uv` 对象添加到 `uvs` 容器中,说明这是三维纹理坐标信息。
1514 else if (line[0] == 'v')
1515 { // 解析顶点坐标
1516 if (line[1] == ' ')
1517 if (sscanf(line, "v %lf %lf %lf",
1518 &v.p.x, &v.p.y, &v.p.z) == 3)
1519 {
1520 vertices.push_back(v);
1521 }
1522 }
1523// 解释:判断当前行是否仅以 "v" 开头,表示这一行是顶点坐标信息。如果第二个字符是空格,说明格式符合常见的顶点坐标格式,
1524 // 尝试通过 `sscanf` 函数按照 "v %lf %lf %lf" 的格式从该行提取三个浮点数(分别赋值给 `v` 对象的 `p`(`p` 应该是 `Vertex` 结构体中表示坐标的成员变量,类型可能是 `vec3f` 等)的 `x`、`y`、`z` 成员变量),
1525 // 如果成功提取到三个值(`sscanf` 返回 3),则将该 `v` 对象添加到 `vertices` 容器中,用于存储所有解析出的顶点坐标信息。
1526 int integers[9];
1527// 判断读取的行首字符是否为'f',通常用于识别某种特定格式的数据行(可能与三角形面相关,比如.obj文件中的面数据格式)
1528 if (line[0] == 'f')
1529 {
1530// 创建一个Triangle类型的对象t,用于存储三角形相关信息(Triangle类型应该是自定义的结构体或类,具体成员表示三角形的顶点等信息)
1531 Triangle t;
1532/ 用于标记三角形数据是否读取正确的布尔变量,初始化为false
1533 bool tri_ok = false;
1534// 用于标记是否包含UV坐标信息的布尔变量,初始化为false
1535 bool has_uv = false;
1536// 尝试按照"f %d %d %d"的格式从line中读取3个整数到integers数组中,若成功读取到3个整数(返回值为3),则表示三角形数据读取正确,将tri_ok置为true
1537 if (sscanf(line, "f %d %d %d",
1538 &integers[0], &integers[1], &integers[2]) == 3)
1539 {
1540 tri_ok = true;
1541 }
1542// 尝试按照"f %d// %d// %d//"的格式从line中读取3个整数到integers数组中,若成功读取到3个整数(返回值为3),则表示三角形数据读取正确,将tri_ok置为true
1543 else if (sscanf(line, "f %d// %d// %d//",
1544 &integers[0], &integers[1], &integers[2]) == 3)
1545 {
1546 tri_ok = true;
1547 }
1548// 尝试按照"f %d//%d %d//%d %d//%d"的格式从line中读取6个整数到integers数组中,若成功读取到6个整数(返回值为6),则表示三角形数据读取正确,将tri_ok置为true
1549 else if (sscanf(line, "f %d//%d %d//%d %d//%d",
1550 &integers[0], &integers[3],
1551 &integers[1], &integers[4],
1552 &integers[2], &integers[5]) == 6)
1553 {
1554 tri_ok = true;
1555 }
1556 else if (sscanf(line, "f %d/%d/%d %d/%d/%d %d/%d/%d",
1557 &integers[0], &integers[6], &integers[3],
1558 &integers[1], &integers[7], &integers[4],
1559 &integers[2], &integers[8], &integers[5]) == 9)
1560 {
1561 tri_ok = true;
1562 has_uv = true;
1563 }
1564 else // Add Support for v/vt only meshes
1565 if (sscanf(line, "f %d/%d %d/%d %d/%d",
1566 &integers[0], &integers[6],
1567 &integers[1], &integers[7],
1568 &integers[2], &integers[8]) == 6)
1569 {
1570 tri_ok = true;
1571 has_uv = true;
1572 }
1573 else
1574 {
1575 printf("unrecognized sequence\n");
1576 printf("%s\n", line);
1577 while (1)
1578 ;
1579 }
1580// 如果三角形数据读取正确(tri_ok为true),进行以下处理
1581 if (tri_ok)
1582 {
1583 t.v[0] = integers[0] - 1 - vertex_cnt;
1584 t.v[1] = integers[1] - 1 - vertex_cnt;
1585 t.v[2] = integers[2] - 1 - vertex_cnt;
1586 t.attr = 0;
1587// 如果允许处理UV坐标(process_uv为true)且确实有UV坐标(has_uv为true),进行以下操作
1588 if (process_uv && has_uv)
1589 {
1590// 创建一个整型的动态数组indices,用于存储UV坐标相关的索引信息
1591 std::vector<int> indices;
1592 indices.push_back(integers[6] - 1 - vertex_cnt);
1593 indices.push_back(integers[7] - 1 - vertex_cnt);
1594 indices.push_back(integers[8] - 1 - vertex_cnt);
1595// 将indices数组添加到uvMap中,uvMap应该是一个存储UV坐标索引信息的容器
1596 uvMap.push_back(indices);
1597 t.attr |= TEXCOORD;
1598 }
1599// 将当前使用的材质赋值给三角形对象t的material成员,material应该是表示材质相关信息的变量
1600 t.material = material;
1601 // geo.triangles.push_back ( tri );
1602 triangles.push_back(t);
1603 // state_before = state;
1604 // state ='f';
1605 }
1606 }
1607 }
1608// 如果允许处理UV坐标(process_uv为true)且uvs容器中有元素(表示有UV坐标数据)
1609 if (process_uv && uvs.size())
1610 {
1611 loopi(0, triangles.size())
1612 {
1613// 内层循环遍历当前三角形的每个顶点(通常三角形有3个顶点),j表示当前顶点的索引
1614 loopj(0, 3)
1615 triangles[i]
1616 .uvs[j] = uvs[uvMap[i][j]];
1617 }
1618 }
1619// 关闭文件指针fn指向的文件,fn应该是之前打开的用于读取数据的文件指针
1620 fclose(fn);
1621
1622 // printf("load_obj: vertices = %lu, triangles = %lu, uvs = %lu\n", vertices.size(), triangles.size(), uvs.size() );
1623 } // load_obj()
1624
1625 // Optional : Store as OBJ
1626// 将数据以OBJ文件格式进行存储的函数,filename为要存储的文件名
1627 void write_obj(const char *filename)
1628 {
1629// 以写入模式打开指定文件名的文件,如果打开失败返回NULL,file为文件指针
1630 FILE *file = fopen(filename, "w");
1631// 用于记录当前材质的索引,初始化为-1
1632 int cur_material = -1;
1633 bool has_uv = (triangles.size() && (triangles[0].attr & TEXCOORD) == TEXCOORD);
1634// 如果文件打开失败,输出错误信息并退出程序
1635 if (!file)
1636 {
1637 printf("write_obj: can't write data file \"%s\".\n", filename);
1638 exit(0);
1639 }
1640 if (!mtllib.empty())
1641 {
1642 fprintf(file, "mtllib %s\n", mtllib.c_str());
1643 }
1644 loopi(0, vertices.size())
1645 {
1646 // fprintf(file, "v %lf %lf %lf\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z);
1647 fprintf(file, "v %g %g %g\n", vertices[i].p.x, vertices[i].p.y, vertices[i].p.z); // more compact: remove trailing zeros
1648 }
1649//如果有UV坐标
1650 if (has_uv)
1651 {
1652//写入UV坐标到文件
1653 loopi(0, triangles.size()) if (!triangles[i].deleted)
1654 {
1655 fprintf(file, "vt %g %g\n", triangles[i].uvs[0].x, triangles[i].uvs[0].y);
1656 fprintf(file, "vt %g %g\n", triangles[i].uvs[1].x, triangles[i].uvs[1].y);
1657 fprintf(file, "vt %g %g\n", triangles[i].uvs[2].x, triangles[i].uvs[2].y);
1658 }
1659 }
1660//初始化索引为1
1661 int uv = 1;
1662//如果有UV坐标且三角形未被删除
1663 loopi(0, triangles.size()) if (!triangles[i].deleted)
1664 {
1665 if (has_uv)
1666 {
1667 fprintf(file, "f %d/%d %d/%d %d/%d\n", triangles[i].v[0] + 1, uv, triangles[i].v[1] + 1, uv + 1, triangles[i].v[2] + 1, uv + 2);
1668 uv += 3;
1669 }
1670 else
1671 {
1672 fprintf(file, "f %d %d %d\n", triangles[i].v[0] + 1, triangles[i].v[1] + 1, triangles[i].v[2] + 1);
1673 }
1674 // fprintf(file, "f %d// %d// %d//\n", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1); //more compact: remove trailing zeros
1675 }
1676 fclose(file);
1677 }
1678 };
1679
1680}
1681///////////////////////////////////////////
auto end() const noexcept
double min(double v1, double v2)
返回两个数中的较小值
Definition Simplify.h:591
#define loopk(start_l, end_l)
定义一个循环宏,使用变量k从start_l迭代到end_l(不包括end_l)。
Definition Simplify.h:116
vec3f interpolate(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c, const vec3f attrs[3])
根据重心坐标插值属性
Definition Simplify.h:575
#define loopj(start_l, end_l)
定义一个循环宏,使用变量j从start_l迭代到end_l(不包括end_l)。
Definition Simplify.h:108
#define loopi(start_l, end_l)
标准输入输出流库
Definition Simplify.h:99
vec3f barycentric(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c)
计算点p相对于三角形abc的重心坐标
Definition Simplify.h:546
用于简化网格的类。
Definition Simplify.h:829
void simplify_mesh_lossless(bool verbose=false)
无损简化网格
Definition Simplify.h:978
void write_obj(const char *filename)
Definition Simplify.h:1627
void update_mesh(int iteration)
更新网格:压缩三角形,并建立引用列表
Definition Simplify.h:1201
double calculate_error(int id_v1, int id_v2, vec3f &p_result)
Definition Simplify.h:1364
std::string mtllib
材质库的名称。
Definition Simplify.h:846
bool flipped(vec3f p, int i0, int i1, Vertex &v0, Vertex &v1, std::vector< int > &deleted)
检查移除指定边后三角形是否会翻转
Definition Simplify.h:1090
std::vector< std::string > materials
存储材质名称的向量。
Definition Simplify.h:850
std::vector< Triangle > triangles
存储三角形的向量。
Definition Simplify.h:834
std::vector< Vertex > vertices
存储顶点的向量。
Definition Simplify.h:838
void update_uvs(int i0, const Vertex &v, const vec3f &p, std::vector< int > &deleted)
更新UV坐标
Definition Simplify.h:1136
char * trimwhitespace(char *str)
Definition Simplify.h:1405
std::vector< Ref > refs
存储引用的向量。
Definition Simplify.h:842
void load_obj(const char *filename, bool process_uv=false)
Definition Simplify.h:1431
void simplify_mesh(int target_count, double agressiveness=7, bool verbose=false)
主简化函数。
Definition Simplify.h:859
void update_triangles(int i0, Vertex &v, std::vector< int > &deleted, int &deleted_triangles)
更新三角形连接和边误差
Definition Simplify.h:1165
double vertex_error(SymetricMatrix q, double x, double y, double z)
Definition Simplify.h:1358
对称矩阵类
Definition Simplify.h:601
SymetricMatrix & operator+=(const SymetricMatrix &n)
矩阵加法赋值运算符重载
Definition Simplify.h:703
SymetricMatrix(double a, double b, double c, double d)
使用平面方程的参数构造对称矩阵
Definition Simplify.h:646
double m[10]
存储矩阵元素的数组
Definition Simplify.h:722
double det(int a11, int a12, int a13, int a21, int a22, int a23, int a31, int a32, int a33)
计算矩阵的子行列式
Definition Simplify.h:677
const SymetricMatrix operator+(const SymetricMatrix &n) const
矩阵加法运算符重载
Definition Simplify.h:690
SymetricMatrix(double c=0)
构造函数,使用默认值初始化所有元素
Definition Simplify.h:609
SymetricMatrix(double m11, double m12, double m13, double m14, double m22, double m23, double m24, double m33, double m34, double m44)
构造函数,使用给定的值初始化矩阵
Definition Simplify.h:618
double operator[](int c) const
访问矩阵元素
Definition Simplify.h:665
命名空间,用于简化网格模型的工具和数据结构。
Attributes
枚举类型,表示三角形的属性。
Definition Simplify.h:738
@ TEXCOORD
包含纹理坐标属性。
Definition Simplify.h:750
@ NONE
无属性。
Definition Simplify.h:742
@ NORMAL
普通属性。
Definition Simplify.h:746
@ COLOR
包含颜色属性。
Definition Simplify.h:754
static double fn[10]
Definition odrSpiral.cpp:86
表示三角形和顶点之间的引用的数据结构。
Definition Simplify.h:818
int tid
三角形的索引和三角形中引用的顶点索引。
Definition Simplify.h:821
表示三角形的数据结构。
Definition Simplify.h:762
double err[4]
误差值数组,用于简化算法。
Definition Simplify.h:770
int deleted
是否被删除的标志、是否需要重新计算的标志和三角形的属性。
Definition Simplify.h:774
int v[3]
三角形的三个顶点索引。
Definition Simplify.h:766
vec3f uvs[3]
三个顶点的纹理坐标。
Definition Simplify.h:782
int material
材质索引。
Definition Simplify.h:786
vec3f n
法向量。
Definition Simplify.h:778
表示顶点的数据结构。
Definition Simplify.h:794
vec3f p
顶点位置。
Definition Simplify.h:798
int border
是否为边界顶点的标志。
Definition Simplify.h:810
int tstart
第一个引用此顶点的三角形的索引和引用此顶点的三角形数量。
Definition Simplify.h:802
SymetricMatrix q
对称矩阵,用于存储顶点信息。
Definition Simplify.h:806
一个包含三维浮点数坐标的向量结构体,提供了一系列向量运算。
Definition Simplify.h:133
double y
Definition Simplify.h:135
vec3f rot_y(double a)
绕Y轴旋转当前向量
Definition Simplify.h:344
vec3f random01_fxyz()
将当前向量的每个分量设置为[0, 1)范围内的随机数
Definition Simplify.h:526
vec3f operator/(const double a) const
向量与标量除法运算符重载,计算当前对象与给定标量相除的结果。
Definition Simplify.h:242
static int random_number
静态成员变量:随机数生成器使用的随机数
Definition Simplify.h:504
vec3f rot_x(double a)
绕X轴旋转当前向量
Definition Simplify.h:328
vec3f frac()
获取当前向量的分数部分
Definition Simplify.h:412
vec3f operator*(const double a) const
Definition Simplify.h:176
void clamp(double min, double max)
将当前向量的分量限制在最小值和最大值之间
Definition Simplify.h:360
vec3f operator=(const vec3f a)
赋值运算符重载,用于将另一个vec3f对象的值赋给当前对象。
Definition Simplify.h:209
static double random_double()
静态方法:生成一个随机双精度浮点数
vec3f operator*(const vec3f a) const
Definition Simplify.h:181
vec3f(void)
默认构造函数。
Definition Simplify.h:139
static void random_init()
静态方法:初始化随机数生成器
double length() const
获取当前向量的长度
Definition Simplify.h:440
vec3f integer()
获取当前向量的整数部分
Definition Simplify.h:426
double angle2(const vec3f &v, const vec3f &w)
计算两个向量之间的角度,考虑第三个向量定义的平面
Definition Simplify.h:304
vec3f invert()
获取当前向量的相反向量
Definition Simplify.h:398
double dot(const vec3f &a) const
计算当前对象与另一个vec3f对象的点积。
Definition Simplify.h:252
vec3f(vector3 a)
拷贝构造函数,从另一个vector3对象构造。
Definition Simplify.h:146
vec3f operator/(const vec3f a) const
向量除法运算符重载,计算当前对象与另一个vec3f对象对应分量相除的结果。
Definition Simplify.h:222
double z
Definition Simplify.h:135
vec3f normalize(double desired_length=1)
将当前向量归一化
Definition Simplify.h:452
vec3f operator=(const vector3 a)
赋值运算符重载,从另一个vec3f对象赋值。
Definition Simplify.h:196
vec3f rot_z(double a)
绕Z轴旋转当前向量
Definition Simplify.h:383
static vec3f normalize(vec3f a)
静态方法:归一化一个向量
vec3f cross(const vec3f &a, const vec3f &b)
计算两个三维向量的叉积
Definition Simplify.h:265
double angle(const vec3f &v)
计算当前向量与另一个向量之间的角度
Definition Simplify.h:280
vec3f operator-(const vec3f &a) const
向量减法运算符重载,计算当前对象与另一个vec3f对象对应分量相减的结果。
Definition Simplify.h:232
double random_double_01(double a)
生成一个[0, 1)范围内的随机双精度浮点数,基于给定的输入进行变换
Definition Simplify.h:513
vec3f operator+(const vec3f &a) const
Definition Simplify.h:166
vec3f(const double X, const double Y, const double Z)
参数化构造函数,从三个浮点数构造。
Definition Simplify.h:159
static vec3f random()
静态方法:生成一个随机三维向量
double x
Definition Simplify.h:135
vec3f v3() const
Definition Simplify.h:186
vec3f operator+=(const vec3f &a) const
Definition Simplify.h:171
一个简单的三维向量结构体,包含x、y、z三个坐标。
Definition Simplify.h:123
double y
Definition Simplify.h:124
double z
Definition Simplify.h:124
double x
Definition Simplify.h:124