Matlab - Subtracting two 3D figures with surf and min function - matlab

I'm trying to make a surf plot that looks like:
So far I have:
x = [-1:1/100:1];
y = [-1:1/100:1];
[X,Y] = meshgrid(x,y);
Triangle1 = -abs(X) + 1.5;
Triangle2 = -abs(Y) + 1.5;
Z = min(Triangle1, Triangle2);
surf(X,Y,Z);
shading flat
colormap winter;
hold on;
[X,Y,Z] = sphere();
Sphere = surf(X, Y, Z + 1.5 );% sphere with radius 1 centred at (0,0,1.5)
hold off;
This code produces a graph that looks like :
A pyramid with square base ([-1,1]x[-1,1]) and vertex at height c = 1.5 above the origin (0,0) is erected.
The top of the pyramid is hollowed out by removing the portion of it that falls within a sphere of radius r=1 centered at the vertex.
So I need to keep the part of the surface of the sphere that is inside the pyramid and delete the rest. Note that the y axis in each plot is different, that's why the second plot looks condensed a bit. Yes there is a pyramid going into the sphere which is hard to see from that angle.
I will use viewing angles of 70 (azimuth) and 35 (elevation). And make sure the axes are properly scaled (as shown). I will use the AXIS TIGHT option to get the proper dimensions after the removal of the appropriate surface of the sphere.

Here is my humble suggestion:
N = 400; % resolution
x = linspace(-1,1,N);
y = linspace(-1,1,N);
[X,Y] = meshgrid(x,y);
Triangle1 = -abs(X)+1.5 ;
Triangle2 = -abs(Y)+1.5 ;
Z = min(Triangle1, Triangle2);
Trig = alphaShape(X(:),Y(:),Z(:),2);
[Xs,Ys,Zs] = sphere(N-1);
Sphere = alphaShape(Xs(:),Ys(:),Zs(:)+2,2);
% get all the points from the pyramid that are within the sphere:
inSphere = inShape(Sphere,X(:),Y(:),Z(:));
Zt = Z;
Zt(inSphere) = nan; % remove the points in the sphere
surf(X,Y,Zt)
shading interp
view(70,35)
axis tight
I use alphaShape object to remove all unwanted points from the pyramid and then plot it without them:
I know, it's not perfect, as you don't see the bottom of the circle within the pyramid, but all my tries to achieve this have failed. My basic idea was plotting them together like this:
hold on;
Zc = Zs;
inTrig = inShape(Trig,Xs(:),Ys(:),Zs(:)+1.5);
Zc(~inTrig) = nan;
surf(Xs,Ys,Zc+1.5)
hold off
But the result is not so good, as you can't really see the circle within the pyramid.
Anyway, I post this here as it might give you a direction to work on.

An alternative to EBH's method.
A general algorithm from subtracting two shapes in 3d is difficult in MATLAB. If instead you remember that the equation for a sphere with radius r centered at (x0,y0,z0) is
r^2 = (x-x0)^2 + (y-y0)^2 + (z-z0)^2
Then solving for z gives z = z0 +/- sqrt(r^2-(x-x0)^2-(y-y0)^2) where using + in front of the square root gives the top of the sphere and - gives the bottom. In this case we are only interested in the bottom of the sphere. To get the final surface we simply take the minimum z between the pyramid and the half-sphere.
Note that the domain of the half-sphere is defined by the filled circle r^2-(x-x0)^2-(y-y0)^2 >= 0. We define any terms outside the domain as infinity so that they are ignored when the minimum is taken.
N = 400; % resolution
z0 = 1.5; % sphere z offset
r = 1; % sphere radius
x = linspace(-1,1,N);
y = linspace(-1,1,N);
[X,Y] = meshgrid(x,y);
% pyramid
Triangle1 = -abs(X)+1.5 ;
Triangle2 = -abs(Y)+1.5 ;
Pyramid = min(Triangle1, Triangle2);
% half-sphere (hemisphere)
sqrt_term = r^2 - X.^2 - Y.^2;
HalfSphere = -sqrt(sqrt_term) + z0;
HalfSphere(sqrt_term < 0) = inf;
Z = min(HalfSphere, Pyramid);
surf(X,Y,Z)
shading interp
view(70,35)
axis tight

