Text Animation with MATLAB

2019-01-13 22:38发布

My simplified problem is to animate some text on a 3D plot.

I have a cube,

vert = [1 1 0;0 1 0;0 1 1;1 1 1;0 0 1;1 0 1;1 0 0;0 0 0];
fac =  [1 2 3 4; 4 3 5 6; 6 7 8 5; 1 2 8 7; 6 7 1 4; 2 3 5 8];
patch('Faces',fac,'Vertices',vert,'FaceColor',[.8 .5 .2]); 
axis([0, 1, 0, 1, 0, 1]);
axis equal
axis off

enter image description here

Is it possible to get something like this?

enter image description here

Using text doesn't help (it looks fake!),

Thanks,

2条回答
兄弟一词,经得起流年.
2楼-- · 2019-01-13 23:25

I have a solution which renders ok by using texture mapping.

The idea is to apply an image to the face and let Matlab take care of everything else. The great advantage is that matlab will take care of all the perspective aspects, and the rendering is pretty good. The small disadvantage is that you can only apply texture to surface objects, and since you want 6 different images, you'll have to manage 6 different surfaces.

The code below shows an example of how to do it:

%% // read faces images
idxFaces = [1 2 3 4 5 6] ;
for iface = idxFaces
    imgface{iface} = imread( ['E:\ICONS\number_blue_' num2str(iface) '-150x150.png'] ) ;
end

%% // Define cube properties
cubeLenght = 1 ;
x = linspace(-cubeLenght/2,cubeLenght/2,21) ;
[X,Y] = meshgrid(x,x) ;
Zp = ones(size(X))*cubeLenght/2 ; Zm = Zp-cubeLenght ;

%// draw face surfaces (organised 2 by 2)
hcubeface(1) = surf(X,Y,Zp ,'CData',imgface{1},'FaceColor','texturemap','LineStyle','none') ; hold on
hcubeface(6) = surf(X,Y,Zm ,'CData',imgface{6},'FaceColor','texturemap','LineStyle','none') ;

hcubeface(2) = surf(X,Zp,Y ,'CData',imgface{2},'FaceColor','texturemap','LineStyle','none') ;
hcubeface(5) = surf(X,Zm,Y ,'CData',imgface{5},'FaceColor','texturemap','LineStyle','none') ;

hcubeface(3) = surf(Zp,X,Y ,'CData',imgface{3},'FaceColor','texturemap','LineStyle','none') ;
hcubeface(4) = surf(Zm,X,Y ,'CData',imgface{4},'FaceColor','texturemap','LineStyle','none') ;

axis square
axis off

This will render the following figure:
dice

Sorry for the actual images I took the first one I had available. You will have to generate a nice image of your cube faces, then apply them as shown above.

If you rotate your cube with the figure interactive tool, you will have no problem.
If you want to rotate it programatically, you have 2 options:

  • if the object is the only one displayed, then the easiest is to only move the point of view (using view or other camera manipulation functions).

  • if you have multiple objects and you only want to rotate your cube, then you will have to rotate each surface individually. In this case I would suggest writing a helper function which will rotate the 6 faces for you given an angle and a direction. An even neater way would be to have your cube managed in a class, then add a method to rotate it (internally it will rotate the 6 faces).


Edit: just for fun, the snippet below will animate your dice (not the pretiest way but it shows you an example of the first option above).

%% // animate by changing point of view
azi = linspace(-180,180,100) ;
for iv =1:100
    view(azi(iv),azi(iv)+randi(5))
    pause(0.01)
end
view(45,45)

Run this block to see your dice dancing ;)


Edit (again)
And this is an example on how to animate the cube object by rotating the object itself

%% // animate by rotating the object

%// this is necessary because the first rotation reset the axes to shading interp
rotate(hcubeface , [0 1 1] , 0.5)
for iface = 1:6 ; set( hcubeface(iface) , 'CData',imgface{iface}, 'FaceColor','texturemap' ) ; end

%// after that, no need to reset the texture map, enjoy as many rotations as you like
for iv =1:360
    axerot = rand(1,3) ;                % // pick a random axis to rotate around
    rotate(hcubeface , axerot , 0.5)    %// rotate the 6 faces by 0.5 degrees
    pause(0.01)
end

Note that I modified the definition of the cube in order to have all the face surfaces handles in one array. This allow to apply the rotate command to all the surfaces in one go just by sending the handle array as parameter.

查看更多
Emotional °昔
3楼-- · 2019-01-13 23:33

The idea is to use texture mapping as @Hoki showed. I tried to implement this on my end, here is what I came up with.

First we'll need 6 images to apply on the cube faces. These can be any random images of any size. For example:

% just a bunch of demo images from IPT toolbox
function imgs = get_images()
    imgs = {
        imread('autumn.tif');
        imread('coloredChips.png');
        imread('toysflash.png');
        imread('football.jpg');
        imread('pears.png');
        imread('peppers.png');
    };
end

Better yet, let's use an online service that returns placeholder images containing digits 1 to 6:

