简介

pixel的大佬,就是cc细分论文的作者。

wiki的步骤是我见过的比较清晰的版本

Recursive evaluation

Catmull–Clark surfaces are defined recursively, using the following refinement scheme:[1]

Start with a mesh of an arbitrary polyhedron. All the vertices in this mesh shall be called original points.

For each face, add a face point

Set each face point to be the average of all original points for the respective face.

For each edge, add an edge point.

Set each edge point to be the average of the two neighbouring face points and its two original endpoints.

For each face point, add an edge for every edge of the face, connecting the face point to each edge point for the face.

For each original point P, take the average F of all n (recently created) face points for faces touching P, and take the average R of all n edge midpoints for (original) edges touching P, where each edge midpoint is the average of its two endpoint vertices (not to be confused with new "edge points" above). (Note that from the perspective of a vertex P, the number of edges neighboring P is also the number of adjacent faces, hence n). Move each original point to the point

\({\displaystyle {\frac {F+2R+(n-3)P}{n}}}{\displaystyle {\frac {F+2R+(n-3)P}{n}}}\)

This is the barycenter of P, R and F with respective weights (n − 3), 2 and 1.

Connect each new face point to the new edge points of all original edges defining the original face.

Connect each new vertex point to the new edge points of all original edges incident on the original vertex.

Define new faces as enclosed by edges.

The new mesh will consist only of quadrilaterals, which in general will not be planar. The new mesh will generally look smoother than the old mesh.

Repeated subdivision results in smoother meshes. It can be shown that the limit surface obtained by this refinement process is at least \({\displaystyle {\mathcal {C}}^{1}}\mathcal{C}^1\) at extraordinary vertices and \({\displaystyle {\mathcal {C}}^{2}}{\mathcal {C}}^{2}\) everywhere else (when n indicates how many derivatives are continuous, we speak of \({\displaystyle {\mathcal {C}}^{n}}\mathcal{C}^n\) continuity). After one iteration, the number of extraordinary points on the surface remains constant.

The arbitrary-looking barycenter formula was chosen by Catmull and Clark based on the aesthetic appearance of the resulting surfaces rather than on a mathematical derivation, although Catmull and Clark do go to great lengths to rigorously show that the method converges to bicubic B-spline surfaces.[1]

参考

链接代码

http://rosettacode.org/wiki/Catmull–Clark_subdivision_surface#Python

https://blog.csdn.net/McQueen_LT/article/details/106102609

code

虽然 rosettacode 提供了 各种类型的细分代码,但是 个人觉得 都不是特别让人容易懂,使用openmesh重写了部分代码,结果很舒适。暂时有点缺憾,不能处理不封闭的网格。