Related

Adjust projection of 3d plot in Matlab

Matlab doesn't have an easy off-the-shelf way to adjust the distortion of the projection's perspective of a 3D figure that I know of. So, given the code below:
figure;
x1 = rand(1,10);
x2 = rand(1,10);
x3 = rand(1,10);
scatter3(x1,x2,x3,80,'o','filled'); hold on
ax = gca;
ax.Projection='perspective';
the results is unimpressive:
I tried modifying ax.CameraTarget, the ax.CameraPosition, etc. but the it's nearly impossible to modify the "vanishing point" to look more distorted, e.g. like this:
Any help as to how to achieve this level of control on the figure appearance?
thanks!
You can achieve this by moving into the "wide angle" regime, i.e. choosing a large CameraViewAngle while moving the camera further towards the plot. That gives you quite some distortion, like you might know from wide angle camera lenses.
In the following code, I assumed that the camera is placed at the center z-level of the coordinate system and x and y-coordinates are displaced just at the connection line between two edge points of the coordinate system being back-shifted by its length. The rest is basically "law of cosines". Feel free to adapt the camera position within the given sensible levels, such that the view angle remains in the range between 0 and 180 degrees, https://de.mathworks.com/help/matlab/ref/matlab.graphics.axis.axes-properties.html#budumk7-View). This example assumes that the view angle in the x-y-plane is about the same as in the y-z-plane. Otherwise one would need to extend the calculation and take the average or maximum angle of both.
figure;
x1 = rand(1, 10) + 10;
x2 = rand(1, 10) + 15;
x3 = rand(1, 10) + 10;
scatter3(x1, x2, x3, 80, 'o', 'filled');
% get the axis limits
xax = xlim; yax = ylim; zax = zlim;
ax = gca;
ax.Projection = 'perspective';
% get the default camera target (where the cam looks at)
camTarget = get(ax, 'CameraTarget');
newCamPos = [ 2*xax(1)-xax(2) 2*yax(1)-yax(2) camTarget(3) ];
a = sqrt((xax(1)-newCamPos(1))^2 + (yax(2)-newCamPos(2))^2);
b = sqrt((xax(2)-newCamPos(1))^2 + (yax(1)-newCamPos(2))^2);
lsqr = (xax(2)-xax(1))^2 + (yax(2)-yax(1))^2;
angle = acos((lsqr - a^2 - b^2) / -(2*a*b)) *180/pi;
set(ax, 'CameraViewAngle', angle, ...
'CameraPosition', newCamPos);

Creating a circle in a square grid

