Code covered by the BSD License  

Highlights from
Sphere Collider

image thumbnail
from Sphere Collider by Ligong Han
This is a simple physics engine for simulating sphere collision.

SphereCollider(balls,ball_pos,ball_v,varargin)
function SphereCollider(balls,ball_pos,ball_v,varargin)
%SPHERECOLLIDER   Physics engine for multi-sphere collision.
%   Althogrim: scan all balls and walls after each collision
%
%   Order of UPDATE: compute acceleration at the current position ->
%   calculate velocity with the updated acceleration -> refresh position
%   with new velocity.
%
%   PROPERTIES and VARIABLES:
%   BALLs:
%    balls     : [r  m  q ]
%    ball_pos  : [x  y  z ]
%    ball_v    : [vx vy vz]
%    ball_color: [R G B alpha]
%   WALLs:
%    wall_plane : [A B C D]
%    wall_sphere: [R x y z]
%   FORCESOURCE:
%    GravitySrc : [M x y z]  (default: [])
%    CoulombSrc : [Q x y z]  (default: [])
%    GravityVec : [gx gy gz] (default: [ 0  0 -1])
%    GroundPoint: [x  y  z ] (default: [ 0  0  0])
%    grav_ball  : 1(on) OR {0(off)}
%   VARs:
%    dt        : time interval between two frames (default: 0.1)
%    tmax      : the total simulation time
%    debug_mode: graph of momentum and energy will be displayed in debug mode
%   CONSTANTs:
%    G : Newtonian constant of gravitation (default: 6.6738480e-11)
%    g : Gravitational acceleration        (default: 9.80665      )
%    k : Coulomb's constant                (default: 8.987551787e9)
%    Cr: Coefficient of restitution        (default: 1            )
%
%   NOTE:
%   Equations for collisions between elastic particles: (u for init, v for final)
%    va = (ma*ua+mb*ub+mb*Cr*(ub-ua))/(ma+mb);
%    vb = (ma*ua+mb*ub+ma*Cr*(ua-ub))/(ma+mb);
%   Distance from a point to a straight line:
%    l: (x-x1)/X == (y-y1)/Y == (z-z1)/Z; P: (x0,y0,z0)
%    d = sqrt((det([x0-x1,y0-y1;X,Y])^2+det([y0-y1,z0-z1;Y,Z])^2+det([z0-z1,x0-x1;Z,X])^2)/...
%        (X^2+Y^2+Z^2));
%
%   See also VARLIST, VARLIST2, SPHEREREPULSIONCOLLIDER, SPHERECOLLIDERST
%
%   Copyright Phymhan Studio.
%   $Revision: 0.2.5 $  $Date: 27-Mar-2013 14:55:00 $
%

%% PARAMETER
%Constants
G_newton  = 6.6738480e-11; %Newtonian constant of gravitation
g_grav    = 9.80665;       %Gravitational acceleration
k_coulomb = 8.987551787e9; %Coulomb's constant
Cr        = 1;             %Coefficient of restitution
dflt_axis_lim = [-10,10;-10,10;0,10];

%Default Values
n_arg_in = nargin;
switch n_arg_in
    case 0
        var_add = {};
    case 1
        var_add = {balls};
    case 2
        var_add = {balls,ball_pos};
    otherwise
        var_add = {balls,ball_pos,ball_v};
end
k_var = 1;
while k_var <= length(var_add)
    if ischar(var_add{k_var})
        break
    end
    k_var = k_var+1;
