RollingWheel

model RollingWheel "Joint (no mass, no inertia) that describes an ideal rolling wheel (rolling on the plane z=0)"
    import Modelica.Mechanics.MultiBody.Frames;

    Modelica.Mechanics.MultiBody.Interfaces.Frame_a frame_a "Frame fixed in wheel center point. x-Axis: upwards, y-axis: along wheel axis"
        annotation (Placement(transformation(extent = {
            {-16, -16}, 
            {16, 16}})));
    parameter SI.Radius wheelRadius "Wheel radius";
    parameter StateSelect stateSelect = StateSelect.always "Priority to use generalized coordinates as states"
        annotation (
            HideResult = true,
            Evaluate = true);
    SI.Position x(start = 0, stateSelect = stateSelect) "x-coordinate of wheel axis";
    SI.Position y(start = 0, stateSelect = stateSelect) "y-coordinate of wheel axis";
    SI.Position z;
    SI.Angle angles[3](start = {0, 0, 0}, each stateSelect = stateSelect) "Angles to rotate world-frame into frame_a around z-, y-, x-axis"
        annotation (Dialog(
            group = "Initialization",
            showStartAttribute = true));
    SI.AngularVelocity der_angles[3](start = {0, 0, 0}, each stateSelect = stateSelect) "Derivative of angles"
        annotation (Dialog(
            group = "Initialization",
            showStartAttribute = true));
    SI.Position r_road_0[3] "Position vector from world frame to contact point on road, resolved in world frame";
    SI.Force f_wheel_0[3] "Contact force acting on wheel, resolved in world frame";
    SI.Force f_n "Contact force acting on wheel in normal direction";
    SI.Force f_lat "Contact force acting on wheel in lateral direction";
    SI.Force f_long "Contact force acting on wheel in longitudinal direction";
    SI.Position err "|r_road_0 - frame_a.r_0| - wheelRadius (must be zero; used for checking)";
protected
    Real e_axis_0[3] "Unit vector along wheel axis, resolved in world frame";
    SI.Position delta_0[3](start = {0, 0, -wheelRadius}) "Distance vector from wheel center to contact point";
    Real e_n_0[3] "Unit vector in normal direction of road at contact point, resolved in world frame";
    Real e_lat_0[3] "Unit vector in lateral direction of wheel at contact point, resolved in world frame";
    Real e_long_0[3] "Unit vector in longitudinal direction of wheel at contact point, resolved in world frame";
    SI.Position s "Road surface parameter 1";
    SI.Position w "Road surface parameter 2";
    Real e_s_0[3] "Road heading at (s,w), resolved in world frame (unit vector)";
    SI.Velocity v_0[3] "Velocity of wheel center, resolved in world frame";
    SI.AngularVelocity w_0[3] "Angular velocity of wheel, resolved in world frame";
    SI.Velocity vContact_0[3] "Velocity of wheel contact point, resolved in world frame";
    Real aux[3];
equation
    assert(abs(e_n_0 * e_axis_0) < 0.99, "Wheel lays nearly on the ground (which is a singularity)");
    Connections.root(frame_a.R);
    0 = delta_0 * e_axis_0;
    0 = delta_0 * e_long_0;
    0 = vContact_0 * e_lat_0;
    0 = vContact_0 * e_long_0;
    0 = wheelRadius - delta_0 * cross(e_long_0, e_axis_0);
    aux = cross(e_n_0, e_axis_0);
    delta_0 = r_road_0 - frame_a.r_0;
    e_axis_0 = Frames.resolve1(frame_a.R, {0, 1, 0});
    e_lat_0 = cross(e_long_0, e_n_0);
    e_long_0 = aux / Modelica.Math.Vectors.length(aux);
    e_n_0 = {0, 0, 1};
    e_s_0 = {1, 0, 0};
    err = Modelica.Math.Vectors.length(delta_0) - wheelRadius;
    f_wheel_0 = f_n * e_n_0 + f_lat * e_lat_0 + f_long * e_long_0;
    r_road_0 = {s, w, 0};
    v_0 = der(frame_a.r_0);
    w_0 = Frames.angularVelocity1(frame_a.R);
    der_angles = der(angles);
    vContact_0 = v_0 + cross(w_0, delta_0);
    frame_a.R = Frames.axesRotations({3, 2, 1}, angles, der_angles);
    frame_a.r_0 = {x, y, z};
    zeros(3) = frame_a.f + Frames.resolve2(frame_a.R, f_wheel_0);
    zeros(3) = frame_a.t + Frames.resolve2(frame_a.R, cross(delta_0, f_wheel_0));

    annotation (
        Icon(
            coordinateSystem(
                preserveAspectRatio = true,
                extent = {
                    {-100, -100}, 
                    {100, 100}}),
            graphics = {
                Rectangle(
                    extent = {
                        {-100, -80}, 
                        {100, -100}},
                    fillColor = {175, 175, 175},
                    fillPattern = FillPattern.Solid), 
                Text(
                    extent = {
                        {-150, 120}, 
                        {150, 80}},
                    lineColor = {0, 0, 255},
                    textString = "%name"), 
                Ellipse(
                    extent = {
                        {-80, 80}, 
                        {80, -80}},
                    fillColor = {255, 255, 255},
                    fillPattern = FillPattern.Solid)}),
        Documentation(info = "<html>\n<p>\nA joint for a wheel rolling on the x-y plane of the world frame.\nThe rolling contact is considered being ideal, i.e. there is no\nslip between the wheel and the ground. This is simply\ngained by two non-holonomic constraint equations on velocity level\ndefined for both longitudinal and lateral direction of the wheel.\nThere is also a holonomic constraint equation on position level\ngranting a permanent contact of the wheel to the ground, i.e.\nthe wheel can not take off.\n</p>\n<p>\nThe origin of the frame frame_a is placed in the intersection\nof the wheel spin axis with the wheel middle plane and rotates\nwith the wheel itself. The y-axis of frame_a is identical with\nthe wheel spin axis, i.e. the wheel rotates about y-axis of frame_a.\nA wheel body collecting the mass and inertia should be connected to\nthis frame.\n</p>\n\n<h4>Note</h4>\n<p>\nTo work properly, the gravity acceleration vector g of the world must point in the negative z-axis, i.e.\n</p>\n<blockquote><pre>\n<span style=\"font-family:'Courier New',courier; color:#0000ff;\">inner</span> <span style=\"font-family:'Courier New',courier; color:#ff0000;\">Modelica.Mechanics.MultiBody.World</span> world(n={0,0,-1});\n</pre></blockquote>\n</html>"));
end RollingWheel;