I try to solve the following 2D elliptic PDE electrostatic problem by fixing the Parallel plate Capacitors code. But I have problem to plot the circle region. How can I plot a circle region rather than the square?
% I use following two lines to label the 50V and 100V squares
% (but it should be two circles)
V(pp1-r_circle_small:pp1+r_circle_small,pp1-r_circle_small:pp1+r_circle_small) = 50;
V(pp2-r_circle_big:pp2+r_circle_big,pp2-r_circle_big:pp2+r_circle_big) = 100;
% Contour Display for electric potential
figure(1)
contour_range_V = -101:0.5:101;
contour(x,y,V,contour_range_V,'linewidth',0.5);
axis([min(x) max(x) min(y) max(y)]);
colorbar('location','eastoutside','fontsize',10);
xlabel('x-axis in meters','fontsize',10);
ylabel('y-axis in meters','fontsize',10);
title('Electric Potential distribution, V(x,y) in volts','fontsize',14);
h1=gca;
set(h1,'fontsize',10);
fh1 = figure(1);
set(fh1, 'color', 'white')
% Contour Display for electric field
figure(2)
contour_range_E = -20:0.05:20;
contour(x,y,E,contour_range_E,'linewidth',0.5);
axis([min(x) max(x) min(y) max(y)]);
colorbar('location','eastoutside','fontsize',10);
xlabel('x-axis in meters','fontsize',10);
ylabel('y-axis in meters','fontsize',10);
title('Electric field distribution, E (x,y) in V/m','fontsize',14);
h2=gca;
set(h2,'fontsize',10);
fh2 = figure(2);
set(fh2, 'color', 'white')
You're creating a square due to the way you're indexing (see this post on indexing). You've specified the rows to run from pp1-r_circle_small to pp1+r_circle_small and similar for the columns. Given that Swiss cheese is not an option, you're creating a complete square.
From geometry we know that all points within distance sqrt((X-X0)^2 - (Y-Y0)^2) < R from the centre of the circle at (X0,Y0) with radius R are within the circle, and the rest outside. This means that you can simply build a mask:
% Set up your grid
Xsize = 30; % Your case: 1
Ysize = 30; % Your case: 1
step = 1; % Amount of gridpoints; use 0.001 or something
% Build indexing grid for circle search, adapt as necessary
X = 0:step:Xsize;
Y = 0:step:Ysize;
[XX,YY] = meshgrid(X, Y);
V = zeros(numel(X), numel(Y));
% Repeat the below for both circles
R = 10; % Radius of your circle; your case 0.1 and 0.15
X0 = 11; % X coordinate of the circle's origin; your case 0.3 and 0.7
Y0 = 15; % Y coordinate of the circle's origin; your case 0.3 and 0.7
% Logical check to see whether a point is inside or outside
mask = sqrt( (XX - X0).^2 + (YY - Y0).^2) < R;
V(mask) = 50; % Give your circle the desired value
imagesc(V) % Just to show you the result
axis equal % Use equal axis to have no image distortion
mask is a logical matrix containing 1 where points are within your circle and 0 where points are outside. You can then use this mask to logically index your potential grid V to set it to the desired value.
Note: This will, obviously, not create a perfect circle, given you cannot plot a perfect circle on a square grid. The finer the grid, the more circle-like your "circle" will be. This shows the result with step = 0.01
Note 2: You'll need to tweek your definition of X, Y, X0, Y0 and R to match your values.

Calculate volume of hollow tilted cylinder inside cube, without a grid

I have a hollow cylinder with inner radius r1 and outer radius r2. This cylinder is tilted from the Z axis towards the X axis, then rotated around the Z axis and then translated along X and along Y. I also have a cube between a range of X and Y values. I then want to calculate how large a volume of the hollow cylinder is inside the cube.
%set variables
r1 = 0.9; r2 = 1.0;
z1 =-1.0; z2 = 1.0;
beta = pi/4; gamma = pi/8;
%create inner surface
[X,Y,Z] = cylinder(r1, 100);
%outer surface
[Xx,Yy,Zz] = cylinder(r2, 100);
%fix coordinates
X = [X,Xx]; Y = [Y,Yy]; Z = [Z,Zz];
Z(1,:) = z1; Z(2,:) = z2;
%elongate in first rotation direction
X = X/cos(beta);
%shift top and bottom accordingly
X(1,:) = X(1,:) + z2/tan(beta);
X(2,:) = X(2,:) + z1/tan(beta);
%also perform another rotation in XY plane
Xnu = X*cos(gamma) + -Y*sin(gamma);
Y = X*sin(gamma) + Y*cos(gamma);
X = Xnu;
%translation not included for this example
surf(X,Y,Z); axis equal
%try to make a cube from its nodes/corners
ix=-1;iy=-1;dx=0.5;dy=dx;
[X,Y,Z] = ndgrid( ix*dx+[-dx/2, dx/2] , iy*dy+[-dy/2, dy/2] , [z1, z2] );
X = reshape(X, 2, []);Y = reshape(Y, 2, []);Z = reshape(Z, 2, []);
%failed
surf(X,Y,Z); axis equal
The hollow cylinder seems OK. Just no 'cap' on the top of the edge.
The cube looks nothing like a cube, but at least the volume between the corners should be correct.
My question is, how do I get the intersection of these two shapes and then calculate the volume?
I actually need to iterate over many of these tubes and cubes, but once I have this single case fixed then it should be easy. Each cube represents a measured pixel from an experiment and each hollow cylinder a physical object that was in the experiment. When simulating this on a meshgrid itself I encounter memory issues very fast.