% online API for placeholder images
function imgs = get_images()
    imgs = cell(6,1);
    clr = round(255*brighten(lines(6),0.75));
    for i=1:6
        %bg = randsample(['0':'9' 'a':'f'], 6, true);
        %fg = randsample(['0':'9' 'a':'f'], 6, true);
        bg = strjoin(cellstr(dec2hex(clr(i,:))).', '');
        fg = strjoin(cellstr(dec2hex(clr(7-i,:))).', '');
        [img,map] = imread(sprintf(...
            'http://placehold.it/100x100/%s/%s&text=%d', bg, fg, i));
        imgs{i} = im2uint8(ind2rgb(img,map));
    end
end

Here are the resulting images:

>> imgs = get_images();
>> montage(cat(4,imgs{:}))

digit_images

Next let's create a function that renders a unit cube with images texture-mapped as faces:

function h = get_unit_cube(imgs)
    % we need a cell array of 6 images, one for each face (they can be any size)
    assert(iscell(imgs) && numel(imgs)==6);

    % coordinates for unit cube
    [D1,D2,D3] = meshgrid([-0.5 0.5], [-0.5 0.5], 0.5);

    % texture mapped surfaces
    opts = {'FaceColor','texturemap', 'EdgeColor','none'};
    h = zeros(6,1);
    h(6) = surface(D1, flipud(D2), D3, imgs{6}, opts{:});           % Z = +0.5 (top)
    h(5) = surface(D1, D2, -D3, imgs{5}, opts{:});                  % Z = -0.5 (bottom)
    h(4) = surface(fliplr(D1), D3, flipud(D2), imgs{4}, opts{:});   % Y = +0.5 (right)
    h(3) = surface(D1, -D3, flipud(D2), imgs{3}, opts{:});          % Y = -0.5 (left)
    h(2) = surface(D3, D1, flipud(D2), imgs{2}, opts{:});           % X = +0.5 (front)
    h(1) = surface(-D3, fliplr(D1), flipud(D2), imgs{1}, opts{:});  % X = -0.5 (back)
end

Here is what it looks like:

imgs = get_images();
h = get_unit_cube(imgs);
view(3), axis vis3d, rotate3d on

unit_cube


Now we can have some fun with this creating interesting animations. Consider the following:

% create two separate unit cubes
figure('Renderer','OpenGL')
h1 = get_unit_cube(get_images());
h2 = get_unit_cube(get_images());
set([h1;h2], 'FaceAlpha',0.8)         % semi-transparent
view(3), axis vis3d off, rotate3d on

% create transformation objects, used as parents of cubes
t1 = hgtransform('Parent',gca);
t2 = hgtransform('Parent',gca);
set(h1, 'Parent',t1)
set(h2, 'Parent',t2)

% transform the second cube (scaled, rotated, then shifted)
M = makehgtform('translate', [-0.7 1.2 0.5]) * ...
    makehgtform('yrotate', 15*(pi/180)) * ...
    makehgtform('scale', 0.5);
set(t2, 'Matrix',M)

drawnow
axis on, axis([-2 2 -2 2 -0.7 1]), box on
xlabel x, ylabel y, zlabel z

% create animation by rotating cubes 5 times
% (1st rotated around z-axis, 2nd around its own z-axis in opposite
%  direction as well as orbiting the 1st)
for r = linspace(0,10*pi,90)
    R = makehgtform('zrotate', r);
    set(t1, 'Matrix',R)
    set(t2, 'Matrix',R\(M/R))
    pause(0.1)
end

orbiting_animation

I'm using the hgtransform function to manage transformations, this is much more efficient than continuously changing the x/y/z data points of the graphics objects.

BTW I've used slightly different images in the animation above.


EDIT:

Let's replace the rotating cubes with images of planet earth mapped onto spheres. First here are two functions to render the spheres (I'm borrowing code from these examples in the MATLAB documentation):

get_earth_sphere1.m

function h = get_earth_sphere1()
    % read images of planet earth
    earth = imread('landOcean.jpg');
    clouds = imread('cloudCombined.jpg');

    % unit sphere with 35x35 faces
    [X,Y,Z] = sphere(35);
    Z = flipud(Z);
    a = 1.02;

    % render first sphere with earth mapped onto the surface,
    % then a second transparent surface with clouds layer
    if verLessThan('matlab','8.4.0')
        h = zeros(2,1);
    else
        h = gobjects(2,1);
    end
    h(1) = surface(X, Y, Z, earth, ...
        'FaceColor','texturemap', 'EdgeColor','none');
    h(2) = surface(X*a, Y*a, Z*a, clouds, ...
        'FaceColor','texturemap', 'EdgeColor','none', ...
        'FaceAlpha','texturemap', 'AlphaData',max(clouds,[],3));
end

get_earth_sphere2.m

function h = get_earth_sphere2()
    % load topographic data
    S = load('topo.mat');
    C = S.topo;
    cmap = S.topomap1;
    n = size(cmap,1);

    % convert altitude data and colormap to RGB image
    C = (C - min(C(:))) ./ range(C(:));   % scale to [0,1]
    C = ind2rgb(round(C*(n-1)+1), cmap);  % convert indexed to RGB

    % unit sphere with 50x50 faces
    [X,Y,Z] = sphere(50);

    % render sphere with earth mapped onto the surface
    h = surface(X, Y, Z, C, ...
        'FaceColor','texturemap', 'EdgeColor','none');
end

The animation script is similar to before (with minor changes), so I'm not gonna repeat it. Here is the result:

planets

(This time I'm using the new graphics system in R2014b)

查看更多
登录 后发表回答