Matlab: Set shading of single surface - matlab

I'm making a function which generalizes the cylinder function so that the cylinder has caps, can be any size and orientation. However on the look of the cylinder I am running into a jam. To get the caps to look right the curved part needs one set of shading and the caps need another. (And before you ask making 3 surfaces is not an option)
Here is the relevant code:
surface(xSurf,ySurf,zSurf,c,'EdgeColor','none','FaceLighting','phong');
and in case you want to see the whole code.
Thank you for your help,
John
function varargout = DrawCylinder(x,y,z,r,h,aVec,bVec,cVec,ccolor, npts)
% DrawCylinder Generate a three-dimensional cylinder
%
% DrawCylinder(x,y,z,a,b,c,aVec,bVec,CVec,ccolor, npts)
% creates a surface plot of a cylinder whose center is at (x,y,z), has
% semiaxes of length a, b, and c. The unit vectors associated with each
% semixis are aVec, bVec, and cVec and must be size 3 x 1 (column vector)
% with size of npts + 1.
%
% H = DrawCylinder(...) creates the surface plot and returns the handle H to each
% graphical object created.
%
% [X Y Z] = DrawCylinder(...) does not generate the surface plot and returns
% the data necessary to create the surface using:
% SURF(X,Y,Z);
%
% [X Y Z C] = DrawCylinder(...) does not generate the surface plot and returns
% the data necessary to create the surface using:
% SURF(X,Y,Z,C,'cdataMapping','direct');
%CREATE SURFACE FOR CYLINDER
[xCyl,yCyl,zCyl]=cylinder(1,npts);
xSurf=[zeros(1,max(size(xCyl)));xCyl;zeros(1,max(size(xCyl)))];
ySurf=[zeros(1,max(size(yCyl)));yCyl;zeros(1,max(size(yCyl)))];
zSurf=[zeros(1,max(size(zCyl)));zCyl;ones(1,max(size(zCyl)))] - 0.5;
xSurf = xSurf*r;
ySurf = ySurf*r;
zSurf = zSurf*h;
%ROTATE CYLINDER
%Make sure aVec,bVec, and cVec are column unit vectors:
if all(size(aVec)==[1,3])
aVec=aVec';
end
if all(size(bVec)==[1,3])
bVec=bVec';
end
if all(size(cVec)==[1,3])
cVec=cVec';
end
aVec=aVec/norm(aVec); %Make unit vectors
bVec=bVec/norm(bVec);
cVec=cVec/norm(cVec);
rot = [aVec,bVec,cVec]; %The rotation matrix
[iMax, jMax] = size(xSurf);
for i=1:iMax
for j=1:jMax
rotatedPt = rot*[xSurf(i,j);ySurf(i,j);zSurf(i,j)];
xSurf(i,j) = rotatedPt(1);
ySurf(i,j) = rotatedPt(2);
zSurf(i,j) = rotatedPt(3);
end
end
%TRANSLATE CYLINDER
xSurf = xSurf + x;
ySurf = ySurf + y;
zSurf = zSurf + z;
c = ccolor*ones(size(xSurf));
if nargout == 0
surface(xSurf,ySurf,zSurf,c,'EdgeColor','none','FaceLighting','phong');
elseif nargout == 1
varargout = {surface(xSurf,ySurf,zSurf,c,'EdgeColor','none','FaceLighting','phong');};
elseif nargout == 3
varargout = {xSurf ySurf zSurf};
elseif nargout == 4
varargout = {xSurf ySurf zSurf c};
end
end
Edit 8/18/12:
Just so you can see.
This is what I am getting ...
And this is what I want ...

