% Simulation of a skateboarder on a ramp.
% Allen B. Downey

function res = bisection_func(flag)
    % if flag is 1, run the animation at the critical velocity
    % (minium that gets over the ramp)
    % otherwise, run a bisection search for the critical velocity
    if flag
        v = 5.357540588225950;       % initial velocity of the rider/board
        ramp_func(v, 1.0);
        return
    end
    
    u = 1.0;
    v = 10.0;
    
    for i=1:50
        w = (u+v) / 2
        xend = ramp_func(w, 100.0);
        if xend < 0
            u = w;
        else
            v = w;
        end
        if xend > 0 && abs(v-u)/u < 1e-15
            break
        end
    end
    res = w;
end


function res = ramp_func(v, speedup)
    % run a simulation with the given initial velocity and
    % display the animation with the given speedup.
    % Run until the rider gets to one end of the ramp or the
    % other and return the final x position.

    % ramp_height is the height of the ramp in meters
    % ramp_dist is the horizontal distance from the tip of the
    % ramp to the post, so half_length is half the length of the ramp
    ramp_height = 1;
    ramp_dist = 4;  
    half_length = norm([ramp_height,ramp_dist]);   
    
    % compute the max and min angles
    maxth = atan2(ramp_height, ramp_dist);
    minth = -maxth;

    % compute the initial unit vectors
    ihat = [1; 0];
    jhat = [0; 1];
    rhat = [cos(maxth); sin(maxth)];
    that = [-sin(maxth); cos(maxth)];
    
    m = 75;        % mass of rider/board in kg
    g = 9.8;       % acceleration of gravity in m/s^2
    d = 1.0;       % height of the rider's CG in m
    I = 100.0;       % moment of inertia of the ramp

    % start at left side of ramp, velocity horizontal
    IP = -half_length * rhat + d * jhat;
    IV = v * ihat;
    init = [IP; maxth; IV; 0];
    
    % run the simulation, then show the results
    tend = 100;
    options = odeset('Events', @events_func);    
    [T, M] = ode45(@slope_func, [0:0.1:tend], init, options);
    animate_func(T,M);
    
    % return the final x position
    xend = M(end,1);
    res = xend;
    
    function animate_func(T,M)
        % show an animation of the data in M in real time (if possible)
        X = M(:,1);
        Y = M(:,2);
        TH = M(:,3);

        for i=1:length(T)-1
            clf;
            hold on;
            f = 1.5;
            unit = f*half_length;
            axis([-unit, unit, -unit, unit]);

            % draw the rider
            plot(X(i), Y(i), 'r.', 'MarkerSize', 50);

            % draw the ramp
            th = TH(i);
            rhat = [cos(th); sin(th)];
            
            left = -half_length * rhat;
            right = half_length * rhat;
            line_func(left, right);
            
            orig = [0,0];
            bottom = -ramp_height * jhat;
            line_func(orig, bottom);
            
            hold off;
            drawnow;
            dt = T(i+1) - T(i);
            pause(dt / speedup);
        end
    end

    function line_func(A, B)
        % draw a blue line from point A to point B
        plot([A(1), B(1)], [A(2), B(2)], 'b-', 'LineWidth', 5)
    end
    
    function res = slope_func(t, W)
        % this is a slope function invoked by ode45
        n = length(W)/2;
        P = W(1:n);
        V = W(n+1:end);

        dRdt = V;                          
        dVdt = acceleration_func(t, P, V);

        res = [dRdt; dVdt];
    end

    function res = acceleration_func(t, P, V)
        % unpack P
        R = P(1:2);
        th = P(3);
        thdot = V(3);
        V = V(1:2);
        
        % compute unit vectors rhat and that
        rhat = [cos(th); sin(th)];
        that = [-sin(th); cos(th)];

        % compute r, the distance from the pivot to the contact point
        r = dot(R, rhat);

        % find the restoring force that moves the center of mass
        % toward where it is supposed to be.
        dx = dot(R, that) - d;
        k = 10000.0;
        Fr = -k * dx * that;

        % if the rider is too high, the ramp doesn't pull down.
        if dx > 0
            Fr = 0 * that;
        end
        
	% apply a damping force to the vertical component of the
	% skateboarder's motion
	cd = 1000.0;
	v = dot(V, that);
	Fd = -cd * v * that; 
	
        % compute the total acceleration
        Ag = -g * jhat;
        Ar = (Fr + Fd) / m;
        A = Ag + Ar;

        % torque calculation can be scalar because we
        % know that R and Fr are in the plane of the ramp
        % and rhat is perpendicular to that.
        aa = r * dot(-Fr, that) / I;

        % if the ramp is touching the ground, simulate
        % a restoring torque
        k = 100000.0;
        if th > maxth
            dth = th - maxth;
        elseif th < minth
            dth = th - minth;
        else
            dth = 0;
        end
        ar = -k * dth / I;
        
        % add some damping to the ramp
        cd = 1000.0;
        adamp = -cd * thdot^2 * sign(thdot);

        % add up the angular accelerations
        aa = aa + ar + adamp;
        
        % pack the acceleration vector
        res = [A; aa];
    end

    function [value,isterminal,direction] = events_func(t,W)
        % stop the simulation if the rider leaves the ramp
        % in either direction
        R = W(1:2);                  
        th = W(3);
        rhat = [cos(th) sin(th)]';
        r = dot(R, rhat);
        value = [r+half_length, r-half_length];  
        isterminal = [1, 1];
        direction = [-1, 1];
    end

end