end
varargin = [var_add(k_var:end) varargin];
switch k_var
    case 1 %n_arg_in == 0 OR arguments start from propertyname
        balls = [1 1 0;1 1 0;1 1 0;1 1 0;1 1 0];
        ball_pos = [-8 -1 5;0 0 5;0 -4 5;6 2*sqrt(3) 5;6 4*sqrt(3) 5];
        ball_v = [2.5 0 0;0 0 0;0 0 0;0 0 0;0 0 0];
        N = size(balls,1);
    case 2
        N = size(balls,1);
        ball_pos = zeros(N,3);
        rmax = max(balls(:,1))+0.5;
        lx = dflt_axis_lim(1,1)+1+rmax:2*rmax:dflt_axis_lim(1,2)-1-rmax;
        ly = dflt_axis_lim(2,1)+1+rmax:2*rmax:dflt_axis_lim(2,2)-1-rmax;
        lz = dflt_axis_lim(3,1)+1+rmax:2*rmax:dflt_axis_lim(3,2)-1-rmax;
        [mx,my,mz] = meshgrid(lx,ly,lz);
        for kk = 1:N
            ball_pos(kk,:) = [mx(kk),my(kk),mz(kk)];
        end
        %random velocity in 3D
        ball_v = 2.5*rand(N,3);
    case 3
        N = size(balls,1);
        %random velocity in 3D
        ball_v = 2.5*rand(N,3);
    otherwise
        N = size(balls,1);
end
%Properties
ball_color = repmat([0 0.5 1 0.75],N,1);
wall_plane = [0 0 1 0;0 0 1 -10;1 0 0 -10;1 0 0 10;0 1 0 -10;0 1 0 10];
wall_sphere = [];
GravityVec  = [0 0 -1];
GravitySrc  = [];
CoulombSrc  = [];
GroundPoint = [0 0 0];
grav_ball = 0;
friction_factor = 0;
axis_lim = dflt_axis_lim;
dt = 0.1;
tmax = 100;
debug_mode = true;
play_beep = false;
dflt_layout = '2d';
show_legend = false;
varlen = n_arg_in-k_var+1;
if rem(varlen,2) == 1
    fprintf('Not enough input arguments.\nValue of "%s" is not specified.\n',...
        varargin{end})
    return