I'm pretty sure that by "shade" you simply mean the colors of the end caps and not a more complicated effect. If so that's pretty straightforward, I'll give an example in gray scale
Change
c = ccolor*ones(size(xSurf));
to
c = (ccolor/255)*ones(size(xSurf));
c([1 3],:)=max(0,(ccolor-10))/255;
the first line initializes the c matrix with a normalized ccolor (expecting an 8 bit greyscale ccolor input, it normalizes to 0..1). The second line changes the caps (rows 1 and 3) to be a slightly darker color bottoming out at 0 and leaves the cylinder surfaces (rows 2 and 4) alone.
In order to make sure you see the results correctly, you need to alter the nargout==0 condition so that it looks like this
surface(xSurf,ySurf,zSurf,c,'EdgeColor','none','FaceLighting','phong');
colormap(gray(256));
caxis([0 1]);
The colormap just sets the colormap, similar to an 8 bit gray scale. The caxis command is fairly critical. According to Matlab's surface documentation
MATLAB performs a linear transformation on this data to obtain colors from the current colormap
For our purposes that is bad. Since we only have two values the lowest would be changed to 0 and the highest to 1. That effectively ignores our ccolor input and gives a white cylinder with two black caps. Using caxis([0 1]) preserves the full scale and ccolor's position within it.
Update:
Sounds like I misunderstood what you wanted, the easiest way to achieve something very close to the effect you want is to set 'MeshStyle' to 'row', like this:
surface(xSurf,ySurf,zSurf,c,'EdgeColor','k','FaceLighting','phong','MeshStyle','row');
This will give you the following result :
There's still a central point, but it's by far the simplest way to produce that effect.

Related

How can I reduce the number of mesh lines shown in a surface plot?

