Code covered by the BSD License  

Highlights from
Move a 3D object with mouse in a traditional 4-view window

image thumbnail

Move a 3D object with mouse in a traditional 4-view window

by

 

27 Oct 2010 (Updated )

Interactively move a 3D object anywhere in the 3D space with a mouse.

Editor's Notes:

This file was selected as MATLAB Central Pick of the Week

TDFig(varargin)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%TDFig is an interactive matlab GUI. It displays a 3D objects in the 
%traditional 4-view window (top, front, right and camera).
%It also allows users to grasp these objects and move them anywhere in
%the 3D space.
%
%%%%Just run:
% 
%TDFig
%
%and it will generate 4 spheres for demo. 
%Grasp and move the spheres in orthogonal windows (top, front, side 
%windows) to see the effects. Any object being moved outside the window 
%screen will be deleted.
%
%I use spherical surf objects for demo. Actually, you also can generate
%'slice' and 'patch' objects with primitive or irregular shape,
%and load them into TDFig.
%
%Some buttons with special displaying options are on the right.
%
%You can do any modifications on my code. Please remember to email
%to me when you find bugs or add on some interesting features.
%
%Author: Wang Gang, dnrwg@nus.edu.sg
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



function varargout = TDFig(varargin)

gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @TDFig_OpeningFcn, ...
                   'gui_OutputFcn',  @TDFig_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end 


% --- Executes just before TDFig is made visible.
function TDFig_OpeningFcn(hObject, eventdata, handles, varargin)


%Here, I use four spherical 'surf' objects for the demo.
%In fact, users can create 'surf','slice','patch' objects and test them in 
%TDFig platform.

%XYZR: XYZ is the sphere's center position, R is the radius.
XYZR=[20 20 20 7.5;...
      40 40 40 7.5;...
      60 60 60 7.5;...
      80 80 80 7.5];
[bx,by,bz]=sphere(11);
%%%


for viewcount=1:4
    h = findobj('Tag',strcat('axes',num2str(viewcount)));
    axes(h);
    daspect([1 1 1]);

    FaceAlphaValue=0.2;
    if viewcount==4
        FaceAlphaValue=1;
    end 
    
    %Generate spherical objects and save their handles in the array 'ball'.
    for ballcount=1:size(XYZR,1)
        tempf=figure('Visible','off');
        psurf=surf(bx.*XYZR(ballcount,4)+XYZR(ballcount,1),by.*XYZR(ballcount,4)+XYZR(ballcount,2),bz.*XYZR(ballcount,4)+XYZR(ballcount,3));
        axes(h);
        
        ball(ballcount)=patch(surf2patch(psurf),...
        'Tag',strcat('Ball',num2str(ballcount)),...
        'FaceColor',[1/ballcount 1-(1/ballcount) 0.5],...
        'EdgeColor','None',...
        'FaceAlpha',FaceAlphaValue);
        
        close(tempf);
    end

    xlabel('X');ylabel('Y');zlabel('Z');
    
    axis tight;
    
    %Setup approriate view angles for all 4 views
    if viewcount==1
    title('Axial View');view(0,90);
    
    
    
    for ballcount=1:size(XYZR,1)
        %this function updates the object's shape and surface information.
        
        %the input of this function is just the object's handle. Therefore 
        %any 'surf','slice' or 'patch' objects, with primitive or irregular
        %geometry, can work here.
        move3dObject(ball(ballcount));
    end
    
    elseif viewcount==2
    title('Side View');view(90,0);
    for ballcount=1:size(XYZR,1)
        move3dObject(ball(ballcount));
    end
    
    
    elseif viewcount==3
    title('Top View');view(0,0);
    for ballcount=1:size(XYZR,1)
        move3dObject(ball(ballcount));
    end
    
    
    elseif viewcount==4
    title('3D View');view(3);
    axis vis3d;axis tight; 
    end

    camlight;lighting gouraud;
end

set(gcf,'UserData',{XYZR}); 
handles.output = hObject; 
guidata(hObject, handles);
 
function varargout = TDFig_OutputFcn(hObject, eventdata, handles)  
varargout{1} = handles.output;