#ifndef __CC_HPP__
#define __CC_HPP__
#include <OpenMesh/Core/Mesh/PolyMesh_ArrayKernelT.hh>
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <map>
typedef OpenMesh::PolyMesh_ArrayKernelT<> MyMesh; typedef struct FFACE{
int faceId;
int edgeId1;
int vertexId;
int edgeId2;
}FFACE; class CC{
private:
MyMesh mesh;
std::map<int, OpenMesh::Vec3d> facePoints;
std::map<int, OpenMesh::Vec3d> edgePoints;
std::map<int, OpenMesh::Vec3d> vertexPoints;
int times;
public:
CC(int _times);
~CC();
void genCube();
void genFacePoint();
void genEdgePoint();
void genVertexPoint();
void connectPoints();
void genMesh(std::string name);
int pipeLine();
};
#endif
/*
* @Author: your name
* @Date: 2020-09-02 13:49:22
* @LastEditTime: 2020-09-03 14:35:38
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: /cc/cc.cc
*/ #include "cc.hpp"
#include <iostream>
#include <unordered_map> /**
* @description: 构造函数
* @param {type}
* @return {type}
*/
CC::CC(int _times):times(_times){
genCube();
} /**
* @description: 析构函数
* @param {type}
* @return {type}
*/
CC::~CC() {} /**
* @description: 一整套流程
* @param {type}
* @return {int} 管线运行是否成功
*/
int CC::pipeLine(){
for(int i=0; i<times; i++){
genFacePoint();
genEdgePoint();
genVertexPoint();
connectPoints();
char a[20];
sprintf(a, "output%d.off", i);
genMesh(a);
// 清空变量
std::cout << "[DEBUG] 迭代了第 " << i << " 次。" << std::endl;
} return 0;
} /**
* @description: 生成一个立方体(四边形网格)
* @param {type}
* @return {type}
*/
void CC::genCube()
{
MyMesh::VertexHandle vhandle[9];
vhandle[0] = mesh.add_vertex(MyMesh::Point(-1, -1, 1));
vhandle[1] = mesh.add_vertex(MyMesh::Point(1, -1, 1));
vhandle[2] = mesh.add_vertex(MyMesh::Point(1, 1, 1));
vhandle[3] = mesh.add_vertex(MyMesh::Point(-1, 1, 1));
vhandle[4] = mesh.add_vertex(MyMesh::Point(-1, -1, -1));
vhandle[5] = mesh.add_vertex(MyMesh::Point(1, -1, -1));
vhandle[6] = mesh.add_vertex(MyMesh::Point(1, 1, -1));
vhandle[7] = mesh.add_vertex(MyMesh::Point(-1, 1, -1)); std::vector<MyMesh::VertexHandle> face_vhandles;
face_vhandles.push_back(vhandle[0]);
face_vhandles.push_back(vhandle[1]);
face_vhandles.push_back(vhandle[2]);
face_vhandles.push_back(vhandle[3]);
mesh.add_face(face_vhandles); face_vhandles.clear();
face_vhandles.push_back(vhandle[7]);
face_vhandles.push_back(vhandle[6]);
face_vhandles.push_back(vhandle[5]);
face_vhandles.push_back(vhandle[4]);
mesh.add_face(face_vhandles);
face_vhandles.clear();
face_vhandles.push_back(vhandle[1]);
face_vhandles.push_back(vhandle[0]);
face_vhandles.push_back(vhandle[4]);
face_vhandles.push_back(vhandle[5]);
mesh.add_face(face_vhandles);
face_vhandles.clear();
face_vhandles.push_back(vhandle[2]);
face_vhandles.push_back(vhandle[1]);
face_vhandles.push_back(vhandle[5]);
face_vhandles.push_back(vhandle[6]);
mesh.add_face(face_vhandles);
face_vhandles.clear();
face_vhandles.push_back(vhandle[3]);
face_vhandles.push_back(vhandle[2]);
face_vhandles.push_back(vhandle[6]);
face_vhandles.push_back(vhandle[7]);
mesh.add_face(face_vhandles);
face_vhandles.clear();
face_vhandles.push_back(vhandle[0]);
face_vhandles.push_back(vhandle[3]);
face_vhandles.push_back(vhandle[7]);
face_vhandles.push_back(vhandle[4]);
mesh.add_face(face_vhandles);
} /**
* @description: 生成所有面点
* @param {type}
* @return {type}
*/
void CC::genFacePoint()
{
facePoints.clear();
for (const auto &fh : mesh.faces())
{
OpenMesh::Vec3d facePoint(0, 0, 0);
int facePointsNumber = 0;
for (const auto &fvh : mesh.fv_range(fh))
{
OpenMesh::DefaultTraits::Point point = mesh.point(fvh);
facePoint += point;
facePointsNumber++;
}
facePoint /= facePointsNumber;
facePoints[fh.idx()] = facePoint;
}
} /**
* @description: 生成所有的边点暂时不考虑hole 就是全是日子结构的
* @param {type}
* @return {type}
*/
void CC::genEdgePoint()
{
edgePoints.clear();
for (auto e_it = mesh.edges_begin(); e_it != mesh.edges_end(); ++e_it)
{
// 得到边所代表的半边
OpenMesh::HalfedgeHandle heh1 = mesh.halfedge_handle(*e_it, 0); // 默认一个方向的半边
OpenMesh::Vec3d edgePoint(0, 0, 0);
int edgePointsNumber = 0;
OpenMesh::DefaultTraits::Point pointV = mesh.point(mesh.from_vertex_handle(heh1)); // 这条(半)边的起点
OpenMesh::DefaultTraits::Point pointW = mesh.point(mesh.to_vertex_handle(heh1)); // 这条(半)边的终点
OpenMesh::FaceHandle fh1 = mesh.face_handle(heh1);
OpenMesh::Vec3d fh1p = facePoints[fh1.idx()]; // 得到面点
OpenMesh::HalfedgeHandle heh = mesh.opposite_halfedge_handle(heh1); // 逆半边
OpenMesh::FaceHandle fh2 = mesh.face_handle(heh);
OpenMesh::Vec3d fh2p = facePoints[fh2.idx()]; // 得到面点
edgePoints[heh1.idx()] = (fh1p + fh2p + pointV + pointW) / 4.0;
}
} /**
* @description: 生成新的顶点
* For each original point P, take the average F of all n (recently created) face points for faces touching P,
* and take the average R of all n edge midpoints for (original) edges touching P,
* where each edge midpoint is the average of its two endpoint vertices (not to be confused with new "edge points" above).
* (Note that from the perspective of a vertex P, the number of edges neighboring P is also the number of adjacent faces, hence n).
* Move each original point to the point
* @param {type}
* @return {type}
*/
void CC::genVertexPoint()
{
vertexPoints.clear();
// 原始点接触的面的所有的面点的均值
for (auto v_it = mesh.vertices_begin(); v_it != mesh.vertices_end(); v_it++)
{
OpenMesh::Vec3d originPoint = (OpenMesh::Vec3d)mesh.point(*v_it);
OpenMesh::Vec3d facePoint(0, 0, 0);
int faceNumber = 0;
for (auto vf_it = mesh.vf_iter(*v_it); vf_it.is_valid(); ++vf_it)
{ //这个顶点所带有的面迭代器
facePoint += facePoints[(*vf_it).idx()];
faceNumber++;
}
facePoint /= faceNumber; // 原始点接触的边的中间点的值的均值 * 2
OpenMesh::Vec3d edgePoint(0, 0, 0);
int edgeNumber = 0;
for(auto vv_it = mesh.vv_begin(*v_it); vv_it != mesh.vv_end(*v_it); vv_it++){ // 为啥还有孤立点?
OpenMesh::Vec3d point =(OpenMesh::Vec3d)mesh.point(*vv_it);
edgePoint += (point + originPoint) / 2;
edgeNumber++;
}
if(faceNumber != edgeNumber){
std::cout << "[debug] is not equal f " << faceNumber << " e " << edgeNumber << std::endl;
}
std::cout << "[DEBUG] n " << edgeNumber << std::endl;
edgePoint /= edgeNumber; OpenMesh::Vec3d newPoint = (edgeNumber - 3.0) / (edgeNumber) * originPoint + facePoint * 1.0 / edgeNumber + 2.0 * edgePoint / edgeNumber;
std::cout << "[DEBUG] oldPoint " << originPoint << " newPoint " << newPoint << std::endl;
vertexPoints[(*v_it).idx()] = newPoint;
}
} /**
* @description: 连接面点和边点 (要严格的封闭的四边形)
* @param {type}
* @return {type}
*/
void CC::connectPoints(){
if (!mesh.has_vertex_status()) mesh.request_vertex_status();
if (!mesh.has_face_status()) mesh.request_face_status();
if (!mesh.has_edge_status()) mesh.request_edge_status();
// 先将旧顶点移动到新顶点
std::unordered_map<int, OpenMesh::VertexHandle> vertexHandle;
for (auto v_it = mesh.vertices_begin(); v_it != mesh.vertices_end(); v_it++){
mesh.set_point(*v_it, (OpenMesh::DefaultTraits::Point)vertexPoints[(*v_it).idx()]);
vertexHandle[(*v_it).idx()] = *v_it;
}
std::vector<MyMesh::VertexHandle> facePointsHandle;
std::vector<MyMesh::FaceHandle> faceHandle;
// 再加入所有的要加入的面点
for (const auto &fh : mesh.faces()) {
facePointsHandle.push_back(mesh.add_vertex(MyMesh::Point(facePoints[fh.idx()])));
faceHandle.push_back(fh);
}
std::unordered_map<int, MyMesh::VertexHandle> edgesHandle;
// 再加入所有的要加入的边点
for (const auto &eh : mesh.edges()){
edgesHandle[mesh.halfedge_handle(eh, 0).idx()] = mesh.add_vertex(MyMesh::Point(edgePoints[mesh.halfedge_handle(eh, 0).idx()]));
} std::vector<std::vector<MyMesh::VertexHandle>> v;
int i = 0;
for (const auto &fh : mesh.faces()){
std::vector<MyMesh::VertexHandle> handlerVector(9);
handlerVector[0] = facePointsHandle[i];
i++;
int pointsNumber = 1;
for (const auto &feh : mesh.fe_range(fh)){
auto fehh = mesh.halfedge_handle(feh, 0);
handlerVector[pointsNumber] = edgesHandle[fehh.idx()];
pointsNumber++;
}
for (const auto &fvh : mesh.fv_range(fh)){
handlerVector[pointsNumber] = vertexHandle[fvh.idx()];
pointsNumber++;
}
v.push_back(handlerVector);
}
// 开始删除面
// for(int i=0; i<faceHandle.size(); i++){
// mesh.delete_face(faceHandle[i], false);
// }
for(int i=0; i<v.size(); i++){
mesh.delete_face(faceHandle[i], true);
std::vector<MyMesh::VertexHandle> handlerVector;
for(int j=0; j<v[i].size(); j++){
handlerVector.push_back(v[i][j]);
}
std::vector<MyMesh::VertexHandle> face_vhandles;
face_vhandles.push_back(handlerVector[0]);
face_vhandles.push_back(handlerVector[1]);
face_vhandles.push_back(handlerVector[5]);
face_vhandles.push_back(handlerVector[2]);
mesh.add_face(face_vhandles);
face_vhandles.clear(); face_vhandles.push_back(handlerVector[0]);
face_vhandles.push_back(handlerVector[2]);
face_vhandles.push_back(handlerVector[6]);
face_vhandles.push_back(handlerVector[3]);
mesh.add_face(face_vhandles);
face_vhandles.clear(); face_vhandles.push_back(handlerVector[0]);
face_vhandles.push_back(handlerVector[3]);
face_vhandles.push_back(handlerVector[7]);
face_vhandles.push_back(handlerVector[4]);
mesh.add_face(face_vhandles);
face_vhandles.clear(); face_vhandles.push_back(handlerVector[0]);
face_vhandles.push_back(handlerVector[4]);
face_vhandles.push_back(handlerVector[8]);
face_vhandles.push_back(handlerVector[1]);
mesh.add_face(face_vhandles);
face_vhandles.clear();
} mesh.garbage_collection();
if (mesh.has_vertex_status()) mesh.release_vertex_status();
if (mesh.has_face_status()) mesh.release_face_status();
if (mesh.has_edge_status()) mesh.release_edge_status();
} /**
* @description: 输出新网格
* @param {string} name 文件名称 默认 output.obj
* @return {type}
*/
void CC::genMesh(std::string name){
if(name == ""){
name = "output.off";
}
try{
if( !OpenMesh::IO::write_mesh(mesh, name) ){
std::cerr << "Cannot write mesh to file 'output.off'" << std::endl;
return;
}
}
catch(std::exception &e){
std::cerr << e.what() << std::endl;
return;
}
}

