问题描述
我在3D中有散射数据X1,Y1,Z1
,我可以将其绘制为
a=1; c=1; t=0:100;
X1 = (a*t/2*pi*c).*sin(t);
Y1 = (a*t/2*pi*c).*cos(t);
Z1 = t/(2*pi*c);
scatter3(X1,Z1);
% or plot3(X1,Z1);
这些点定义3D路径。如何将其制作成类似于下面的功能区图?
使用delaunay三角剖分,我可以将其绘制为曲面:
tri = delaunay(X1,Y1);
h = trisurf(tri,X1,Z1);
但是ribbon
并没有达到期望的结果:
ribbon(Y1)
下图显示了我的追求。
解决方法
ribbon
函数只能接受2D输入,因为它使用第3维来“构建”功能区。
获得3D功能区的一种方法是在每个点之间构建一系列patch
或surface
并正确定向它们,使它们看起来连续。
以下代码将在由(x,y,z)
矢量定义的任意3D路径周围构建功能区。我不会解释代码的每一行,但是有很多注释,我停下来进行中间可视化,以便您可以了解其构造。
%% Input data
a=1; c=1; t=0:.1:100;
x = (a*t/2*pi*c).*sin(t);
y = (a*t/2*pi*c).*cos(t);
z = t/(2*pi*c);
nPts = numel(x) ;
%% display 3D path only
figure;
h.line = plot3(x,z,'k','linewidth',2,'Marker','none');
hold on
xlabel('X')
ylabel('Y')
zlabel('Z')
%% Define options
width = ones(size(x)) * .4 ;
% define surface and patch display options (FaceAlpha etc ...),for later
surfoptions = {'FaceAlpha',0.8,'EdgeColor','EdgeAlpha','DiffuseStrength',1,'AmbientStrength',1 } ;
%% get the gradient at each point of the curve
Gx = diff([x,x(1)]).' ;
Gy = diff([y,y(1)]).' ;
Gz = diff([z,z(1)]).' ;
% get the middle gradient between 2 segments (optional,just for better rendering if low number of points)
G = [ (Gx+circshift(Gx,1))./2 (Gy+circshift(Gy,1))./2 (Gz+circshift(Gz,1))./2] ;
%% get the angles (azimuth,elevation) of each plane normal to the curve
ux = [1 0 0] ;
uy = [0 1 0] ;
uz = [0 0 1] ;
for k = nPts:-1:1 % running the loop in reverse does automatic preallocation
a = G(k,:) ./ norm(G(k,:)) ;
angx(k) = atan2( norm(cross(a,ux)),dot(a,ux)) ;
angy(k) = atan2( norm(cross(a,uy)),uy)) ;
angz(k) = atan2( norm(cross(a,uz)),uz)) ;
[az(k),el(k)] = cart2sph( a(1),a(2),a(3) ) ;
end
% compensate for poor choice of initial cross section plane
az = az + pi/2 ;
el = pi/2 - el ;
%% define basic ribbon element
npRib = 2 ;
xd = [ 0 0] ;
yd = [-1 1] ;
zd = [ 0 0] ;
%% Generate coordinates for each cross section
cRibX = zeros( nPts,npRib ) ;
cRibY = zeros( nPts,npRib ) ;
cRibZ = zeros( nPts,npRib ) ;
cRibC = zeros( nPts,npRib ) ;
for ip = 1:nPts
% cross section coordinates.
csTemp = [ ( width(ip) .* xd ) ; ... %// X coordinates
( width(ip) .* yd ) ; ... %// Y coordinates
zd ] ; %// Z coordinates
%// rotate the cross section (around X axis,around origin)
elev = el(ip) ;
Rmat = [ 1 0 0 ; ...
0 cos(elev) -sin(elev) ; ...
0 sin(elev) cos(elev) ] ;
csTemp = Rmat * csTemp ;
%// do the same again to orient the azimuth (around Z axis)
azi = az(ip) ;
Rmat = [ cos(azi) -sin(azi) 0 ; ...
sin(azi) cos(azi) 0 ; ...
0 0 1 ] ;
csTemp = Rmat * csTemp ;
%// translate each cross section where it should be and store in global coordinate vector
cRibX(ip,:) = csTemp(1,:) + x(ip) ;
cRibY(ip,:) = csTemp(2,:) + y(ip) ;
cRibZ(ip,:) = csTemp(3,:) + z(ip) ;
end
%% Display the full ribbon
hd.cyl = surf( cRibX,cRibY,cRibZ,cRibC ) ;
set( hd.cyl,surfoptions{:} )
现在,您的图形对象包含在一个曲面对象中,您可以设置最终渲染的选项。例如(仅作为示例,探索surface
对象属性以找到所有可能性)。
%% Final render
h.line.Visible = 'off' ;
surfoptionsfinal = {'FaceAlpha','none',1 } ;
set( hd.cyl,surfoptionsfinal{:} )
axis off
请注意,此代码是对此answer(针对该问题:Matlab: “X-Ray” plot line through patch)中提供的代码的改编(简化)。
此方法允许绘制任意横截面(答案中为光盘)并构建将遵循路径的表面。对于您的问题,我用一条简单的线替换了disc
横截面。您也可以将其替换为任意横截面(圆盘,正方形,马铃薯状的形状……是极限)。
编辑
替代方法:
事实证明,有一个Matlab函数可以做到这一点。我首先将其丢弃是因为它是用于3D 体积可视化的,并且大多数调用它的方法都需要网格输入(meshgrid
样式)。对我们来说幸运的是,还有一种调用语法可以处理您的数据。
% Same input data
a=1; c=1; t=0:.1:100;
x = (a*t/2*pi*c).*sin(t);
y = (a*t/2*pi*c).*cos(t);
z = t/(2*pi*c);
% Define vertices (and place in cell array)
verts = {[x.',y.',z.']};
% Define "twistangle". We do not need to twist it in that direction but the
% function needs this input so filling it with '0'
twistangle = {zeros(size(x.'))} ;
% call 'streamribbon',the 3rd argument is the width of the ribbon.
hs = streamribbon(verts,tw,0.4) ;
% improve rendering
view(25,9)
axis off
shading interp;
camlight
lighting gouraud
对于其他图形控制(在功能区的边缘),可以参考此问题和我的答案:MATLAB streamribbon edge color