function pb_resetview_Callback(hObject, eventdata, handles)

h = handles.axes1;axes(h);view(0,90);
h = handles.axes2;axes(h);view(90,0);
h = handles.axes3;axes(h);view(0,0);
h = handles.axes4;axes(h);view(3);

function pb_hideball_Callback(hObject, eventdata, handles)

ball_handles=findobj(gcf,'Type','Patch');


button_state = get(hObject,'Value');
if button_state == get(hObject,'Max') 
   for ballcount=1:size(ball_handles,1)
       set(ball_handles(ballcount),'Visible','off');
   end

elseif button_state == get(hObject,'Min') 
   for ballcount=1:size(ball_handles,1)
       set(ball_handles(ballcount),'Visible','on');
   end
end 
 

% Functions starts from here are modified from Anders Brun's moveit2 function
function move3dObject(h)  
  
gui = get(gcf,'UserData');
  
set(h,'ButtonDownFcn',@startmovit);
 
set(gcf,'UserData',gui);

 
function startmovit(src,evnt) 
gui = get(gcf,'UserData');
 
set(gcf,'PointerShapeCData',nan(16,16));
set(gcf,'Pointer','custom');
 
gui.currenthandle = src;
thisfig = gcbf();
set(thisfig,'WindowButtonMotionFcn',@movit);
set(thisfig,'WindowButtonUpFcn',@stopmovit);
 
gui.startpoint = get(gca,'CurrentPoint'); 

%Anders's 2D code : 
%set(gui.currenthandle,'UserData',{get(gui.currenthandle,'XData') get(gui.currenthandle,'YData') get(gui.currenthandle,'ZData')});
%is replaced by the code below.
%Because on top of XYZ Data, the vertices, vertex normals and faces data
%should also be updated for 3D object's graphic.

set(gui.currenthandle,'UserData',{get(gui.currenthandle,'XData')...
    get(gui.currenthandle,'YData') get(gui.currenthandle,'ZData') ...
    get(gui.currenthandle,'Vertices') get(gui.currenthandle,'VertexNormals')...
    get(gui.currenthandle,'Faces')});
 
set(gcf,'UserData',gui);
 
function movit(src,evnt) 
gui = get(gcf,'UserData');

try
if isequal(gui.startpoint,[])
    return
end
catch
end
 
pos = get(gca,'CurrentPoint')-gui.startpoint;

%my major modification starts from here. 
XYZVData = get(gui.currenthandle,'UserData');


axistag=get(gca,'Tag');

V=XYZVData{4};
VN=XYZVData{5};
F=XYZVData{6};

%%%Confine object's motion along the 3rd axis only in each view.
if strcmp(axistag,'axes1')==1 

set(gui.currenthandle,'YData',XYZVData{2} + pos(1,2));
set(gui.currenthandle,'XData',XYZVData{1} + pos(1,1));

V(:,2)=V(:,2)+pos(1,2);
V(:,1)=V(:,1)+pos(1,1);

elseif strcmp(axistag,'axes2')==1 

set(gui.currenthandle,'YData',XYZVData{2} + pos(1,2));
set(gui.currenthandle,'ZData',XYZVData{3} + pos(1,3));
 
V(:,2)=V(:,2)+pos(1,2);
V(:,3)=V(:,3)+pos(1,3);

elseif strcmp(axistag,'axes3')==1 

set(gui.currenthandle,'XData',XYZVData{1} + pos(1,3));
set(gui.currenthandle,'ZData',XYZVData{3} + pos(1,3));
 
V(:,3)=V(:,3)+pos(1,3);
V(:,1)=V(:,1)+pos(1,1);

end

%%%

%V, VN and F info will not be calculated by matlab in a new frame, 
%therefore, we must manually copy them to the new frame. 
%Otherwise, the object surface will look funny. 
set(gui.currenthandle,'Vertices',V);
set(gui.currenthandle,'VertexNormals',VN);
set(gui.currenthandle,'Faces',F);
%%%

%%%synchronize object registration in all three views
if strcmp(axistag,'axes1')==1 
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes2'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    copyobj(gui.currenthandle,gca);
     
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes3'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    copyobj(gui.currenthandle,gca);
    
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes4'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    new_h=copyobj(gui.currenthandle,gca);
    set(new_h,'FaceAlpha',1);

    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes1'));