image

附赠Makefile

LOCAL_LIBRARY +=  -L/home/ling/lee/lib/openmesh/static
LOCAL_LDFLAGS += -lm -lpthread -ldl -lOpenMeshCore -lOpenMeshTools
LOCAL_CFLAGS += -I/home/ling/lee/include -I./include -D OM_STATIC_BUILD CC := g++ -g
TARGETS1 = genCube
SRCS1 = mian1.cc cc.cc
OBJS1 = $(patsubst %.cc, %.o, $(SRCS1)) CFLAGS += $(LOCAL_CFLAGS)
LDFLAGS += $(LOCAL_LIBRARY) $(LOCAL_LDFLAGS) $(info $(OBJS))
$(info $(TARGETS)) all: $(TARGETS1) $(TARGETS1):$(OBJS1)
$(CC) -o $@ $^ $(LDFLAGS) $(CFLAGS) $(OBJS1): %.o:%.cc
$(CC) -c $< -o $@ $(CFLAGS) clean :
@rm -rf $(TARGETS1) $(OBJS1) #.SUFFIXES:
.PHONY : all clean

附赠 main.cc

/*
* @Author: your name
* @Date: 2020-09-02 09:25:40
* @LastEditTime: 2020-09-03 11:09:19
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: /cc/mian1.cc
*/
#include <iostream>
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Core/Mesh/PolyMesh_ArrayKernelT.hh>
#include "cc.hpp" int main(){
CC c(5);
c.pipeLine();
return 0;
}

