CC(Catmull–Clark) 细分过程分析
“ 图片借鉴博客:
https://blog.csdn.net/McQueen_LT/article/details/106102609 ”
CC 细分 ——> 四边形网格细化
" 主要为后面实现BMesh中的体细分做一个准备 "
"""
WIKI百科上CC细分的过程:
Catmull–Clark surfaces are defined recursively, using the following refinement scheme:
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) [ 与之相连的边的中点,而不是之前求出来的边点edge point ] . (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
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
at extraordinary vertices and
everywhere else (when n indicates how many derivatives are continuous, we speak of
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.
"""
首先输入的原始模型为一个简单的立方体,其中每个面由四个点组成,input_points中第i个向量表示点V_i 。且定义形成一个面的四个点的顺序为顺时针记录。
第一步:得到面点,每一个面点为该面所有点的平均
第二步:得到边点
第三步:计算平均面点 (第三、四、五步都是针对某个原始点来操作 ——> 来更新原始点)
对于某一个原始点P,与之相连的面的面点(假设有3个面,f1,f2,f3 ,且面点分别为 fp1,fp2,fp3)
则 avg_fp 的计算公式为 :
第四步:计算平均边点
对于某一个原始点P,与之相连的边的边的中点(假设有3条e1,e2,e3,且边的中点分别为em1,em2,em3)则avg_emp的计算公式为 :
第五步:利用 P' 更新原始坐标P即可:
若按照上诉的公式,则n 取 3,得:
对于全部的原始点进行遍历来更新P
第六步 :
插入新增点:第一步和第二步中得到的面点fp和边点ep,对于一个面上的面点fp连接这个面上产生的四个边点ep。以下图为例——原始图(V8,V3,V2,V1)转化为了四个面f1,f2,f3,f4,其中:
f1 = (V8,ep1,fp,ep2)
其他三个面同理
结果展示:
主要代码分析:
def get_edges_faces(input_points, input_faces):
"""
一条边对应两个不同的面
得到 边的集合,因为在之后的操作中会有求每条边的中点,所以在构造时直接加上
-> E = [e1,e2,e3,e4,...,en] , ei = [p_begin,p_end,face_i,face_j,center]
主要思想 : 首先遍历每个面,并从中提取边
"""
edges = []
'''
从每个面中获得边
对于一个面 f(v1,v2,v3,v4)
得到边 -> (v1,v2), (v2,v3), (v3,v4), (v1,v4) 始终保持vi>vj
以 边(v1,v2) 为例 -> 存储格式为 (v1,v2,f,_,(v1+v2)/2)
'''
for facenum in range(len(input_faces)):
# 遍历每个面 和 面 中的点
face = input_faces[facenum]
num_points = len(face)
for pointindex in range(num_points):
if pointindex < num_points - 1:
pointnum_1 = face[pointindex]
pointnum_2 = face[pointindex + 1]
else:
pointnum_1 = face[pointindex]
pointnum_2 = face[0]
if pointnum_1 > pointnum_2:
temp = pointnum_1
pointnum_1 = pointnum_2
pointnum_2 = temp
edges.append([pointnum_1, pointnum_2, facenum])
"""
对于现在边的集合进行排序 -> 为了合并相同边不同面的边
"""
edges = sorted(edges)
num_edges = len(edges)
eindex = 0
merged_edges = []
"""
对排序后的边集机进行排序,因为排序后的边,相同边的对应两个面将会相邻
"""
while eindex < num_edges:
e1 = edges[eindex]
# 第一条边
if eindex < num_edges - 1:
e2 = edges[eindex + 1]
# 第二条边
if e1[0] == e2[0] and e1[1] == e2[1]:
merged_edges.append([e1[0], e1[1], e1[2], e2[2]])
eindex += 2
else:
merged_edges.append([e1[0], e1[1], e1[2], None])
eindex += 1
else:
merged_edges.append([e1[0], e1[1], e1[2], None])
eindex += 1
# add edge centers
edges_centers = []
for me in merged_edges:
p1 = input_points[me[0]]
p2 = input_points[me[1]]
cp = center_point(p1, p2)
edges_centers.append(me + [cp])
return edges_centers
# 得到 平均面点 与 平均边中点
def get_avg_face_points(input_points, input_faces, face_points):
"""
求平均面点
对于每一个点,求它所在边的面的面点和的平均
ex. (x,y,z) ∈ (f1,f2,f3)
则 (x,y,z) 的平均面点为 (fp1+fp2+fp3)/3;
计算思想:
创建一个列表((fpx,fpy,fpz),n)
fp_{x,y,z} 为面点和 的x,y,z维度上的和,n表示有多少个面点
对于每一个面进行遍历,将在这个面上的点对应的 ((fpx,fpy,fpz),n) 进行修改和更新即可
最后(fpx,fpy,fpz) 除 n 即可
"""
num_points = len(input_points)
temp_points = []
for pointnum in range(num_points):
temp_points.append([[0.0, 0.0, 0.0], 0])
for facenum in range(len(input_faces)):
fp = face_points[facenum]
for pointnum in input_faces[facenum]:
tp = temp_points[pointnum][0]
temp_points[pointnum][0] = sum_point(tp, fp)
temp_points[pointnum][1] += 1
#以上统计每个点属于多少个面,并将每个面点求和,在接下来求平均
avg_face_points = []
for tp in temp_points:
afp = div_point(tp[0], tp[1])
avg_face_points.append(afp)
return avg_face_points
def get_avg_mid_edges(input_points, edges_faces):
"""
与求平均面点相类似
创建一个集合 ((emx,emy,emz),n)
(emx,emy,emz) 为与之相连的边的中点坐标和,n为多少个这样的点
对于之前求出来的边集,遍历边集,对每条边对应的中点加到对应的点上即可
最后(emx,emy,emz) 除 n 即可
"""
num_points = len(input_points)
temp_points = []
for pointnum in range(num_points):
temp_points.append([[0.0, 0.0, 0.0], 0])
for edge in edges_faces:
cp = edge[4]
for pointnum in [edge[0], edge[1]]:
tp = temp_points[pointnum][0]
temp_points[pointnum][0] = sum_point(tp, cp)
temp_points[pointnum][1] += 1
avg_mid_edges = []
for tp in temp_points:
ame = div_point(tp[0], tp[1])
avg_mid_edges.append(ame)
return avg_mid_edges
def cmc_subdiv(input_points, input_faces):
"""
"CC细分" 流程
得到面点->得到边与面的集合->得到边点->计算平均面点和平均边点->计算每个点对应多少个面(计算n)
-> 利用公式更新原始点
"""
face_points = get_face_points(input_points, input_faces)
edges_faces = get_edges_faces(input_points, input_faces)
edge_points = get_edge_points(input_points, edges_faces, face_points)
avg_face_points = get_avg_face_points(input_points, input_faces, face_points)
avg_mid_edges = get_avg_mid_edges(input_points, edges_faces)
points_faces = get_points_faces(input_points, input_faces)
"""
m1 = (n - 3) / n
m2 = 1 / n
m3 = 2 / n
new_coords = (m1 * old_coords)
+ (m2 * avg_face_points)
+ (m3 * avg_mid_edges)
"""
new_points = get_new_points(input_points, points_faces, avg_face_points, avg_mid_edges)
"""
Then each face is replaced by new faces made with the new points,
对于三角形面 (a,b,c):
(a, edge_point ab, face_point abc, edge_point ca)
(b, edge_point bc, face_point abc, edge_point ab)
(c, edge_point ca, face_point abc, edge_point bc)
对于四边形面 (a,b,c,d):
(a, edge_point ab, face_point abcd, edge_point da)
(b, edge_point bc, face_point abcd, edge_point ab)
(c, edge_point cd, face_point abcd, edge_point bc)
(d, edge_point da, face_point abcd, edge_point cd)
面点和边点都是直接可以进行直接访问
-> face_points 和 edge_points
并将面点和边点加入新的点集中
并产生两个新的结构体:
face_point_nums -> 每个面点的序号列表
edge_point_nums -> 每条边的 边点的序号
"""
# 标记每个面点的序号
face_point_nums = []
# next_pointnum -> 下一个插入点集中的点的序号
next_pointnum = len(new_points)
for face_point in face_points:
new_points.append(face_point)
face_point_nums.append(next_pointnum)
next_pointnum += 1
# 标记每个边点的序号
edge_point_nums = dict()
for edgenum in range(len(edges_faces)):
pointnum_1 = edges_faces[edgenum][0]
pointnum_2 = edges_faces[edgenum][1]
edge_point = edge_points[edgenum]
new_points.append(edge_point)
edge_point_nums[(pointnum_1, pointnum_2)] = next_pointnum
next_pointnum += 1
"""
计算新面:
for a quad face (a,b,c,d):
(a, edge_point ab, face_point abcd, edge_point da)
(b, edge_point bc, face_point abcd, edge_point ab)
(c, edge_point cd, face_point abcd, edge_point bc)
(d, edge_point da, face_point abcd, edge_point cd)
new_faces will be a list of lists where the elements are like this:
[pointnum_1, pointnum_2, pointnum_3, pointnum_4]
"""
new_faces = []
# 每一个旧面进行细分,细分成多个面
for oldfacenum in range(len(input_faces)):
oldface = input_faces[oldfacenum]
# 4 point face
if len(oldface) == 4:
a = oldface[0]
b = oldface[1]
c = oldface[2]
d = oldface[3]
face_point_abcd = face_point_nums[oldfacenum]
edge_point_ab = edge_point_nums[switch_nums((a, b))]
edge_point_da = edge_point_nums[switch_nums((d, a))]
edge_point_bc = edge_point_nums[switch_nums((b, c))]
edge_point_cd = edge_point_nums[switch_nums((c, d))]
new_faces.append((a, edge_point_ab, face_point_abcd, edge_point_da))
new_faces.append((b, edge_point_bc, face_point_abcd, edge_point_ab))
new_faces.append((c, edge_point_cd, face_point_abcd, edge_point_bc))
new_faces.append((d, edge_point_da, face_point_abcd, edge_point_cd))
return new_points, new_faces