elseif strcmp(axistag,'axes2')==1
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes1'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    copyobj(gui.currenthandle,gca);
    
 
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes3'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    copyobj(gui.currenthandle,gca);
    
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes4'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    new_h=copyobj(gui.currenthandle,gca);
    set(new_h,'FaceAlpha',1);

    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes2')); 
elseif strcmp(axistag,'axes3')==1
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes2'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    copyobj(gui.currenthandle,gca);
     
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes1'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    copyobj(gui.currenthandle,gca);
    
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes4'));
    temp_del=findobj(gca,'Tag',get(gui.currenthandle,'Tag'));
    delete(temp_del);
    new_h=copyobj(gui.currenthandle,gca);
    set(new_h,'FaceAlpha',1);

    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes3'));
end 
%%%

%my major modification ends here. 
drawnow;
 
set(gcf,'UserData',gui);

 
function stopmovit(src,evnt)
 

thisfig = gcbf();
gui = get(gcf,'UserData');
set(gcf,'Pointer','arrow');
set(thisfig,'WindowButtonUpFcn','');
set(thisfig,'WindowButtonMotionFcn','');


pos = get(gca,'CurrentPoint')-gui.startpoint;
XYZVData = get(gui.currenthandle,'UserData');

axistag=get(gca,'Tag');

if strcmp(axistag,'axes1')==1 

Current_X=XYZVData{1} + pos(1,1);
Current_Y=XYZVData{2} + pos(1,2);
Current_Z=XYZVData{3};

elseif strcmp(axistag,'axes2')==1 

Current_Y=XYZVData{2} + pos(1,2);
Current_Z=XYZVData{3} + pos(1,3);
Current_X=XYZVData{1};
 
elseif strcmp(axistag,'axes3')==1 

Current_X=XYZVData{1} + pos(1,1);
Current_Z=XYZVData{3} + pos(1,3);
Current_Y=XYZVData{2};

end


%%Codes starts from here simply removes the object once it has been dragged
%%outside the axis limits.
max_X=max(Current_X(:)); min_X=min(Current_X(:));
max_Y=max(Current_Y(:)); min_Y=min(Current_Y(:));
max_Z=max(Current_Z(:)); min_Z=min(Current_Z(:));

mid_X=(max_X+min_X)/2; mid_Y=(max_Y+min_Y)/2; mid_Z=(max_Z+min_Z)/2;

R_X=(max_X-min_X)/2; R_Y=(max_Y-min_Y)/2; R_Z=(max_Z-min_Z)/2;

X_limits=get(gca,'XLim');Y_limits=get(gca,'YLim');Z_limits=get(gca,'ZLim');

delete_it=0;
if mid_X<(X_limits(1)-R_X)
    delete_it=1;
elseif mid_X>(X_limits(2)+R_X)
    delete_it=1;
elseif mid_Y<(Y_limits(1)-R_Y)
    delete_it=1;
elseif mid_Y>(Y_limits(2)+R_Y)
    delete_it=1;
elseif mid_Z<(Z_limits(1)-R_Z)
    delete_it=1;
elseif mid_Z>(Z_limits(2)+R_Z)
    delete_it=1;
end

if delete_it==1
    currentaxis=gca;
     
    currenttag=get(gui.currenthandle,'Tag');
    
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes1'));
    temp_del=findobj(gca,'Tag',currenttag);
    delete(temp_del);
    
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes2'));
    temp_del=findobj(gca,'Tag',currenttag);
    delete(temp_del);
         
    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes3'));
    temp_del=findobj(gca,'Tag',currenttag);
    delete(temp_del);

    set(gcf,'CurrentAxes',findobj(gcf,'Tag','axes4'));
    temp_del=findobj(gca,'Tag',currenttag);
    delete(temp_del);
      
    set(gcf,'CurrentAxes',currentaxis);
    
    clear gui;
    
end
%%%


drawnow;

if exist('gui','var')
set(gui.currenthandle,'UserData','');
end
set(gcf,'UserData',[]);





Contact us