cc 细分 网格细分的更多相关文章

  1. 关于3DSMAX中opensubdiv细分功能的笔记

    说到建模和细分,估计用过3dsmax的同学就会心有余悸,每次添加"涡轮平滑"或者"网格平滑"之前,都会下意识的进行保存,没有为啥,就是因为太容易使软件挂掉了. ...

  2. 三维网格细分算法(Catmull-Clark subdivision & Loop subdivision)附源码

    下图描述了细分的基本思想,每次细分都是在每条边上插入一个新的顶点,可以看到随着细分次数的增加,折线逐渐变成一条光滑的曲线.曲面细分需要有几何规则和拓扑规则,几何规则用于计算新顶点的位置,拓扑规则用于确 ...

  3. Unity3d 使用DX11的曲面细分

    Unity3d surface Shaderswith DX11 Tessellation Unity3d surface shader 在DX11上的曲面细分 I write this articl ...

  4. 三维网格细分算法(Catmull-Clark subdivision & Loop subdivision)附源码(转载)

    转载:  https://www.cnblogs.com/shushen/p/5251070.html 下图描述了细分的基本思想,每次细分都是在每条边上插入一个新的顶点,可以看到随着细分次数的增加,折 ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段 代码工程地址: https://github. ...

  6. DirectX11 With Windows SDK--33 曲面细分阶段(Tessellation)

    前言 曲面细分是Direct3D 11带来的其中一项重要的新功能.它引入了两个可编程着色器阶段以及一个固定的镶嵌处理过程.简单来说,曲面细分技术可以将几何体细分为更小的三角形,并以某种方式把这些新生成 ...

  7. C++实现网格水印之调试笔记(二)

    整理了一下要实现的论文Watermarking 3D Polygonal Meshes in the Mesh Spectral Domain,步骤如下: 嵌入水印 à 提取水印 à 优化(网格细分) ...

  8. CloudCompare 的简单的使用说明

    来自:https://blog.csdn.net/datase/article/details/79797795 File open:打开 save:保存 Global Shift settings: ...

  9. [转载]John Burkardt搜集的FORTRAN源代码

    Over the years, I have collected, modified, adapted, adopted or created a number of software package ...

  10. Unity3D 引擎基础 C# (数据结构入门) Unity3D 界面 UI(NGUI)(动画系统,导航系统)(委托与事件,常用设计模式)

    Geomagic Sculpt 2016.2 Windows Software 11个月前 (01-17) 0评论 Geomagic Sculpt 触觉式三维设计 触碰您的设计使用三维工具做三维设计. ...