I've found this answer, but I can't complete my work. I wanted to plot more precisely the functions I am studying, without overcoloring my function with black ink... meaning reducing the number of mesh lines. I precise that the functions are complex.
I tried to add to my already existing code the work written at the link above.
This is what I've done:
r = (0:0.35:15)'; % create a matrix of complex inputs
theta = pi*(-2:0.04:2);
z = r*exp(1i*theta);
w = z.^2;
figure('Name','Graphique complexe','units','normalized','outerposition',[0.08 0.1 0.8 0.55]);
s = surf(real(z),imag(z),imag(w),real(w)); % visualize the complex function using surf
s.EdgeColor = 'none';
x=s.XData;
y=s.YData;
z=s.ZData;
x=x(1,:);
y=y(:,1);
% Divide the lengths by the number of lines needed
xnumlines = 10; % 10 lines
ynumlines = 10; % 10 partitions
xspacing = round(length(x)/xnumlines);
yspacing = round(length(y)/ynumlines);
hold on
for i = 1:yspacing:length(y)
Y1 = y(i)*ones(size(x)); % a constant vector
Z1 = z(i,:);
plot3(x,Y1,Z1,'-k');
end
% Plotting lines in the Y-Z plane
for i = 1:xspacing:length(x)
X2 = x(i)*ones(size(y)); % a constant vector
Z2 = z(:,i);
plot3(X2,y,Z2,'-k');
end
hold off
But the problem is that the mesh is still invisible. How to fix this? Where is the problem?
And maybe, instead of drawing a grid, perhaps it is possible to draw circles and radiuses like originally on the graph?
I found an old script of mine where I did more or less what you're looking for. I adapted it to the radial plot you have here.
There are two tricks in this script:
The surface plot contains all the data, but because there is no mesh drawn, it is hard to see the details in this surface (your data is quite smooth, this is particularly true for a more bumpy surface, so I added some noise to the data to show this off). To improve the visibility, we use interpolation for the color, and add a light source.
The mesh drawn is a subsampled version of the original data. Because the original data is radial, the XData and YData properties are not a rectangular grid, and therefore one cannot just take the first row and column of these arrays. Instead, we use the full matrices, but subsample rows for drawing the circles and subsample columns for drawing the radii.
% create a matrix of complex inputs
% (similar to OP, but with more data points)
r = linspace(0,15,101).';
theta = linspace(-pi,pi,101);
z = r * exp(1i*theta);
w = z.^2;
figure, hold on
% visualize the complex function using surf
% (similar to OP, but with a little bit of noise added to Z)
s = surf(real(z),imag(z),imag(w)+5*rand(size(w)),real(w));
s.EdgeColor = 'none';
s.FaceColor = 'interp';
% get data back from figure
x = s.XData;
y = s.YData;
z = s.ZData;
% draw circles -- loop written to make sure the outer circle is drawn
for ii=size(x,1):-10:1
plot3(x(ii,:),y(ii,:),z(ii,:),'k-');
end
% draw radii
for ii=1:5:size(x,2)
plot3(x(:,ii),y(:,ii),z(:,ii),'k-');
end
% set axis properties for better 3D viewing of data
set(gca,'box','on','projection','perspective')
set(gca,'DataAspectRatio',[1,1,40])
view(-10,26)
% add lighting
h = camlight('left');
lighting gouraud
material dull
How about this approach?
[X,Y,Z] = peaks(500) ;
surf(X,Y,Z) ;
shading interp ;
colorbar
hold on
miss = 10 ; % enter the number of lines you want to miss
plot3(X(1:miss:end,1:miss:end),Y(1:miss:end,1:miss:end),Z(1:miss:end,1:miss:end),'k') ;
plot3(X(1:miss:end,1:miss:end)',Y(1:miss:end,1:miss:end)',Z(1:miss:end,1:miss:end)','k') ;

Specifying different colour schemes for different orientations of Polarhistogram in MATLAB

Question
When using polarhistogram(theta) to plot a dataset containing azimuths from 0-360 degrees. Is it possible to specify colours for given segments?
Example
In the plot bellow for example would it be possible to specify that all bars between 0 and 90 degrees (and thus 180-270 degrees also) are red? whilst the rest remains blue?
Reference material
I think if it exists it will be within here somewhere but I am unable to figure out which part exactly:
https://www.mathworks.com/help/matlab/ref/polaraxes-properties.html
If you use rose, you can extract the edges of the histogram and plot each bar one by one. It's a bit of a hack but it works, looks pretty and does not require Matlab 2016b.
theta = atan2(rand(1e3,1)-0.5,2*(rand(1e3,1)-0.5));
n = 25;
colours = hsv(n);
figure;
rose(theta,n); cla; % Use this to initialise polar axes
[theta,rho] = rose(theta,n); % Get the histogram edges
theta(end+1) = theta(1); % Wrap around for easy interation
rho(end+1) = rho(1);
hold on;
for j = 1:floor(length(theta)/4)
k = #(j) 4*(j-1)+1; % Change of iterator
h = polar(theta(k(j):k(j)+3),rho(k(j):k(j)+3));
set(h,'color',colours(j,:)); % Set the color
[x,y] = pol2cart(theta(k(j):k(j)+3),rho(k(j):k(j)+3));
h = patch(x,y,'');
set(h,'FaceColor',colours(j,:),'FaceAlpha',0.2);
uistack(h,'down');
end
grid on; axis equal;
title('Coloured polar histogram')
Result

Customizing multiple rootlocus plot colors (scale of grey) Matlab

I would like to customize the color of my rootlocus plot.
I use a for cycle to plot 10 rootlocus (with slightly different systems in the loop) and I would like every of them to be of a different shade of grey. I thought to use the gray command to obtain a matrix to store the RGB data and then use this matrix in the rlocus(sys,K,'style') command (choosing the i-th line at the i-th iteration of my cycle). Unfortunately the command requires the style to be a cell (for example 'g' or 'b') and not a vector of numbers.
This is a sample of my code:
figure()
hold on
L = [sys1, sys2, ..., sys10];
colors = gray(10);
for i = 0:9
rlocus (L(i+1), 'Color', colors(i+1, :));
end
The rlocus() function is not as powerful as the plot() function and only has limited support for setting colours with rlocus(sys, 'b') as you've noticed. However, we can combine it with the plot() function to make use of its power.
Here I use [R, K] = rlocus(sys) to return the values of the root locus, R. Each row of R represents a different trajectory. We can plot 1 trajectory of the root locus with plot(R(m, :)) and utilise the strength of plot() to change the colour however we wish.
L = [sys1, sys2, sys3, sys4, sys5, sys6, sys7, sys8, sys9, sys10];
C = gray(numel(L) + 1); % Extra 1 because the last value will be
% white and plotting white on white does
% not look well :P
figure;
hold on
for n = 1:numel(L)
[R, K] = rlocus(L(n));
for m = 1:numel(R)/length(R)
plot(R(m, :), 'Color', C(n, :));
end
end
hold off

3d plot with axes of same length

I have some 3D trajectories I want to plot.
Since they vary a lot in XY, but much less in Z, the default plot3 is misleading, because it automatically scales axes.
I've been told to use axes equal but it has no effect (see the commented line where I used it).
I came up with this code, which in my opinion is very long to achieve a so simple task:
[D,rate]=read_vicon_ascii('csvdata/a1-0.csv');
% or replace above line with D=csvread('stackoverflow-31289872.csv');
% get stackoverflow-31289872.csv at https://drive.google.com/file/d/0B5GjKiDZk3F5UHlVQUxKeFo4SG8/view?pli=1
% indices of X,Y,Z columns
X = 1+(2:3:14);
Y = 2+(2:3:14);
Z = 3+(2:3:14);
Bounds = [ min(min(D(:,X))) max(max(D(:,X)))
min(min(D(:,Y))) max(max(D(:,Y)))
min(min(D(:,Z))) max(max(D(:,Z))) ];
MaxDelta = max(Bounds(:,2)-Bounds(:,1));
SquareBounds = Bounds;
for xyz=1:3
Delta = SquareBounds(xyz,2) - SquareBounds(xyz,1);
SquareBounds(xyz,:) = SquareBounds(xyz,:) + (MaxDelta - Delta) * [-0.5 0.5];
end
figure
hold on
for i=1:5
plot3(D(:,X(i)),D(:,Y(i)),D(:,Z(i)),'r-')
end
xlim(SquareBounds(1,:))
ylim(SquareBounds(2,:))
zlim(SquareBounds(3,:))
%axes equal
hold off
Is there any way to make it better. (or a correct usage of axes equal if that does what is supoosed to do?)

MATLAB - Intersect between a surface and a plane

I have a 3D mesh like in this picture.
Now what I want to do is create a plane that will intersect the surface at a certain Z value. I would then want to get the x and y coordinates of this intersection and have matlab output them.
What I'm planning on doing is that this picture is a model of a lake. This lake will have water evaporating that will be removing a certain z value of water. I would then like to see what the new shoreline would look like by obtaining the x and y coordinates of this intersection.
This is my code for plotting that picture.
function plot(x,y,z)
Contour = xlsread('C:\Users\Joel\Copy\Contour','A2:D4757');
x = Contour(:,1);
y = Contour(:, 2);
z = Contour(:,3);
F = TriScatteredInterp(x,y,z);
[X,Y]=meshgrid(linspace(-600,600,300));
Z=F(X,Y);
surf(X,Y,Z);
end
You can do this by simply thresholding the interpolated Z values
inside = Z < seaLevel; % binary mask of points inside water
shoreline = bwmorph( inside, 'remove' ); % a mask with only shoreline pixels eq to 1
figure;
surf( X, Y, Z, 'EdgeColor', 'none', 'FaceColor', [210,180,140]/255, 'FaceAlpha', .5 );
hold on;
ii = find( shoreline );
plot3( X(ii), Y(ii), seaLevel*ones(size(ii)), 'b', 'LineWidth', 2 );
The contour3 will give nicer boundaries:
[h,c]=contour3(X,Y,Z,[seaLevel seaLevel]);
seaLevel is given twice: otherwise contour3 thinks seaLevel is the number of levels to automatically calibrate. And to nicely annotate with the numeric height of your seaLevel:
clabel(h,c);
you may modify c to print "sea level" instead of num2str(seaLevel).
If you don't have the MATLAB toolbox for image processing you can't use the function "bwmorph". As a solution you can replace line 2 of Shai's code with the following code which reimplements the bwmorph function for parameter 'remove'. (The reimplementation is probably neither very perfomrant nor an exact reimplementatin (the borders of the matrix aren't used) - but the solution should work as a first step. Feel free to improve).
shoreline= zeros(size(inside));
for i_row = 2:size(inside,1)-1
for i_col = 2:size(inside,2)-1
if(inside(i_row,i_col))
if (~( inside(i_row+1,i_col) && ...
inside(i_row-1,i_col) && ...
inside(i_row,i_col+1) && ...
inside(i_row,i_col-1)))
inside2(i_row,i_col) = 1;
end
end
end
end