Matplotlib Animate does not render when blit is set to be True - matplotlib-animation

I am trying to use animation.FuncAnimation within a function. The goal is to make an animated demonstration of an orbit in a two body model.
The function that calls the animation is setup as follows:
def plot_animate(r, bod_rad, steps, dt):
#Setup plot environment
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(111, projection='3d')
max_val = np.max(np.abs(r))
ax.set_xlim([-max_val, max_val])
ax.set_ylim([-max_val, max_val])
ax.set_zlim([-max_val, max_val])
#Plot background and body to orbit
ax = plot_bckground(ax, bod_rad)
#Setup initial position and current position
ax.plot([r[0,0]],[r[0, 1]], [r[0,2]],'ko', label='Starting Position', zorder=20)
orb, = ax.plot(r[0,0], r[0,1], r[0,2], 'k--', label='trajectory', zorder=10)
pos, = ax.plot([r[0,0]],[r[0, 1]], [r[0,2]],'go', label='Current Position', zorder=10)
#Animate trajectory
anime = animation.FuncAnimation(fig, orbit_anim, fargs=(ax, r, orb, pos),\
frames=steps, interval=dt, blit=True)
plt.legend()
plt.show()
plt_background adds the plot of a sphere and a starting point.
orbit_anim looks as follows:
def orbit_anim(frame, ax, r, orb, pos):
#Trajectory and current position implementation to animate the satellite
orb = ax.plot(r[:frame+1, 0], r[:frame+1, 1], r[:frame+1, 2], 'k--', label='trajectory', zorder=10)
pos.set_data(r[frame, 0], r[frame, 1])
pos.set_3d_properties(r[frame, 2], 'z')
return orb
The code works as intended when blit is false. The green "Current Position" dot leads the orbital trajectory line and is rendered.
However, when blit is set to true, the code still works, but the green "Current position" is no longer automatically rendered. It only shows up when I change the perspective of the 3d plot view and no longer leads the trajectory.

The error was caused because instead of updating the value, I rendered the entire line again, which I am guessing would override the render of the pos artist object. The rendering worked when I switched into changing the data instead:
def orbit_anim(frame, ax, r, orb, pos):
orb.set_data(r[:frame+1, 0], r[:frame+1, 1])
orb.set_3d_properties(r[:frame+1, 2], 'z')
pos.set_data(r[frame, 0], r[frame, 1])
pos.set_3d_properties(r[frame, 2], 'z')
return orb, pos

Related

Matlab - Add a specific tick on a colorbar

I'm representing a surface using "surf" function, with a colorbar. I would like to keep the default ticks of the colorbar, but add a custom tick on this colorbar, at a specific value (that I could make red to distinguish it from other ticks for example). Any idea on how to add a custom tick like that with keeping existing ticks on the colorbar ?
Thanks
As Luis mentioned in the comments, you can add an additional tick mark like so
h = colorbar;
newTick = 0.75;
h.Ticks = sort([h.Ticks newTick]);
If you want to add a line to the bar, the easiest thing (I think) is to use an annotation which is positioned relative to the figure (the same as the colorbar), so we can overlay it
pos = h.Position;
r = (newTick - min(h.Ticks))/(max(h.Ticks)-min(h.Ticks));
annotation( 'line', pos(1)+[0, pos(3)], [1, 1]*(pos(2)+pos(4)*r), ...
'color', [1,0,0], 'linewidth', 2 );
I'm setting the x position of the annotation to match the left and right sides of the colorbar, and the y position to match the bottom plus the relative % of the height according to the tick value.
Result:
Similarly, you could use annotatation exclusively to just get a red label, it's a little more convoluted to get everything lined up correctly, you have to make sure the text box is wide enough to be on a single line and vertically aligned to the middle to get the position right:
h = colorbar;
newTick = 0.75;
pos = h.Position;
r = (newTick - min(h.Ticks))/(max(h.Ticks)-min(h.Ticks));
h = 0.2;
annotation( 'textbox', [pos(1)+pos(3)/2, (pos(2)+pos(4)*r)-(h/2), pos(3)*2, h], ...
'color', [1,0,0], 'string', ['- ' num2str(newTick)], 'linestyle', 'none', ...
'VerticalAlignment', 'middle' );

How can I create a camera fly-through effect in MATLAB?