随机推荐

  1. python,去掉“xa0”和“\r\n”

    爬小说网站,输出内容有时候会出现下图字符 首先,去掉"xa0" s = 'T-shirt\xa0\xa0短袖圆领衫,体恤衫\xa0' out = "".join ...

  2. Spring 中@Autowired,@Resource,@Inject 注解实现原理

    使用案例 前置条件: 现在有一个 Vehicle 接口,它有两个实现类 Bus 和 Car ,现在还有一个类 VehicleService 需要注入一个 Vehicle 类型的 Bean: publi ...

  3. 在使用import win32api时,报错:No module named win32api

    二.在使用import win32api时,报错:No module named win32api 网上查到有下面解决办法: pip install pypiwin32 或 pip3 install ...

  4. 拆解 Cursor Pro 自动化工具,看看它是怎么实现的?

    深入解析Cursor Pro自动化工具的核心实现 ‍ 从源码角度剖析关键技术 完整解读:注册.认证.机器码重置的自动化方案 项目概述 大家好,我是松哥.这篇文章将为大家详细解析一个Cursor自动化管 ...

  5. Java Solon-MCP 实现 MCP 实践全解析:SSE 与 STDIO 通信模式详解

    参文参考自:https://blog.csdn.net/lingding_cn/article/details/147355620 一.MCP简介 MCP(Model Context Protocol ...

  6. MongoDB从入门到实战之Windows快速安装MongoDB

    前言 本章节的主要内容是在 Windows 系统下快速安装 MongoDB 并使用 Navicat 工具快速连接. MongoDB从入门到实战之MongoDB简介 MongoDB从入门到实战之Mong ...

  7. useEffect的那些坑,你知道多少

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 14px; o ...

  8. FMEA方法,排除架构可用性隐患的利器

    极客时间:<从 0 开始学架构>:FMEA方法,排除架构可用性隐患的利器 FMEA 方法,就是保证我们做到全面分析的一个非常简单但是非常有效的方法. 1.FMEA 介绍 FMEA(Fail ...

  9. 简单说说C#中委托的使用-01

    简单说说C#中委托的使用-01 前言 距离上次更新文章,已经过去...月了. 没更新文章的主要原因,主要是因为参加工作后,感觉思维没有上学那会活跃,写文章没有思绪.再就是上班的时候把精力用光了,下班后 ...

  10. 入库出库查询软件——qt

    miniMes系统操作说明 一:功能说明 主界面有扫描--查询两个界面,扫描界面如下 1:默认开启自动入库出库功能 2:右上角限制位数可根据需求设定二维码字符串的位数,设置完成后点击设定,弹窗设定成功 ...