(二)模型简化(QEM)
**
(三)放缩、旋转、移动、保存
classdef Mesh < handle
%MESH : 3D网格对象
%继承handel后可以在对象内部修改对象的属性
properties
V%点 nv*3
E%线 ne*3
F%面 nf*3
NV%顶点法线
NF%平面法线
file_name%文件名称
matrix0%操作过程中进行的变换
print%控制 网格更新后是否以绘制出来
end
properties (Constant,Hidden)
m34=[1 0 0 0;0 1 0 0;0 0 1 0]
voxel_size=0.1;%体素方块大小
end
methods
function n=nv(o)
n=size(o.V,1);
end
function n=ne(o)
n=size(o.E,1);
end
function n=nf(o)
n=size(o.F,1);
end
function out=box(o,type)
box0=[min(o.V);max(o.V)];
if nargin==1
out=box0;
else
if type=="max"
out=box0(1,:);
elseif type=="min"
out=box0(2,:);
else%size
out=box0(2,:)-box0(1,:);
end
end
end
function o = Mesh(file_name)
o.file_name=file_name;
o.matrix0=eye(4);
[o.V,o.F] = o.read(file_name);
%o.mergeVertex();
o.computeNormal();%计算所有平面的法线
o.computeEdge();
o.print=0;%不展示每次网格更新的结果
end
function download(o)
%inv(o.matrix0);
o.write(o.file_name+"_save",o.V,o.F);
end
function applyMove(o,in1,in2,in3)
if nargin==2
d=in1;
else
d=[in1,in2,in3];
end
o.applyMatrix([1 0 0 d(1);0 1 0 d(2);0 0 1 d(3);0 0 0 1]);
end
function applyRotation(o,in1,in2,in3)
if nargin==2
in=in1;
else
in=[in1,in2,in3];
end
o.applyMatrix(rotx(in(1))*roty(in(2))*rotz(in(3)));
end
function applyScale(o,in1,in2,in3)
if nargin==2
s=in1;
else
s=[in1,in2,in3];
end
o.applyMatrix([s(1) 0 0;0 s(2) 0;0 0 s(3)]);
end
function applyMatrix(o,mat)
if length(mat)==3
mat=[mat;[0 0 0]];
mat=[mat';[0 0 0 1]]';
end
V2=[o.V';ones(o.nv,1)']';%nv*4
o.V=(o.m34*mat*(V2'))';
o.matrix0=mat*o.matrix0;%记录进行的变换
if o.print==1
o.draw();
end
end
function normal(o)
%旋转
voxel=o.voxelization();
mean0 = mean(voxel);% 样本均值
Z = voxel-repmat(mean0,length(voxel), 1);%减去均值
covMat = Z' * Z;% covMat 协方差矩阵 %r*3 3*r
[mat,~] = eigs(covMat, 3);% V每一列为一个特征向量
o.applyMatrix(mat');%对齐坐标轴
%翻转
voxel=o.voxelization();
mean0 = mean(voxel);% 样本均值
Z = voxel-repmat(mean0,length(voxel), 1);%减去均值
o.applyScale((sum(Z.^3)>0)*2-1);%取x^3大于0的方向为正方向
%移动
voxel=o.voxelization();
mean0 = mean(voxel);% 样本均值
o.applyMove(mean0.*-1);
%放缩
%voxel=o.voxelization();
%d=sum((voxel-mean(voxel)).^2);
%d=sum(voxel.^2);
%o.applyScale(ones(1,3)./(d.^0.5));%除以标准差
end
function reset(o)
o.applyMatrix(inv(o.matrix0));
end
function draw(o)
clf
trimesh(o.F, o.V(:,1), o.V(:,2), o.V(:,3),'LineWidth',1,'EdgeColor','k');
axis equal
%axis off %隐藏坐标轴
camlight
lighting gouraud
cameratoolbar%创建一个工具栏
drawnow
o.print=1;%展示每次网格更新的结果
end
function simplify(o,r)
myQEM=QEM();
o=myQEM.simplification(o,r);
if o.print==1
o.draw();
end
end
end%methods
methods(Hidden)
function process(this)
myQEM=QEM();
this=myQEM.simplification(this,0.5);
this.download();
end
function computeNormal(o)
%输入: vertex:nv*3 face:nf*3
%输出:
% compute_normal - compute the normal of a triangulation
%
% [normal,normalf] = compute_normal(vertex,face);
%
% normal(i,:) is the normal at vertex i.
% normalf(j,:) is the normal at face j.
[vertex,face] = check_face_vertex(o.V,o.F);
%vertex:3*nv face:3*nf
nface = size(face,2);
nvert = size(vertex,2);
% unit normals to the faces 单位面法线
normalf = crossp( vertex(:,face(2,:))-vertex(:,face(1,:)), ...
vertex(:,face(3,:))-vertex(:,face(1,:)) );
d = sqrt( sum(normalf.^2,1) );
d(d<eps)=1;%eps是极小值
normalf = normalf ./ repmat( d, 3,1 );%面法线单位化
% unit normal to the vertex
normal = zeros(3,nvert);%顶点法线 normal:3*nv
for i=1:nface
f = face(:,i);
for j=1:3
normal(:,f(j)) = normal(:,f(j)) + normalf(:,i);
end
end
% normalize
d = sqrt( sum(normal.^2,1) ); d(d<eps)=1;
normal = normal ./ repmat( d, 3,1 );
% enforce that the normal are outward
v = vertex - repmat(mean(vertex,1), 3,1);
s = sum( v.*normal, 2 );
if sum(s>0)<sum(s<0)
% flip
normal = -normal;
normalf = -normalf;
end
o.NV=normal';
o.NF=normalf';
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function z = crossp(x,y)%x,y可以看做三角形的两个边,z是和它们垂直的方向
% x and y are (m,3) dimensional
z = x;
z(1,:) = x(2,:).*y(3,:) - x(3,:).*y(2,:);
z(2,:) = x(3,:).*y(1,:) - x(1,:).*y(3,:);
z(3,:) = x(1,:).*y(2,:) - x(2,:).*y(1,:);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [vertex,face] = check_face_vertex(vertex,face)
% check_face_vertex - check that vertices and faces have the correct size
%
% [vertex,face] = check_face_vertex(vertex,face);
%
% Copyright (c) 2007 Gabriel Peyre
vertex = check_size(vertex,2,4);
face = check_size(face,3,4);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function a = check_size(a,vmin,vmax)
if isempty(a)
return;
end
if size(a,1)>size(a,2)
a = a';
end
if size(a,1)<3 && size(a,2)==3
a = a';
end
if size(a,1)<=3 && size(a,2)>=3 && sum(abs(a(:,3)))==0
% for flat triangles
a = a';
end
if size(a,1)<vmin || size(a,1)>vmax
error('face or vertex is not of correct size');
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
end%函数结束
function computeEdge(o)
TR = triangulation(o.F,o.V);%进行三角剖分,梳理出所有三角形
o.E = edges(TR);%返回所有边的顶点索引 ne*2
end
function rectifyindex(o)
%清除为空的顶点
%RECTIFYINDEX Summary of this function goes here
num_of_NaN=zeros(o.nv(),1);
sum=0;
for i=1:o.nv()
if isnan(o.V(i,1)) % 为空 NaN
sum=sum+1;
end
num_of_NaN(i)=sum;
end
recF=zeros(o.nf(),3);%三角面个数不变,但是由于顶点个改变,三角面的顶点索引需要修改
for i=1:o.nf()
for j=1:3
recF(i,j)=o.F(i,j)-num_of_NaN(o.F(i,j));
end
end
recV=zeros(o.nv-sum,3);%总个数-为空的个数
j=1;
for i=1:o.nv()
if ~isnan(o.V(i,1))
recV(j,:)=o.V(i,:);
j=j+1;
end
end
o.V=recV;
o.F=recF;
end
function voxel=voxelization(o)
box=o.box();
box_size=box(2,:)-box(1,:);
step=min(box_size)*o.voxel_size;%体素方块大小
if step==0
step=1;
end
%box_size_step=ceil(box_size./step);
voxel=[];%zeros(box_size_step);
for i=1:o.nf()
oF=o.F();
v1=o.V(oF(i,1),:);
v2=o.V(oF(i,2),:);
v3=o.V(oF(i,3),:);
voxel=addF(v1,v2,v3,voxel,box,step);
end
voxel=voxel.*o.voxel_size;%+repmat(box(1,:),length(voxel),1);%体素方块大小
function voxel=addF(v1,v2,v3,voxel,box,step)
for A=0:0.2:1
for B=0:0.2:(1-A)
v=v1.*A+v2.*B+v3.*(1-A-B);
voxel=addV(v,voxel,box,step);
end
end
end
function voxel=addV(v,voxel,box,step)
v=v-box(1,:);
v=round(v./step);
%v=v+ones(size(v));
if v(1)==0
v(1)=1;
end
if v(2)==0
v(2)=1;
end
if v(3)==0
v(3)=1;
end
voxel=[voxel;[v(1),v(2),v(3)]];
%voxel(v(1),v(2),v(3))=1;
end
end
end%methods(Hidden)
methods(Static)
function [vertex,faces] = read(filename)
vertex = [];
faces = [];
fid = fopen(filename+".obj");%fid是一个大于0的整数
s = fgetl(fid);
while ischar(s)
if ~isempty(s)
if strcmp(s(1), 'f')%如果字符串第一个字符为f %face
% F V1 V2 V3 ...
% F V1/VT1/VN1 ...
% F V1//VN1 ...
str2=strsplit(s," ");
coordinate1=strsplit(cell2mat(str2(2)),"/");
if isempty(length(coordinate1)==1)
faces(end+1,:) =sscanf(s(3:end), '%d %d %d');
else
coordinate2=strsplit(cell2mat(str2(3)),"/");%length(str2num("1 "))
coordinate3=strsplit(cell2mat(str2(4)),"/");
faces(end+1,:) = [
str2double(coordinate1(1))
str2double(coordinate2(1))
str2double(coordinate3(1))
];
end
elseif strcmp(s(1), 'v')%如果字符串第一个字符为v %vertex
vertex(end+1,:) = sscanf(s(3:end), '%f %f %f');
end%vertex添加一行、在最后一列 s从第三个字符开始到最后一个
end
s = fgetl(fid);%获取下一行
end
fclose(fid);
end
function write(filename,vertices,faces )
fid=fopen(filename+".obj",'w');
fid=arrPrintf(fid,vertices,'v');
fid=arrPrintf(fid,faces,'f');
fclose(fid);
function fid=arrPrintf(fid,arr,head)
[x,y]=size(arr);
for i=1:x
fprintf(fid,head);
for j=1:y
fprintf(fid,' %d',arr(i,j));
end
fprintf(fid,'\n');%每一行回车\n
end
end
end
end%methods(Static)
methods(Static,Hidden)%用于测试的方法
function test()
mesh=Mesh("man_sim2");
myQEM=QEM();
mesh=myQEM.simplification(mesh,0.5);
mesh.download();
end
function test2()
mesh=Mesh("mesh2");
mesh.box();
mesh.applyMatrix([
1 0 0 ;
0 0 1 ;
0 1 0
]);
voxel=mesh.voxelization();
size(voxel);
%sum(voxel,"all")
mesh.normal();
mesh.draw();
%mesh.download();
end
function test3()
%untitled
Mesh.read("untitled");
end
end%methods(Static)
end%class
QEM模型简化算法
参考:https://lafengxiaoyu.blog.csdn.net/article/details/72812681?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.baidujs&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.baidujs
原始算法有一个BUG,每次更新代价矩阵时,如果需要更新的点只有一个的时候运行会出错,添加一句“s=permute(s, [2,1,3])”就可以解决这个问题。
classdef QEM < handle
properties
%mesh
QVex %4*4*nv 顶点的矩阵
QEdge%4*4*ne 边的矩阵
cost % ne*3
v %4*3*ne 顶点坐标
end
methods
function o= QEM()
end
function mesh=simplification(o,mesh,percent )
o.pretreatment(mesh);
%开始坍塌%坍塌后的空洞推测是由于“多网格”/“重合点”引起的
for iii = 1:(1-percent)*mesh.nv()%每次删除一个顶点(一条边/一个三角面)
[min_cost, vidx] = min(o.cost,[],2);%返回包含每一行的最小值的列向量
% min_cost:ne*1 vidx:ne*1
[~, k] = min(min_cost);%获取代价最小的边序号
mesh=o.deleteEdge(k,mesh, vidx);
end
mesh.rectifyindex();
end%simplification
function pretreatment(o,mesh)
N=mesh.NF;
nv = mesh.nv(); % 顶点个数
%计算平面方程
p=getPlane(mesh.V,mesh.F,N);
function p=getPlane(V,F,N)%所有的平面,n*4
%p = [N, -sum(N .* V(F(:,1),:), 2)];
v0=V(F(:,1),:);
d=sum(v0.*N,2);
p=[N,-d];%每一行对应一个平面
end
%计算三角面的矩阵
QFace = getQFace(p);%面数个4*4的矩阵,size为4*4*n
function QFace=getQFace(p)
%bsxfun(@times, permute(p, [2,3,1]), permute(p, [3,2,1]))
%p的维度是 n*4=n*4*1
p1=permute(p, [2,3,1]);%4*1*n
p2=permute(p, [3,2,1]);%1*4*n
QFace=bsxfun(@times,p1,p2);%4*4*n
end
%1.计算顶点的矩阵Q
o.QVex=getQ(mesh.F,QFace,nv);%4*4*nv
function Q=getQ(F,QFace,nv)
Q = zeros(4,4,nv);%每个顶点都对应一个4*4的矩阵
nf = size(F,1);%三角面个数
for i = 1:nf%遍历三角面
for j = 1:3%遍历三角面的顶点
v_indx=F(i,j);%获取三角面上的顶点序号
Q(:,:,v_indx) = Q(:,:,v_indx) + QFace(:,:,i);%顶点的矩阵等于所有所处三角面的矩阵和
end
end
end
%2.计算每个边矩阵
%E=getE(mesh.F,mesh.V);%获取所有边
ne = size(mesh.E,1);
o.QEdge = getQEdge(o.QVex,mesh.E);%边矩阵的合理性在于两个端点折叠后的位置相同,计算出所有的边矩阵
%QEdge:4*4*ne
function QEdge=getQEdge(Q,E)% compute Q1+Q2 for each pair
%QEdge = Q(:,:,E(:,1)) + Q(:,:,E(:,2))
e1=E(:,1);% ne*2 -> ne*1
e2=E(:,2);
QEdge = Q(:,:,e1) + Q(:,:,e2);% Q:4*4*nv -> QEdge:4*4*ne
end
%3.计算每个边的代价
[o.cost,o.v]=getcost(o,mesh.V,mesh.E,ne,o.QEdge);
%cost:ne*3 v:4*3*ne
function [cost,v]=getcost(o,V,E,ne,QEdge)% a simple scheme: select either v1, v2 or (v1+v2)/2
%v:4*3*ne (坐标+1)*(3个点)*(边数)
v=getv(V,E,ne);%4*3*ne
function v=getv(V,E,ne)
v1 = getv_4(V,E,ne,1);%4*1*ne
v2 = getv_4(V,E,ne,2);
function v_4=getv_4(V,E,ne,col)
%v1 = permute([V(E(:,1),:),ones(ne,1)], [2,3,1]);%获取边的另一个端点位置
%v2 = permute([V(E(:,2),:),ones(ne,1)], [2,3,1]);%获取边的另一个端点位置
vertex_index=E(:,col);%边的第一列数据 ne*1
vertex_pos=V(vertex_index,:);% V:nv*3 -> vertex_pos:ne*3
v_4 = permute([vertex_pos,ones(ne,1)], [2,3,1]);%ne*4*1 ->4*1*ne %获取边的一个端点位置
end
vm = 0.5 .* (v1 + v2);%获取边的中点位置
v = [v1, v2, vm]; % 4*1*ne -> 4*3*ne
end
cost = zeros(ne,3);%用于记录每条边的代价
cost(:,1)=o.get_costi(v(:,1,:),QEdge);% ne*1
cost(:,2)=o.get_costi(v(:,2,:),QEdge);
cost(:,3)=o.get_costi(v(:,3,:),QEdge);
%{
for i=1:ne
if i==1
%display(cost(i,1))
end
if V(E(i,1),1)>0 %如果x>0 代价加100
cost(i,1)=cost(i,1)+100;
cost(i,2)=cost(i,2)+100;
cost(i,3)=cost(i,3)+100;
end
end
%}
end
end%pretreatment
function mesh=deleteEdge(o,k,mesh, vidx)
%k是待删除的边的序号
e = mesh.E(k,:);%获取边对应的两个顶点
% update position for v1
mesh.V(e(1),:) = o.v(1:3, vidx(k), k)';%一个顶点坍塌到指定位置
mesh.V(e(2),:) = NaN;%删除另一个顶点
% update Q for v1 %更新代价矩阵,这里的代价之后似乎重新计算了
o.QVex(:,:,e(1)) = o.QVex(:,:,e(1)) + o.QVex(:,:,e(2));%e(1)的代价为之前两个点的代价之和
o.QVex(:,:,e(2)) = NaN;%e(2)的代价为空
%更新三角面
mesh.F(mesh.F == e(2)) = e(1);%e1、e2都是具体数值 %三角面中e2的索引现在都指向e1
f_remove = sum(diff(sort(mesh.F,2),[],2) == 0, 2) > 0;%如果三角面中有两个相同的点就应当移除
mesh.F(f_remove,:) = [];%需要移除的平面置为空
%删除去除的边和与该边相关的信息 collapse and delete edge and related edge information
mesh.E(mesh.E == e(2)) = e(1);%边中e2的索引现在都指向e1
mesh.E(k,:) = [];%k是代价最小的边序号,置为空
o.cost(k,:) = [];%修改边的代价信息
o.QEdge(:,:,k) = [];%删除边对应的矩阵
o.v(:,:,k) = [];%v的每行对应一条边
%删除重复的边和与该边相关的信息 delete duplicate edge and related edge information
[mesh.E,ia] = unique(sort(mesh.E,2), 'rows'); %E:ne*2 获取独一的行(边)
o.cost = o.cost(ia,:);
o.QEdge = o.QEdge(:,:,ia);%QEdge:4*4*ne
o.v = o.v(:,:,ia);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% pairs involving v1
pair = sum(mesh.E == e(1), 2) > 0;%与e1相关的边的序号
npair = sum(pair);%与e1相关的边的个数
% updata edge information
o.QEdge(:,:,pair) = o.QVex(:,:,mesh.E(pair,1)) + o.QVex(:,:,mesh.E(pair,2));
%QEdge:4*4*ne pair:n*1
pair_v1 = permute([mesh.V(mesh.E(pair,1),:),ones(npair,1)], [2,3,1]);
pair_v2 = permute([mesh.V(mesh.E(pair,2),:),ones(npair,1)], [2,3,1]);
%pair_v2:3*1*n V(E(pair,2),:))--n*3
%pair_v2:3*1*1 V(E(pair,2),:))--1*3
pair_vm = 0.5 .* (pair_v1 + pair_v2);
o.v(:,:,pair) = [pair_v1, pair_v2, pair_vm];
%更新所有与e1相关的边的代价
o.cost(pair,1) =o.get_costi(pair_v1,o.QEdge(:,:,pair));
o.cost(pair,2) =o.get_costi(pair_v2,o.QEdge(:,:,pair));
o.cost(pair,3) =o.get_costi(pair_vm,o.QEdge(:,:,pair));
% cost(pair,1) =sum(squeeze(sum(bsxfun(@times,pair_v1,QEdge(:,:,pair)),1)).*squeeze(pair_v1),1)';
end%deleteEdge
end% methods
methods(Static)
function costi=get_costi(vi,QEdge)
costi=QEM.get_costi1(vi,QEdge);
end
function costi=get_costi1(vi,QEdge)
%输入 vi:4*1*ne QEdge:4*4*ne
%输出 costi:ne*1
%统一使用边矩阵?,感觉不是很合理
bsx=bsxfun(@times,QEdge,vi); %{QEdge:4*4*ne vi:4*1*ne } -> 4*4*ne
s=sum(bsx,1); % 4*4*ne -> 1*4*ne
s=permute(s, [2,1,3]);%!!!!!!!!!!!!!!!!!解决了BUG
costi=sum(squeeze(s).*squeeze(vi),1)';
% s:1*4*ne vi:4*1*ne
% ne*1 = { 1*4*ne , 4*1*ne }'
%坐标z>=0的点删除代价加大
for i=1:size(vi,3) % vi:4*1*ne
if vi(3,1,i)>=0 %z>=-1
costi(i)=costi(i)*10000000;%最前面
elseif vi(3,1,i)>=-1 %z>=0
costi(i)=costi(i)*1000;%中间
end
end
end
function costi=get_costi0(vi,QEdge)
%输入 vi:4*1*ne QEdge:4*4*ne
%输出 costi:ne*1
%统一使用边矩阵?,感觉不是很合理
bsx=bsxfun(@times,QEdge,vi); %{QEdge:4*4*ne vi:4*1*ne } -> 4*4*ne
s=sum(bsx,1); % 4*4*ne -> 1*4*ne
s=permute(s, [2,1,3]);%!!!!!!!!!!!!!!!!!解决了BUG
costi=sum(squeeze(s).*squeeze(vi),1)';
% s:1*4*ne vi:4*1*ne
% ne*1 = { 1*4*ne , 4*1*ne }'
end
end%methods(Static)
end%class