I would like to implement the fly-through effect as shown in this nice example. First I created a figure from matrix hm, a 300X300 matrix by using the surf function:
surf(hm);
Then, I defined an animated line from variables x, y, and z and displayed on the figure as follows:
curve = animatedline;
curve.LineWidth = 6;
curve.Color = [ 1 0 1];
for i = 1:length(x)
addpoints(curve, x(i), y(i), z(i));
drawnow;
end
Then, I wanted to implement the fly-through effect so that the camera will move along the line. I tried this code piece I took from the example above and I slightly modified it:
for i = 1:length(x)
campos([(x(i) -5), (y(i)-5), 0]);
camtarget([x(i), y(i), z(i)]);
drawnow;
end
But the camera doesn't move as I intended. What am I doing wrong?
If you want to mimic the behavior of the linked example, you need to have both the camera target and camera position moving along your curve defined by (x, y, z). The way you've written it above, the camera target moves along the curve, but the camera position is always offset from the target by (-5, -5) in the xy plane and sitting at z = 0. If you want the camera to follow the curve behind the target, you should try something like this:
for iPoint = 6:numel(x)
campos([x(iPoint-5) y(iPoint-5) z(iPoint-5)]); % Note the index is shifted, not the value
camtarget([x(iPoint) y(iPoint) z(iPoint)]);
drawnow;
end
If you don't want the camera moving along the same curve, and instead want it to always be at a fixed offset from your moving camera target, you could try this:
offset = [-5 -5 0]; % X, Y, and Z offset from target
for iPoint = 1:numel(x)
campos([x(iPoint)+offset(1) y(iPoint)+offset(2) z(iPoint)+offset(3)]);
camtarget([x(iPoint) y(iPoint) z(iPoint)]);
drawnow;
end
Finally, if you'd like to control the speed of the animation, you can replace the drawnow command with a call to pause. Note that a call to pause is equivalent to a call to drawnow in that it will force an update of graphics objects. You can also animate graphics using a timer object, as I illustrate in this answer.

How do I break the game when objects collide on Matlab?

I made this small game similar to flappy birds, where one can use to fly up and down using the mouse and keyboard.
I won't be posting the full code because it's a university project and I don't want to get caught for any possible plagiarism.
What I did was, I used two objects:
A rectangle as the bird:
r= rectangle('Position', pos, 'curvature', [0.99,0.1], 'FaceColor', 'r', 'EraseMode','xor');
Thick lines to represent the walls:
line([ 100 100], [10 400], 'color', 'm', 'LineWidth', 10, 'EraseMode', 'background')
My problem:
The problem is that the bird moves through the walls, as if the walls were transparent. As you can imagine, I want to break the game and show something like "game over", when the bird hits the wall (not go through them). How do I make it such that my game breaks when the bird (object 1) collides with the walls (other objects)?
Thank You Very Much for reading my question!
If you have the Mapping Toolbox installed, it provides a function called polyxpoly that allows you to intersect polylines, so you can find the intersection of the whole rectangle against every wall, and you will not have to worry about splitting every edge of the rectangle.
Here you have a full working example of a collision with one wall:
% Bird (rectangle).
% Position.
xr = 30;
yr = 100;
% Length of rectangle edges.
deltaxr = 10;
deltayr = 10;
% Vector for rectangle object.
vr = [xr, yr, deltaxr, deltayr];
% Bird polyline.
a = [xr, yr];
b = [xr + deltaxr, yr];
c = [xr + deltaxr, yr + deltayr];
d = [xr, yr + deltayr];
r = [a; b; c; d];
% Wall (line).
% Wall polyline.
l = [40 0; 40 105];
% Draw objects.
r1 = rectangle('Position',vr,'LineWidth',2);
line(l(:,1), l(:,2),'LineWidth',2,'Color','r');
axis equal;
% Find intersections.
[xi,yi] = polyxpoly(r(:,1),r(:,2),l(:,1),l(:,2));
% Are there any intersections? If so, GAME OVER.
if ~isempty(xi)
% Stop the game and display GAME OVER.
text(xr-20,yr-20,'GAME OVER','Color','b','FontSize', 20);
end
You can try with different positions of the bird to test the collision detection:
You'd want to use an intersection algorithm to check whether there is an intersection of one of the wall-lines (check it four times) with the rectangle. The rectangle basically also consists of 4 lines, that means you should check the 4 rectangle-lines against the four wall-lines (if you use a line-line-intersection algorithm).
E.g. check this topic:
How to find the intersection point between a line and a rectangle?
It's basically a simple mathematical equation to solve, see https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection.

Make background of annotation box in image semi-transparent

I am currently working on adding annotations to satellite images in MATLAB. Since the color underneath each text field can vary quite a lot, I want to use a background color under the text to make it easier to see and read.
However, when I do this, a lot of the terrain gets obscured. I though of trying to make the background color for each text box semi transparent, but hit a dead end trying to come up with a solution.
Any ideas? I was hoping for some UI-element where I could just set the 'facealpha' to maybe 0.5. I also need the text to support being rotated (as can be seen in the examples below).
Below is some example code and the resulting image. The workspace with the satellite data can also be found in the link:
Example workspace
figure(1);clf
imagesc(xx,yy,Map);
hold on
plot(xInspection,yInspection,'g.-')
% # Two ways of making a rotated text annotation.
% # Cant make background semi-transparent
testAnno= annotation('textarrow',[0.5 0.5],[0.5 0.5], ...
'string','textarrow annotation', ...
'HeadStyle','none','LineStyle', 'none',...
'TextRotation',asin(directionVec(1))*180/pi,...
'TextBackgroundColor',[0.7 0.7 0.7]);
testText = text(mean(xInspection),mean(yInspection),'text annotation', ...
'rotation',asin(directionVec(1))*180/pi, ...
'HorizontalAlignment','right', ...
'color',[0 0 0], ...
'backgroundcolor',[0.7 0.7 0.7], ...
'fontsize',8);
It doesn't look like either annotation or text return HgObjects that have BackgroundAlpha properties (they might exist but I wasn't able to find them using getundoc or by trying various different hacks).
I was able to get something working by drawing the background myself. Here is a simple proof of concept:
f = figure;
tObj = text(.5, .5, 'text object', 'FontSize', 20);
set(gca,'XLimMode', 'manual', 'YLimMode', 'manual'); % prevent the axes from resizing automatically
p = get(tObj, 'Extent'); %Get the outer position of the text
% now create a patch around the text object
pObj = patch([p(1) p(1) p(1)+p(3) p(1)+p(3)], [p(2) p(2)+p(4) p(2)+p(4) p(2)], 'r');
uistack(tObj, 'top'); % put the text object on top of the patch object
set(pObj , 'FaceAlpha', .2); % set the alpha of the patch face to .2
%Rotate the objects
set(tObj, 'Rotation', 20);
rotate(pObj, [0 0 1], 20);
I am afraid the only way you can do this is by not setting any color to your annotations, and then placing a patch in the background of each annotation. So something like this:
% Use completely transparent annotations
hA = annotation('textarrow', ..., 'TextBackgroundColor', 'none')
% Place a transparent patch exactly in the background of your annotation
hP = patch(X, Y, 'white', 'EdgeColor', 'none', 'FaceColor', 'white', ...
'alpha', 0.3)
% Ensure that your annotation is on top
uistack(hA, 'top')
But of course the big problem is to determine the correct coordinates of the patch (X and Y). Rotating is easy by simply multiplying your coordinates by a rotation matrix. However, finding the length and height of the patch and its central location is not that easy. You might be able to find some useful functions for this at Matlab central...

How to emphase some region on a spherical surface

Introduction
I'm trying to emphase some region on a spherical surface saying that this region should be colored as not transparent (alpha = 1.0) and other parts of the sphere should be colored as semi-transparent (alpha = 0.5).
Problem
Considering WAlpha(Data >= DummyValue) = 1.0 and WAlpha(Data < DummyValue) = 0.5, the following command does not work as expected:
surf(X, Y, Z, Data, 'AlphaData', WAlpha, 'FaceAlpha', 'interp');
It draws all non-selected region as fully-transparent:
Note
I have no issue when setting 'FaceAlpha' to scalar value (i.e its not an issue with my graphic card):
surf(X, Y, Z, Data, 'AlphaData', WAlpha, 'FaceAlpha', 0.5);
Source code
Here is the link to the very short and dummy code I created to reproduce the issue: link
Please let me know if you have any other idea for emphasing selected region rather than using transparency.
Here is quick test:
%# surface data
Z = membrane;
%# alpha-transparency matrix
A = ones(size(Z))*0.3; %# transparent by default
A(abs(Z)>0.5) = 1; %# make certain region opaque
%# plot
figure('Renderer','opengl')
surf(Z, 'AlphaData',A, 'AlphaDataMapping','none', ...
'FaceAlpha','interp', 'EdgeColor','none')
Result:
Ooops, found it...
One needs to change the Alim property on axes object because it is improperly set to [min(WAlpha) max(WAlpha)] when setting AlphaData instead of keeping [0 1]. So the command is:
surf(X, Y, Z, Data, 'AlphaData', WAlpha, 'FaceAlpha', 'interp');
alim([0 1]);