如何在 Matlab 中将 3D 路径数据绘制为色带

How to plot 3D path data as a ribbon in Matlab

我有 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,Y1,Z1);
% or plot3(X1,Y1,Z1);

这些点定义了 3D 路径。如何将其制作成类似下面的带状图?

使用 delaunay 三角剖分,我可以将其绘制为曲面:

tri = delaunay(X1,Y1); h = trisurf(tri, X1, Y1, Z1);

但是ribbon没有给出想要的结果:

ribbon(Y1)

下图就是我所追求的

ribbon 函数只能接受二维输入,因为它使用第 3 维来 'build' 色带。

实现 3D 色带的一种方法是在每个点之间构建一系列 patchsurface,并正确定位它们,使它们看起来是连续的。

以下代码将围绕由 (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,y,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','k' , 'EdgeAlpha',0.8 , '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)) , dot(a,uy))  ;
    angz(k) =  atan2( norm(cross(a,uz)) , dot(a,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',0.8 , 'EdgeColor','none' , 'DiffuseStrength',1 , 'AmbientStrength',1 } ;
set( hd.cyl , surfoptionsfinal{:} )
axis off


请注意,此代码是此 answer (to that question: Matlab: “X-Ray” plot line through patch) 中提供的代码的改编(简化)。

此方法允许绘制任意横截面(答案中的圆盘)并构建将遵循路径的曲面。对于您的问题,我用一条简单的线替换了 disc 横截面。您也可以将其替换为任意横截面(圆盘、正方形、马铃薯……没有限制)。


编辑

替代方法:

原来有一个 Matlab 函数可以做到这一点。我首先放弃它是因为它用于 3D volume 可视化,并且大多数调用它的方法都需要网格化输入(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

将呈现下图:

额外的图形控制(over the edges of the ribbon),你可以参考这个问题和我的回答: