Code covered by the BSD License  

Highlights from
Virtual Tackball

image thumbnail
from Virtual Tackball by Tobias
Virtual trackball to rotate the view of a 3-D plot interactively.

trackball(varargin)
function trackball(varargin)
%TRACKBALL Interactively rotate the view of a 3D plot with a trackball.
%   If 'FindJobj' is installed, the view can also be scaled (2nd mouse
%   button) and dragged (3rd mouse button).
%
%   TRACKBALL ON turns on mouse-based 3D rotation.
%   TRACKBALL OFF turns if off.
%   TRACKBALL by itself toggles the state.
%
%   TRACKBALL(FIG,...) works on the figure FIG.
%
%   Example:
%     sphere(36);
%     axis equal
%     trackball on
%
%   Known Bugs:
%    - Rotation is not intuitive if axis are not equal, e.g.:
%       t = 0:pi/6:4*pi; [x,y,z] = cylinder(4+cos(t),30); surf(x,y,z)
%
%   See also ROTATE3D.
%
%   Author: Tobias Maier, tobias.maier@stud.unibas.ch
%

if nargin >= 1
    arg = 'toggle';
    if ishandle(varargin{1})
        h = varargin{1};
        if ~ (isa(handle(h), 'figure'))
            error('MATLAB:trackball:InvalidHandle', 'Invalid handle');
        end
        if nargin > 1
            arg = varargin{2};
        end
    else
        % Get handle of curreng figure
        h = gcf;
        arg = varargin{1};
    end
    
    switch(lower(arg))
        case 'on',     state = 'on';
        case 'off',    state = 'off';
        case 'toggle', state = 'toggle';
        otherwise
            error('MATLAB:trackball:ActionStringUnknown', 'Unknown action string.');
    end
else
    h = gcf;
    state = 'toggle';
end

if strcmp(state, 'toggle')
    data = get(h, 'UserData');
    if isfield(data, 'trackball') && isfield(data.trackball, 'active')
        state = 'off';
    else
        state = 'on';
    end
end

% Set or unset callbacks
if strcmp(state, 'off')
    hstart  = '';
    hstop   = '';
    hjstart = '';
    
    data = get(h, 'UserData');
    data.trackball = [];
    set(h, 'UserData', data);
else
    hstart  = @start;
    hstop   = @stop;
    hjstart = {@start, h};
    
    data = get(h, 'UserData');
    data.trackball = [];
    data.trackball.active = 1;
    set(h, 'UserData', data);
end

try
    jhandle = findjobj(h);
    set(jhandle, 'MousePressedCallback', hjstart);
catch e
    if strcmp(e.identifier, 'MATLAB:UndefinedFunction')
        % no 'findjobj' installed
        set(h, 'WindowButtonDownFcn', hstart);
    else
        rethrow(e);
    end
end

set(h, 'WindowButtonUpFcn', hstop);

end


function start(h, eventData, handle)
if nargin == 3
    % If figure is docked findjobj returns the handle of the main
    % window, so check click comes from figure or somewhere else
    classpath = java.lang.String('com.mathworks.hg');
    class= h.getClass().getName();
    if ~class.startsWith(classpath);
        return
    end
    
    % Called by java callback, set correct handle
    h = handle;
    
    if eventData.isMetaDown
        % Right
        set(h,'WindowButtonMotionFcn',@drag);
    elseif eventData.isAltDown
        % Middle
        set(h,'WindowButtonMotionFcn',@zoom);
    else
        % Left
        set(h,'WindowButtonMotionFcn',@rotate);
    end
else
    % Rotate as default
    set(h,'WindowButtonMotionFcn',@rotate);
end

% Figure dimensions
pos = get(h,'Position');
width = pos(3);
height = pos(4);

center = [width height] / 2;
radius = sqrt(width^2 + height^2) / 2;

% Mouse xyz-coordinates on virtual sphere
xy = get(h, 'CurrentPoint');
xy_ctr = xy - center;
xyz = get_xyz(xy_ctr(1), xy_ctr(2), radius);

% Rotation from eye to world coordinates
v = normr(camtarget - campos);
u = normr(camup);
r = normr(cross(v, u));
R0 = [r; u; -v]';

% Save data
data = get(h, 'UserData');

data.trackball.xy0    = xy;
data.trackball.xyz0   = xyz;
data.trackball.width  = width;
data.trackball.height = height;
data.trackball.center = center;
data.trackball.radius = radius;

data.trackball.campos0    = campos;
data.trackball.camup0     = camup;
data.trackball.camtarget0 = camtarget;
data.trackball.camva0     = camva;
data.trackball.R0         = R0(1:3,1:3);
data.trackball.daspect0   = daspect;

set(h, 'UserData', data);

end


function stop(h, eventData)
set(h, 'WindowButtonMotionFcn', '');

data = get(h, 'UserData');
data.trackball = [];
data.trackball.active = 1;
set(h, 'UserData', data);

end


function rotate(h, eventData)
% TODO: take care of data aspect ratio
data = get(h, 'UserData');
tb = data.trackball;

% Mouse xyz-coordinates on virtual sphere
xy = get(h, 'CurrentPoint') - tb.center;
xyz = get_xyz(xy(1), xy(2), tb.radius);

% Rotation axis in eye coordinates
v = cross(xyz, tb.xyz0);
v = normc(v(:));

% Transform rotation axis to world coordinates
v = tb.R0 * v;
v_world = v;

% Rotation angle
a = acos(tb.xyz0 * xyz');

% Rotate the camera
R = makehgtform('axisrotate', v, a);
campos(R(1:3,1:3) * tb.campos0');
camup(R(1:3,1:3) * tb.camup0');

end


function drag(h, eventData)
data = get(h, 'UserData');
tb = data.trackball;

% Relative Position
xy = get(h, 'CurrentPoint');
delta = tb.xy0 - xy;
dx = 2 * delta(1) / tb.width  ;
dy = 2 * delta(2) / tb.height  ;

% View, right and up directions
v = (tb.camtarget0 - tb.campos0) ./ tb.daspect0;
r = normr(cross(v, tb.camup0 ./ tb.daspect0));
u = normr(tb.camup0 ./ tb.daspect0);

% Field of view
fov = 2 * norm(v) * tan(tb.camva0/2*pi/180);

% Delta relative to field of view
delta = tb.daspect0 .* (fov/2 .* ((dx * r) + (dy * u)));

% Update camera and targets position
campos(tb.campos0 + delta);
camtarget(tb.camtarget0 + delta);

end


function zoom(h, evntData)
data = get(h, 'UserData');
tb = data.trackball;

% Use up/down to zoom
xy = get(h, 'CurrentPoint');
delta = 2 * (xy(2) - tb.xy0(2)) / tb.height;

% get linear zoom factor
zoom_factor = 1 + abs(delta);
if delta < 0
    zoom_factor = 1/zoom_factor;
end

% Calculate new camera view angle
view_angle = tb.camva0 * zoom_factor;

% View angle must not be to small
if view_angle < .1;
    view_angle = .1;
end

% Update the camera view angle
camva(view_angle);

end


function xyz = get_xyz(x, y, r)
% Returns xyz coordinates on a virtual sphere with radius r
x = x/r;
y = y/r;

if x^2 + y^2 < 1
    z = sqrt( 1 - (x^2 + y^2) );
    xyz = [x y z];
else
    xyz = normr([x y 0]);
end

end

Contact us at files@mathworks.com