end
for kk = 1:2:varlen
    switch upper(varargin{kk})
        case {'BALLS','BALL','BALL_PROPERTY','BALL_PROPERTIES',...
                'PROPERTY','PROPERTIES','PROP'}
            balls = varargin{kk+1};
        case {'BALL_POS','POS','POSITION'}
            ball_pos = varargin{kk+1};
        case {'BALL_V','BALL_VELOCITY','BALL_VELOCITIES',...
                'VELOCITY','VELOCITIES','V'}
            ball_v = varargin{kk+1};
        case {'BALL_COLOR','BALL_COLORS','COLOR','COLORS'}
            ball_color = varargin{kk+1};
        case {'WALL_PLANE','PLANE'}
            wall_plane = varargin{kk+1};
        case {'WALL_SPHERE','SPHERE'}
            wall_sphere = varargin{kk+1};
        case 'GRAVITYVEC'
            GravityVec = varargin{kk+1};
        case {'GRAVITYSRC','GRAVITY SRC','GRAVITY_SRC',...
                'GRAVITYSOURCE','GRAVITY SOURCE','GRAVITY_SOURCE',...
                'GRAVSRC','GRAV SRC','GRAV_SRC'}
            GravitySrc = varargin{kk+1};
        case {'COULOMBSRC','COULOMB SRC','COULOMB_SRC',...
                'COULOMBSOURCE','COULOMB SOURCE','COULOMB_SOURCE'}
            CoulombSrc = varargin{kk+1};
        case {'GROUNDPOINT','GROUND POINT','GROUND_POINT'}
            GroundPoint = varargin{kk+1};
        case {'GRAV_BALL','BALL_GRAV','GRAV BALL','BALL GRAV',...
                'GRAVBALL','BALLGRAV'}
            if ischar(varargin{kk+1})
                switch upper(varargin{kk+1})
                    case 'ON'
                        grav_ball = 1;
                    case 'OFF'
                        grav_ball = 0;
                    otherwise
                        fprintf('''%s'' is not a property value of "debug_mode".\n',...
                            varargin{kk+1})
                        return
                end
            else
                grav_ball = 1;
            end
        case 'DT'
            dt = varargin{kk+1};
        case {'TMAX','T'}
            tmax = varargin{kk+1};
        case {'DEBUG_MODE','DEBUG MODE','DEBUGMODE','DEBUG'}
            if ischar(varargin{kk+1})
                switch upper(varargin{kk+1})
                    case 'ON'
                        debug_mode = true;
                    case 'OFF'
                        debug_mode = false;
                    otherwise
                        fprintf('''%s'' is not a property value of "debug_mode".\n',...
                            varargin{kk+1})
                        return
                end
            else
                debug_mode = varargin{kk+1};
            end
        case 'G_NEWTON'
            G_newton = varargin{kk+1};
        case 'G_GRAV'
            g_grav = varargin{kk+1};
        case 'G'
            switch varargin{kk}
                case 'G'
                    G_newton = varargin{kk+1};
                case 'g'
                    g_grav = varargin{kk+1};
            end
        case 'K_COULOMB'
            k_coulomb = varargin{kk+1};
        case 'K'
            switch varargin{kk}
                case 'k'
                    k_coulomb = varargin{kk+1};
                case 'K'
                    fprintf('"K" is not a property name.\nDid you mean "k"?\n')
                    return
            end
        case {'CR','E'}
            Cr = varargin{kk+1};
        case {'FRICTION','FRICTION_FACTOR','FRICTION FACTOR','FRICT'}
            friction_factor = varargin{kk+1};
        case {'PLAY_BEEP','BEEP'}
            if ischar(varargin{kk+1})
                switch upper(varargin{kk+1})
                    case 'ON'
                        play_beep = true;
                    case 'OFF'
                        play_beep = false;
                    otherwise
                        fprintf('''%s'' is not a property value of "play_beep".\n',...
                            varargin{kk+1})
                        return
                end
            else
                play_beep = varargin{kk+1};
            end
        case {'DFLT_LAYOUT','DFLT LAYOUT','DEFAULT_LAYOUT',...
                'DEFAULT LAYOUT','LAYOUT'}
            if ischar(varargin{kk+1})
                switch upper(varargin{kk+1})
                    case {'2D','3D'}
                        dflt_layout = varargin{kk+1};
                    otherwise
                        fprintf('''%s'' is not a property value of "dflt_layout".\n',...
                            varargin{kk+1})
                        return
                end
            else
                fprintf('''%f'' is not a property value of "show_legend".\n',...
                    varargin{kk+1})
                return
            end
        case {'SHOW_LEGEND','LEGEND'}
            if ischar(varargin{kk+1})
                switch upper(varargin{kk+1})
                    case 'ON'
                        show_legend = true;
                    case 'OFF'
                        show_legend = false;
                    otherwise
                        fprintf('''%s'' is not a property value of "show_legend".\n',...
                            varargin{kk+1})
                        return
                end
            else
                show_legend = varargin{kk+1};
            end
        case {'AXIS_LIM','AXIS_LIMIT','LIM','LIMIT','AXIS'}
            if ischar(varargin{kk+1})
                switch upper(varargin{kk+1})
                    case {'AUTO','CALC'}
                        axis_lim = 'AUTO';
                    case {'DEFAULT','DFLT'}
                        axis_lim = 'DEFAULT';
                    otherwise
                        fprintf('''%s'' is not a property value of "axis_lim".\n',...
                            varargin{kk+1})
                        return
                end
            else
                axis_lim = varargin{kk+1};
            end
        otherwise
            fprintf('''%s'' is not a property name.\n',varargin{kk})
            return
    end
end
switch upper(dflt_layout)
    case '2D'
        ball_v(:,3) = 0;
    case '3D'
        if ball_v(:,3) == 0
            ball_v(:,3) = 2.5*rand(N,1);
        end
end
if ~debug_mode && show_legend
    fprintf('The property "show_legend" is only used in debug mode.\n')
end
%Variables
tlen = tmax/dt;
cldmax = 10000;
n_plane = size(wall_plane,1);
n_sphere = size(wall_sphere,1);
n_grav = size(GravitySrc,1);
n_coulomb = size(CoulombSrc,1);
if ischar(axis_lim)
    switch axis_lim
        case 'AUTO'
            axis_lim = find_axislim;
        case 'DEFAULT'
            axis_lim = dflt_axis_lim;
    end
elseif isvector(axis_lim)
    axis_lim = reshape(axis_lim,2,3)';
end
%% MODIFY
if n_plane ~= 0
    plane_magn = sqrt(sum(wall_plane(:,1:3).^2,2));
    wall_plane = wall_plane./repmat(plane_magn,1,4); %normalize
end
ball_pos(:,4) = 1;
ball_pos_new = ball_pos;
ball_v_new = ball_v;
GravityVec = GravityVec/sqrt(GravityVec*GravityVec'); %normalize
GravityField = g_grav*GravityVec;
GravityPlane = [GravityVec -GravityVec*GroundPoint'];
%CHECK
[~,kc] = check_overlap;
if kc > 0
    fprintf(['ERROR: There is an overlap in the current sphere layout.'...
        '\nPlease check ''ball_pos'' and try again.\n\n'])
    return
end

%% PLOT and SIMULATION
%Generate Figure and Axes
t_dura = max([min([10^(round(log10(dt/N^2))),0.01]),1e-4]);
f_h = figure('name','Sphere Collider','menubar','none','numbertitle','off',...
    'position',[30 270 600 450]);
a_h = axes('parent',f_h);
set(a_h,'box','on','projection','perspective','dataaspectratio',[1 1 1])
set(a_h,'xlim',axis_lim(1,:),'ylim',axis_lim(2,:),'zlim',axis_lim(3,:))
set(a_h,'xlimmode','manual','ylimmode','manual','zlimmode','manual')
set(a_h,'xgrid','on','ygrid','on','zgrid','on')
xlabel(a_h,'x')
ylabel(a_h,'y')
zlabel(a_h,'z')
rotate3d(a_h)
%Debugging Figure
if debug_mode
    lin_color = [1 0 0;0 1 0;0 0 1;0 1 0;0 0 1;0 0 0];
    lin_style = {'-','-','-',':',':','-'};
    % figure1: Total Momentum and Energy
    % subplot(2,1,1): Momentum, subplot(2,1,2): Energy;
    fd1_h = figure('name','DEBUGGING: Total Momentum and Energy','menubar','none',...
        'numbertitle','off','position',[728 270 600 450]); %[728 415 600 305]
    ad1_h = zeros(1,2);
    lin1_h = zeros(1,6);
    t_span = 0:dt:tmax;
    p_total = zeros(tlen+1,3);
    E_total = zeros(tlen+1,3);
    E_total(1,:) = total_energy;
    Emax = max(E_total(1,:));
    Emin = 0;
    if Emax == 0
        Emax = 10; %!!
    end
    ad1_h(2) = subplot(2,1,2,'parent',fd1_h,'xlim',[0 tmax],'ylim',[Emin Emax*1.1]);
    title(ad1_h(2),'Energy in Total');
    xlabel(ad1_h(2),'time')
    ylabel(ad1_h(2),'Energy')
    for dk = 4:6
        lin1_h(dk) = line('xdata',0,'ydata',E_total(1,dk-3),'parent',ad1_h(2),...
            'color',lin_color(dk,:),'linestyle',lin_style{dk});
    end %energy
    p_total(1,:) = total_momentum;
    pmax = max(abs(p_total(1,:)));
    if pmax == 0
        pmax = sqrt(2*Emax);
    end
    pmin = -pmax;
    ad1_h(1) = subplot(2,1,1,'parent',fd1_h,'xlim',[0 tmax],'ylim',[pmin pmax]);
    title(ad1_h(1),'Momentum in Total')
    xlabel(ad1_h(1),'time')
    ylabel(ad1_h(1),'Momentum')
    for dk = 1:3
        lin1_h(dk) = line('xdata',0,'ydata',p_total(1,dk),'parent',ad1_h(1),...
            'color',lin_color(dk,:),'linestyle',lin_style{dk});
    end %momentum
    if show_legend
        legend(ad1_h(2),'E_k','E_p','E');
        legend(ad1_h(1),'P_x','P_y','P_z');
    end
end
%Generate Balls
sph_n = 25;
[bx,by,bz] = sphere(sph_n);
xyzdata = cell(1,N); %{[(sph_n+1)x(sph_n+1)x3 matrix]}
sf_h = zeros(1,N);
%Initial xyz_data of balls
for ikb = 1:N
    xyzdata{ikb} = zeros(sph_n+1,sph_n+1,3);
    xyzdata{ikb}(:,:,1) = balls(ikb)*bx;
    xyzdata{ikb}(:,:,2) = balls(ikb)*by;
    xyzdata{ikb}(:,:,3) = balls(ikb)*bz;
    sf_h(ikb) = surface(xyzdata{ikb}(:,:,1),xyzdata{ikb}(:,:,2),xyzdata{ikb}(:,:,3),...
        'FaceColor',ball_color(ikb,1:3),'FaceAlpha',ball_color(ikb,4),...
        'linestyle','none','visible','off','parent',a_h);
end %Initial xyz_data of balls
%Initial plot
for ikb = 1:N
    set(sf_h(ikb),'xdata',xyzdata{ikb}(:,:,1)+ball_pos(ikb,1),...
        'ydata',xyzdata{ikb}(:,:,2)+ball_pos(ikb,2),...
        'zdata',xyzdata{ikb}(:,:,3)+ball_pos(ikb,3),'visible','on')
end %Initial plot
%Plot Walls
for ikws = 1:n_sphere
    surface(wall_sphere(ikws,1)*bx+wall_sphere(ikws,2),...
        wall_sphere(ikws,1)*by+wall_sphere(ikws,3),...
        wall_sphere(ikws,1)*bz+wall_sphere(ikws,4),...
        'parent',a_h,'facecolor',[0.8 0.5 0.25],'facealpha',0.25,...
        'linestyle','none')
end %plot wall_sphere

%% SIMULATION
%Start Simulation!
dt_p = dt; % dt_p: time interval for prediction
dt_c = 0;  % dt_c: cumulative time of collision
cnt_t = 1; % counter for time
cnt_c = 0; % counter for collision
cnt_error = 0; % counter for errors
while cnt_c < cldmax && cnt_t < tlen
    cnt_c = cnt_c+1;
    %predict and update position
    ball_pos_new(:,1:3) = ball_pos(:,1:3)+dt_p*ball_v;
    %Collision Detection
    [dt_int,kc] = check_overlap;
    if dt_int < inf %collide!
        %processing
        %update position and velocity
        dt_c = dt_c+dt_int;
        dt_p = dt-dt_c;
        ball_pos(:,1:3) = ball_pos(:,1:3)+dt_int*ball_v;
        ball_v(kc,:) = ball_v_new(kc,:);
        if play_beep
            beep
        end
        %ACCELERATION NOT UPDATED
    else %No overlapping
        %#(ONE FRAME)#
        cnt_t = cnt_t+1;
        for kb1 = 1:N
            %update acceleration and velocity
            a = [0 0 0];
            for kb2 = [1:kb1-1,kb1+1:N]
                norm_balls = ball_pos(kb2,1:3)-ball_pos(kb1,1:3); %kb1 -> kb2
                dist_balls = sqrt(norm_balls*norm_balls');
                a_grav_magn = G_newton*balls(kb1,2)*balls(kb2,2)/dist_balls^2;
                a_coulomb_magn = -k_coulomb*balls(kb1,3)*balls(kb2,3)/dist_balls^2; %negative
                norm_balls = norm_balls/dist_balls; %normalize
                a = a+(a_grav_magn*grav_ball+a_coulomb_magn)*norm_balls;
            end %Interaction between balls
            for ksg = 1:n_grav
                norm_src = ball_pos(kb1,1:3)-GravitySrc(ksg,2:4);
                dist_src = sqrt(norm_src*norm_src');
                a_grav_magn = G_newton*balls(kb1,2)*GravitySrc(ksg,1)/dist_src^2;
                norm_src = norm_src/dist_src; %normalize
                a = a+a_grav_magn*norm_src;
            end %Gravity
            for ksc = 1:n_coulomb
                norm_src = ball_pos(kb1,1:3)-CoulombSrc(ksc,2:4);
                dist_src = sqrt(norm_src*norm_src');
                a_coulomb_magn = k_coulomb*balls(kb1,3)*CoulombSrc(ksc,1)/dist_src^2;
                norm_src = norm_src/dist_src; %normalize
                a = a+a_coulomb_magn*norm_src;
            end %Coulomb force
            %Gravity Field
            a = a+GravityField;
            a_frict = -friction_factor/balls(kb1,2)*ball_v(kb1,:);
            ball_v(kb1,:) = ball_v(kb1,:)+dt*(a+a_frict);
            %DRAW
            set(sf_h(kb1),'xdata',xyzdata{kb1}(:,:,1)+ball_pos_new(kb1,1),...
                'ydata',xyzdata{kb1}(:,:,2)+ball_pos_new(kb1,2),...
                'zdata',xyzdata{kb1}(:,:,3)+ball_pos_new(kb1,3))
        end %update acceleration and velocity
        %update position
        ball_pos = ball_pos_new;
        pause(t_dura);
        %reset dt_p and dt_c
        dt_p = dt; %this is important!
        dt_c = 0;
        
        %DEBUG
        if debug_mode
            %test for conservation of momentum
            p_total(cnt_t,:) = total_momentum;
            E_total(cnt_t,:) = total_energy;
            for dk = 1:3
                set(lin1_h(dk),'xdata',t_span(1:cnt_t),'ydata',p_total(1:cnt_t,dk));
            end %momentum
            for dk = 4:6
                set(lin1_h(dk),'xdata',t_span(1:cnt_t),'ydata',E_total(1:cnt_t,dk-3));
            end %energy
        end
    end
end %Simulation

%% REPORT
fprintf('Number of ERRORs: %d\n',cnt_error)

%% FUNCTIONS
    function p_total = total_momentum
        p_total = zeros(1,3);
        p_total(1) = balls(:,2)'*ball_v(:,1);
        p_total(2) = balls(:,2)'*ball_v(:,2);
        p_total(3) = balls(:,2)'*ball_v(:,3);
    end %compute total momentum

    function E_total = total_energy
        E_total = zeros(1,3);
        E_total(1) = balls(:,2)'*sum(ball_v.^2,2)/2;
        E_total(2) = balls(:,2)'*abs(ball_pos*GravityPlane')*g_grav;
        E_total(3) = E_total(1)+E_total(2);
    end %compute total energy

    function [dt_int,kc] = check_overlap
        dt_int = inf;
        kc = -1; %unnecessary, actually :)
        for k1 = 1:N
            %update acceleration
            %detect possible collision between ball and walls
            for kwp = 1:n_plane
                dist_plane = abs(wall_plane(kwp,:)*ball_pos_new(k1,:)');
                iscross = (wall_plane(kwp,:)*ball_pos_new(k1,:)')*...
                    (wall_plane(kwp,:)*ball_pos(k1,:)') < 0; %bug fixed
                if dist_plane < balls(k1,1) || iscross %!!
                    vn = ball_v(k1,:)*wall_plane(kwp,1:3)'*wall_plane(kwp,1:3);
                    dist_plane_old = abs(wall_plane(kwp,:)*ball_pos(k1,:)');
                    dt_int_c = (dist_plane_old-balls(k1,1))/sqrt(vn*vn');
                    if dt_int_c < dt_int
                        dt_int = dt_int_c;
                        ball_v_new(k1,:) = ball_v(k1,:)-(1+Cr)*vn;
                        kc = k1;
                    end
                end
            end %detect possible collision between ball and walls
            for kws = 1:n_sphere
                sphere_norm = ball_pos_new(k1,1:3)-wall_sphere(kws,2:4);
                sphere_norm_old = ball_pos(k1,1:3)-wall_sphere(kws,2:4);
                dist_sphere = sqrt(sphere_norm*sphere_norm');
                dist_sphere_old = sqrt(sphere_norm_old*sphere_norm_old');
                iscross = ((dist_sphere-wall_sphere(kws,1))*...
                    (dist_sphere_old-wall_sphere(kws,1))) < 0; %bug fixed
                if abs(dist_sphere-wall_sphere(kws,1)) < balls(k1,1) || iscross %!!
                    sphere_norm = sphere_norm/dist_sphere; %normalize
                    vn = (ball_v(k1,:)*sphere_norm')*sphere_norm;
                    dt_int_c = (abs(dist_sphere_old-wall_sphere(kws,1))...
                        -balls(k1,1))/sqrt(vn*vn');
                    if dt_int_c < dt_int
                        dt_int = dt_int_c;
                        ball_v_new(k1,:) = ball_v(k1,:)-(1+Cr)*vn;
                        kc = k1;
                    end
                end
            end %detect possible collision between ball and walls
            for k2 = k1+1:N
                %detect possible collision between balls
                norm_balls = ball_pos_new(k2,1:3)-ball_pos_new(k1,1:3); %kb1 -> kb2
                if norm_balls*norm_balls' < (balls(k1,1)+balls(k2,1))^2
                    v2_1 = ball_v(k2,:)-ball_v(k1,:);         %relative velocity
                    r2_1 = ball_pos(k2,1:3)-ball_pos(k1,1:3); %relative position
                    d_sq = (det([r2_1([1,2]);v2_1([1,2])])^2+det([r2_1([2,3]);v2_1([2,3])])^2+...
                        det([r2_1([3,1]);v2_1([3,1])])^2)/(v2_1*v2_1');
                    d_c = sqrt(r2_1*r2_1'-d_sq)-sqrt((balls(k1,1)+balls(k2,1))^2-d_sq);
                    dt_int_c = abs(d_c)/sqrt(v2_1*v2_1');
                    %DEBUG
                    if d_sq >= (balls(k1,1)+balls(k2,1))^2
                        set(sf_h([k1,k2]),'facecolor',[0.75 0.25 0.75])
                        disp('ERROR: "d" >= R1+R2')
                        cnt_error = cnt_error+1;
                    end
                    if dt_int_c < 0
                        set(sf_h([k1,k2]),'facecolor',[0.75 0.25 0.75])
                        disp('ERROR: "dt_int_c" < 0')
                        cnt_error = cnt_error+1;
                    end
                    ball_pos_new(k1,1:3) = ball_pos(k1,1:3)+dt_int_c*ball_v(k1,1:3);
                    ball_pos_new(k2,1:3) = ball_pos(k2,1:3)+dt_int_c*ball_v(k2,1:3);
                    norm_balls_c = (ball_pos_new(k2,1:3)-ball_pos_new(k1,1:3))/...
                        (balls(k1,1)+balls(k2,1)); %normalize
                    if dt_int_c < dt_int
                        dt_int = dt_int_c;
                        vn1 = (ball_v(k1,:)*norm_balls_c')*norm_balls_c;
                        vn2 = (ball_v(k2,:)*norm_balls_c')*norm_balls_c;
                        va = (balls(k1,2)*vn1+balls(k2,2)*vn2+balls(k2,2)*...
                            Cr*(vn2-vn1))/(balls(k1,2)+balls(k2,2));
                        vb = (balls(k1,2)*vn1+balls(k2,2)*vn2+balls(k1,2)*...
                            Cr*(vn1-vn2))/(balls(k1,2)+balls(k2,2));
                        ball_v_new(k1,:) = ball_v(k1,:)-vn1+va;
                        ball_v_new(k2,:) = ball_v(k2,:)-vn2+vb;
                        kc = [k1 k2];
                    end
                end
            end %detect possible collision between balls
        end %loop contains every ball
    end %detect possible collisions

    function axislim = find_axislim
        %axislim: [xmin,xmax;ymin,ymax;zmin,zmax]
        plane_idx = combination(n_plane,3);
        pt = zeros(size(plane_idx,1),3);
        for kp = 1:size(plane_idx,1)
            pt(kp,:) = -wall_plane(plane_idx(kp,:),1:3)\wall_plane(plane_idx(kp,:),4);
        end
        pt(isinf(pt) | isnan(pt)) = 0;
        axislim = [min(pt(:,1)),max(pt(:,1));min(pt(:,2)),max(pt(:,2));...
            min(pt(:,3)),max(pt(:,3))];
    end %compute axis limits

end

Contact us