Shade a subset of a sphere's surface in MATLAB

I'm attempting to generate a plot of a hemisphere with a shaded area on the surface bound by max/min values of elevation and azimuth. Essentially I'm trying to reproduce this:
Generating the hemisphere is easy enough, but past that I'm stumped. Any ideas?
Here's the code I used to generate this sphere:
[x,y,z] = sphere;
x = x(11:end,:);
y = y(11:end,:);
z = z(11:end,:);
r = 90;
surf(r.*x,r.*y,r.*z,'FaceColor','yellow','FaceAlpha',0.5);
axis equal;
If you want to highlight a certain area of your hemisphere, first you decide the minimum and maximum azimuth (horizontal sweep) and elevation (vertical sweep) angles. Once you do this, take your x,y,z co-ordinates and convert them into their corresponding angles in spherical co-ordinates. Once you do that, you can then subset your x,y,z co-ordinates based on these angles. To convert from Cartesian to spherical, you would thus do:
Source: Wikipedia
theta is your elevation while phi is your azimuth. r would be the radius of the sphere. Because sphere generates co-ordinates for a unit sphere, r = 1. Therefore, to calculate the angles, we simply need to do:
theta = acosd(z);
phi = atan2d(y, x);
Take note that the elevation / theta is restricted 0 to 180 degrees, while the azimuth / phi is restricted between -180 to 180 degrees. Because you're only creating half of a sphere, the elevation should simply vary from 0 to 90 degrees. Also note that acosd and atan2d return the result in degrees. Now that we're here, you just have to subset what part of the sphere you want to draw. For example, let's say we wanted to restrict the sphere such that the min. and max. azimuth span from -90 to 90 degrees while the elevation only spans from 0 to 45 degrees. As such, let's find those x,y,z co-ordinates that satisfy these constraints, and ensure that anything outside of this range is set to NaN so that these points aren't drawn on the sphere. As such:
%// Change your ranges here
minAzimuth = -90;
maxAzimuth = 90;
minElevation = 0;
maxElevation = 45;
%// Compute angles - assuming that you have already run the code for sphere
%// [x,y,z] = sphere;
%// x = x(11:end,:);
%// y = y(11:end,:);
%// z = z(11:end,:);
theta = acosd(z);
phi = atan2d(y, x);
%%%%%// Begin highlighting logic
ind = (phi >= minAzimuth & phi <= maxAzimuth) & ...
(theta >= minElevation & theta <= maxElevation); % // Find those indices
x2 = x; y2 = y; z2 = z; %// Make a copy of the sphere co-ordinates
x2(~ind) = NaN; y2(~ind) = NaN; z2(~ind) = NaN; %// Set those out of bounds to NaN
%%%%%// Draw our original sphere and then the region we want on top
r = 90;
surf(r.*x,r.*y,r.*z,'FaceColor','white','FaceAlpha',0.5); %// Base sphere
hold on;
surf(r.*x2,r.*y2,r.*z2,'FaceColor','red'); %// Highlighted portion
axis equal;
view(40,40); %// Adjust viewing angle for better view
... and this is what I get:
I've made the code modular so that all you have to do is change the four variables that are defined at the beginning of the code, and the output will highlight that desired part of the hemisphere that are bounded by those min and max ranges.
Hope this helps!
One option is to create an array with the corresponding color you want to attribute to each point.
A minimal example (use trigonometry to convert your azimuth and elevation to logical conditions on x, y, and z):
c=(y>0).*(x>0).*(z>0.1).*(z<0.5);
c(c==0)=NaN;
surf(r.*x,r.*y,r.*z,c ,'FaceAlpha',0.5); axis equal;
yields this:
Note: this only works with the resolution of the grid. (i.e each 'patch' of the surface can have a different color). To exactly reproduce your plot, you might want to superpose the grid sphere with another one that has a much larger number of grid points on which you apply the above code.

