RollingWheelJoint
Joint (no mass, no inertia) for an ideal rolling wheel on the flat plane y = 0.
The rolling contact is considered ideal: no slip between the wheel and the ground. This is enforced by two non-holonomic velocity-level constraints (longitudinal and lateral) plus one holonomic position-level constraint (the wheel stays in contact with the ground).
The origin of frame_a is placed at the intersection of the wheel spin axis with the wheel midplane and rotates with the wheel. The z-axis of frame_a is the wheel spin axis. A wheel body collecting the mass and inertia should be connected to frame_a.
For a wheel with mass and inertia attached, see RollingWheel.
This component extends from Renderable
Usage
MultibodyComponents.RollingWheelJoint(render=true, color=[1, 0, 0, 1], specular_coefficient=1.5, radius=0.3, width=0.035)
Parameters:
| Name | Description | Units | Default value |
|---|---|---|---|
sequence | Rotation axis sequence for the Cardan angles that orient frame_a (default y, z, x) | – | [2, 3, 1] |
render | – | true | |
color | – | [1, 0, 0, 1] | |
specular_coefficient | – | 1.5 | |
radius | Radius of the wheel | m | 0.3 |
width | Width of the wheel in animations | – | 0.035 |
Connectors
frame_a- Frame3D is the fundamental 3D connector used for 6DOF motion. Most components have one or severalFrame
connectors that can be connected together (Frame3D)
Variables
| Name | Description | Units |
|---|---|---|
x | x-position of the wheel axis | m |
y | y-position of the wheel axis | m |
z | z-position of the wheel axis | m |
angles | Angles that rotate the world frame into frame_a around the y-, z-, x-axis | rad |
der_angles | Time derivatives of angles | rad/s |
r_road_0 | Position vector from world frame to contact point on the road, resolved in world frame | m |
f_wheel_0 | Force vector on the wheel at the contact point, resolved in world frame | – |
f_n | Contact force acting on the wheel in the normal direction | N |
f_lat | Contact force acting on the wheel in the lateral direction | N |
f_long | Contact force acting on the wheel in the longitudinal direction | N |
e_axis_0 | Unit vector along the wheel axis, resolved in world frame | – |
delta_0 | Distance vector from wheel center to contact point, resolved in world frame | – |
e_n_0 | Unit vector normal to the road at the contact point, resolved in world frame | – |
e_lat_0 | Unit vector in the lateral direction of the road at the contact point, resolved in world frame | – |
e_long_0 | Unit vector in the longitudinal direction of the road at the contact point, resolved in world frame | – |
e_s_0 | Road heading at (s, w), resolved in world frame (unit vector) | – |
s | Road surface parameter 1 | – |
w | Road surface parameter 2 | – |
v_0 | Velocity of wheel center, resolved in world frame | m/s |
w_0 | Angular velocity of the wheel, resolved in world frame | rad/s |
vContact_0 | Velocity of the contact point, resolved in world frame | m/s |
aux | Auxiliary vector for building the contact frame | – |
Behavior
Source
"""
Joint (no mass, no inertia) for an ideal rolling wheel on the flat plane y = 0.
The rolling contact is considered ideal: no slip between the wheel and the
ground. This is enforced by two non-holonomic velocity-level constraints
(longitudinal and lateral) plus one holonomic position-level constraint
(the wheel stays in contact with the ground).
The origin of `frame_a` is placed at the intersection of the wheel spin axis
with the wheel midplane and rotates with the wheel. The z-axis of `frame_a`
is the wheel spin axis. A wheel body collecting the mass and inertia should
be connected to `frame_a`.
For a wheel with mass and inertia attached, see `RollingWheel`.
"""
component RollingWheelJoint
extends Renderable(color = [1, 0, 0, 1])
frame_a = Frame3D() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 140, "y1": 20, "x2": 240, "y2": 120, "rot": 0}
},
"tags": []
}
}
# Tire visualization: cylinder centered on frame_a with axis along z
tire_shape = CylinderShape(render = render, color = color, r = frame_a.r_0, R = transpose(frame_a.R), r_shape = [0, 0, -width / 2], length_direction = [0, 0, 1], length = width, width = 2 * radius, height = 2 * radius) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 20, "y1": 20, "x2": 120, "y2": 120, "rot": 0}
},
"tags": []
}
}
"Rim cross-cylinder 1: thin spoke spanning the wheel diameter; rotates with the wheel to make the spin visible"
rim1_shape = CylinderShape(render = render, color = [0.8, 0.8, 0.8, 1.0], r = frame_a.r_0, R = transpose(frame_a.R), r_shape = [-radius, 0, 0], length_direction = [1, 0, 0], length = 2 * radius, width = radius / 3, height = radius / 3)
"Rim cross-cylinder 2: second spoke, perpendicular to the first"
rim2_shape = CylinderShape(render = render, color = [0.8, 0.8, 0.8, 1.0], r = frame_a.r_0, R = transpose(frame_a.R), r_shape = [0, -radius, 0], length_direction = [0, 1, 0], length = 2 * radius, width = radius / 3, height = radius / 3)
"Rotation axis sequence for the Cardan angles that orient `frame_a` (default y, z, x)"
structural parameter sequence::Integer[3] = [2, 3, 1]
"Radius of the wheel"
parameter radius::Length = 0.3
"Width of the wheel in animations"
parameter width::Real = 0.035
"x-position of the wheel axis"
variable x::Length(statePriority = 20)
"y-position of the wheel axis"
variable y::Length(statePriority = 0)
"z-position of the wheel axis"
variable z::Length(statePriority = 20)
"Angles that rotate the world frame into `frame_a` around the y-, z-, x-axis"
variable angles::Angle(statePriority = 30)[3]
"Time derivatives of `angles`"
variable der_angles::AngularVelocity(statePriority = 30)[3]
"Position vector from world frame to contact point on the road, resolved in world frame"
variable r_road_0::Position[3]
"Force vector on the wheel at the contact point, resolved in world frame"
variable f_wheel_0::Real[3]
"Contact force acting on the wheel in the normal direction"
variable f_n::Dyad.Force
"Contact force acting on the wheel in the lateral direction"
variable f_lat::Dyad.Force
"Contact force acting on the wheel in the longitudinal direction"
variable f_long::Dyad.Force
"Unit vector along the wheel axis, resolved in world frame"
variable e_axis_0::Real[3]
"Distance vector from wheel center to contact point, resolved in world frame"
variable delta_0::Real[3]
"Unit vector normal to the road at the contact point, resolved in world frame"
variable e_n_0::Real[3]
"Unit vector in the lateral direction of the road at the contact point, resolved in world frame"
variable e_lat_0::Real[3]
"Unit vector in the longitudinal direction of the road at the contact point, resolved in world frame"
variable e_long_0::Real[3]
"Road heading at (s, w), resolved in world frame (unit vector)"
variable e_s_0::Real[3]
"Road surface parameter 1"
variable s::Real
"Road surface parameter 2"
variable w::Real
"Velocity of wheel center, resolved in world frame"
variable v_0::Velocity[3]
"Angular velocity of the wheel, resolved in world frame"
variable w_0::AngularVelocity[3]
"Velocity of the contact point, resolved in world frame"
variable vContact_0::Velocity[3]
"Auxiliary vector for building the contact frame"
variable aux::Real[3]
relations
# Solver guesses for algebraic intermediates (mirrors Multibody.jl defaults)
guess x = 0
guess y = radius
guess z = 0
guess angles = [0, 0, 0]
guess der_angles = [0, 0, 0]
guess delta_0 = [0, -radius, 0]
guess e_n_0 = [0, 1, 0]
guess aux = [1, 0, 0]
# Flat road description
r_road_0 = [s, 0, w]
e_n_0 = [0, 1, 0]
e_s_0 = [1, 0, 0]
# Pose
frame_a.r_0 = [x, y, z]
RotationMatrix(frame_a.R) = axes_rotations(sequence, angles, der_angles)
der_angles = der(angles)
# Contact-point coordinate frame (e_long_0, e_lat_0, e_n_0)
e_axis_0 = resolve1(frame_a.R, [0, 0, 1])
assert(abs(dot(e_n_0, e_axis_0)) < 0.99, "Wheel lays nearly on the ground (which is a singularity)")
aux = cross(e_n_0, e_axis_0)
e_long_0 = aux / norm_(aux)
e_lat_0 = cross(e_long_0, e_n_0)
# Contact-point geometry
delta_0 = r_road_0 - frame_a.r_0
0 = dot(delta_0, e_axis_0)
0 = dot(delta_0, e_long_0)
# Holonomic constraint: wheel touches road, no penetration
0 = radius - dot(delta_0, cross(e_long_0, e_axis_0))
# Velocities — derive angular velocity directly from der_angles via axes_rotations
# (mirrors Multibody.jl's explicit `Ra.w ~ Rarot.w`, avoids state selection picking D(R)-based states)
v_0 = der(frame_a.r_0)
w_0 = resolve1(frame_a.R, angular_velocity2(axes_rotations(sequence, angles, der_angles)))
vContact_0 = v_0 + cross(w_0, delta_0)
# Non-holonomic constraints: ideal rolling, no slip
0 = dot(vContact_0, e_long_0)
0 = dot(vContact_0, e_lat_0)
# Contact force decomposition
f_wheel_0 = f_n * e_n_0 + f_lat * e_lat_0 + f_long * e_long_0
# Force and torque balance at the wheel center
[0, 0, 0] = frame_a.f + resolve2(frame_a.R, f_wheel_0)
[0, 0, 0] = frame_a.tau + resolve2(frame_a.R, cross(delta_0, f_wheel_0))
metadata {
"Dyad": {
"icons": {"default": "dyad://MultibodyComponents/RollingWheelJoint.svg"},
"labels": [
{
"label": "$(instance)",
"x": 500,
"y": 200,
"rot": 0,
"attrs": {"font-size": "160"}
}
]
}
}
endFlattened Source
"""
Joint (no mass, no inertia) for an ideal rolling wheel on the flat plane y = 0.
The rolling contact is considered ideal: no slip between the wheel and the
ground. This is enforced by two non-holonomic velocity-level constraints
(longitudinal and lateral) plus one holonomic position-level constraint
(the wheel stays in contact with the ground).
The origin of `frame_a` is placed at the intersection of the wheel spin axis
with the wheel midplane and rotates with the wheel. The z-axis of `frame_a`
is the wheel spin axis. A wheel body collecting the mass and inertia should
be connected to `frame_a`.
For a wheel with mass and inertia attached, see `RollingWheel`.
"""
component RollingWheelJoint
parameter render::Boolean = true
parameter color::Real[4] = [0.5, 0.5, 0.5, 1.0]
parameter specular_coefficient::Real = 1.5
frame_a = Frame3D() {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 140, "y1": 20, "x2": 240, "y2": 120, "rot": 0}
},
"tags": []
}
}
# Tire visualization: cylinder centered on frame_a with axis along z
tire_shape = CylinderShape(render = render, color = color, r = frame_a.r_0, R = transpose(frame_a.R), r_shape = [0, 0, -width / 2], length_direction = [0, 0, 1], length = width, width = 2 * radius, height = 2 * radius) {
"Dyad": {
"placement": {
"diagram": {"iconName": "default", "x1": 20, "y1": 20, "x2": 120, "y2": 120, "rot": 0}
},
"tags": []
}
}
"Rim cross-cylinder 1: thin spoke spanning the wheel diameter; rotates with the wheel to make the spin visible"
rim1_shape = CylinderShape(render = render, color = [0.8, 0.8, 0.8, 1.0], r = frame_a.r_0, R = transpose(frame_a.R), r_shape = [-radius, 0, 0], length_direction = [1, 0, 0], length = 2 * radius, width = radius / 3, height = radius / 3)
"Rim cross-cylinder 2: second spoke, perpendicular to the first"
rim2_shape = CylinderShape(render = render, color = [0.8, 0.8, 0.8, 1.0], r = frame_a.r_0, R = transpose(frame_a.R), r_shape = [0, -radius, 0], length_direction = [0, 1, 0], length = 2 * radius, width = radius / 3, height = radius / 3)
"Rotation axis sequence for the Cardan angles that orient `frame_a` (default y, z, x)"
structural parameter sequence::Integer[3] = [2, 3, 1]
"Radius of the wheel"
parameter radius::Length = 0.3
"Width of the wheel in animations"
parameter width::Real = 0.035
"x-position of the wheel axis"
variable x::Length(statePriority = 20)
"y-position of the wheel axis"
variable y::Length(statePriority = 0)
"z-position of the wheel axis"
variable z::Length(statePriority = 20)
"Angles that rotate the world frame into `frame_a` around the y-, z-, x-axis"
variable angles::Angle(statePriority = 30)[3]
"Time derivatives of `angles`"
variable der_angles::AngularVelocity(statePriority = 30)[3]
"Position vector from world frame to contact point on the road, resolved in world frame"
variable r_road_0::Position[3]
"Force vector on the wheel at the contact point, resolved in world frame"
variable f_wheel_0::Real[3]
"Contact force acting on the wheel in the normal direction"
variable f_n::Dyad.Force
"Contact force acting on the wheel in the lateral direction"
variable f_lat::Dyad.Force
"Contact force acting on the wheel in the longitudinal direction"
variable f_long::Dyad.Force
"Unit vector along the wheel axis, resolved in world frame"
variable e_axis_0::Real[3]
"Distance vector from wheel center to contact point, resolved in world frame"
variable delta_0::Real[3]
"Unit vector normal to the road at the contact point, resolved in world frame"
variable e_n_0::Real[3]
"Unit vector in the lateral direction of the road at the contact point, resolved in world frame"
variable e_lat_0::Real[3]
"Unit vector in the longitudinal direction of the road at the contact point, resolved in world frame"
variable e_long_0::Real[3]
"Road heading at (s, w), resolved in world frame (unit vector)"
variable e_s_0::Real[3]
"Road surface parameter 1"
variable s::Real
"Road surface parameter 2"
variable w::Real
"Velocity of wheel center, resolved in world frame"
variable v_0::Velocity[3]
"Angular velocity of the wheel, resolved in world frame"
variable w_0::AngularVelocity[3]
"Velocity of the contact point, resolved in world frame"
variable vContact_0::Velocity[3]
"Auxiliary vector for building the contact frame"
variable aux::Real[3]
relations
# Solver guesses for algebraic intermediates (mirrors Multibody.jl defaults)
guess x = 0
guess y = radius
guess z = 0
guess angles = [0, 0, 0]
guess der_angles = [0, 0, 0]
guess delta_0 = [0, -radius, 0]
guess e_n_0 = [0, 1, 0]
guess aux = [1, 0, 0]
# Flat road description
r_road_0 = [s, 0, w]
e_n_0 = [0, 1, 0]
e_s_0 = [1, 0, 0]
# Pose
frame_a.r_0 = [x, y, z]
RotationMatrix(frame_a.R) = axes_rotations(sequence, angles, der_angles)
der_angles = der(angles)
# Contact-point coordinate frame (e_long_0, e_lat_0, e_n_0)
e_axis_0 = resolve1(frame_a.R, [0, 0, 1])
assert(abs(dot(e_n_0, e_axis_0)) < 0.99, "Wheel lays nearly on the ground (which is a singularity)")
aux = cross(e_n_0, e_axis_0)
e_long_0 = aux / norm_(aux)
e_lat_0 = cross(e_long_0, e_n_0)
# Contact-point geometry
delta_0 = r_road_0 - frame_a.r_0
0 = dot(delta_0, e_axis_0)
0 = dot(delta_0, e_long_0)
# Holonomic constraint: wheel touches road, no penetration
0 = radius - dot(delta_0, cross(e_long_0, e_axis_0))
# Velocities — derive angular velocity directly from der_angles via axes_rotations
# (mirrors Multibody.jl's explicit `Ra.w ~ Rarot.w`, avoids state selection picking D(R)-based states)
v_0 = der(frame_a.r_0)
w_0 = resolve1(frame_a.R, angular_velocity2(axes_rotations(sequence, angles, der_angles)))
vContact_0 = v_0 + cross(w_0, delta_0)
# Non-holonomic constraints: ideal rolling, no slip
0 = dot(vContact_0, e_long_0)
0 = dot(vContact_0, e_lat_0)
# Contact force decomposition
f_wheel_0 = f_n * e_n_0 + f_lat * e_lat_0 + f_long * e_long_0
# Force and torque balance at the wheel center
[0, 0, 0] = frame_a.f + resolve2(frame_a.R, f_wheel_0)
[0, 0, 0] = frame_a.tau + resolve2(frame_a.R, cross(delta_0, f_wheel_0))
metadata {
"Dyad": {
"icons": {"default": "dyad://MultibodyComponents/RollingWheelJoint.svg"},
"labels": [
{
"label": "$(instance)",
"x": 500,
"y": 200,
"rot": 0,
"attrs": {"font-size": "160"}
}
]
}
}
endTest Cases
No test cases defined.
Related
Examples
Experiments
Analyses