Interpolate surface of 3D cylinder in Matlab

I have a dataset that describes a point cloud of a 3D cylinder (xx,yy,zz,C):
and I would like to make a surface plot from this dataset, similar to this
In order to do this I thought I could interpolate my scattered data using TriScatteredInterp onto a regular grid and then plot it using surf:
F = TriScatteredInterp(xx,yy,zz);
max_x = max(xx); min_x = min(xx);
max_y = max(yy); min_y = min(yy);
max_z = max(zz); min_z = min(zz);
xi = min_x:abs(stepSize):max_x;
yi = min_y:abs(stepSize):max_y;
zi = min_z:abs(stepSize):max_z;
[qx,qy] = meshgrid(xi,yi);
qz = F(qx,qy);
F = TriScatteredInterp(xx,yy,C);
qc = F(qx,qy);
figure
surf(qx,qy,qz,qc);
axis image
This works really well for convex and concave objects but ends in this for the cylinder:
Can anybody help me as to how to achieve a nicer plot?
Have you tried Delaunay triangulation?
http://www.mathworks.com/help/matlab/ref/delaunay.html
load seamount
tri = delaunay(x,y);
trisurf(tri,x,y,z);
There is also TriScatteredInterp
http://www.mathworks.com/help/matlab/ref/triscatteredinterp.html
ti = -2:.25:2;
[qx,qy] = meshgrid(ti,ti);
qz = F(qx,qy);
mesh(qx,qy,qz);
hold on;
plot3(x,y,z,'o');
I think what you are loking for is the Convex hull function. See its documentation.
K = convhull(X,Y,Z) returns the 3-D convex hull of the points (X,Y,Z),
where X, Y, and Z are column vectors. K is a triangulation
representing the boundary of the convex hull. K is of size mtri-by-3,
where mtri is the number of triangular facets. That is, each row of K
is a triangle defined in terms of the point indices.
Example in 2D
xx = -1:.05:1; yy = abs(sqrt(xx));
[x,y] = pol2cart(xx,yy);
k = convhull(x,y);
plot(x(k),y(k),'r-',x,y,'b+')
Use plot to plot the output of convhull in 2-D. Use trisurf or trimesh to plot the output of convhull in 3-D.
A cylinder is the collection of all points equidistant to a line. So you know that your xx, yy and zz data have one thing in common, and that is that they all should lie at an equal distance to the line of symmetry. You can use that to generate a new cylinder (line of symmetry taken to be z-axis in this example):
% best-fitting radius
% NOTE: only works if z-axis is cylinder's line of symmetry
R = mean( sqrt(xx.^2+yy.^2) );
% generate some cylinder
[x y z] = cylinder(ones(numel(xx),1));
% adjust z-range and set best-fitting radius
z = z * (max(zz(:))-min(zz(:))) + min(zz(:));
x=x*R;
y=y*R;
% plot cylinder
surf(x,y,z)
TriScatteredInterp is good for fitting 2D surfaces of the form z = f(x,y), where f is a single-valued function. It won't work to fit a point cloud like you have.
Since you're dealing with a cylinder, which is, in essence, a 2D surface, you can still use TriScatterdInterp if you convert to polar coordinates, and, say, fit radius as a function of angle and height--something like:
% convert to polar coordinates:
theta = atan2(yy,xx);
h = zz;
r = sqrt(xx.^2+yy.^2);
% fit radius as a function of theta and h
RFit = TriScatteredInterp(theta(:),h(:),r(:));
% define interpolation points
stepSize = 0.1;
ti = min(theta):abs(stepSize):max(theta);
hi = min(h):abs(stepSize):max(h);
[qx,qy] = meshgrid(ti,hi);
% find r values at points:
rfit = reshape(RFit(qx(:),qy(:)),size(qx));
% plot
surf(rfit.*cos(qx),rfit.*sin(qx),qy)