I really liked the problem, where you want to modify the spirograph to roll inside a stadium shaped track.
My understanding of the problem is that though the stadium is correctly drawn, but the rolling circle is still tracing as if the outer boundary is circular. As a result, it does not follow the stadium path, particularly the straight segments.
The problem arises because on the straight segments, there's no central angle like in a circle, and linspace(0, 2π) only works when the boundary is circular, not piecewise.
The way I approached it is by modelling the motion along the four parts of the stadium (2 straight lines and 2 semi-circles) separately, keeping track of:
- The current segment (left curve, top line, right curve, bottom line),
- The arc length travelled,
- The position and rotation of the small circle.
I've also shared an implementation that makes the rolling circle follow the stadium shaped path. The main steps it goes through are:
- Drawing the stadium shape
- Figuring out where the rolling circle touches the track
- Calculating the center position of the rolling circle
- Tracking the position of the dot inside the rolling circle
function stadium_spirograph(a, b)
% Defining the function parameters
% a = radius of semicircle ends of the track
% b = radius of the rolling circle
% Track geometry
straightLength = 4; % Length of straight segments
centerDistance = straightLength/2; % Half-distance between semicircles
dt = 0.01; % Time step
theta = 0; % Angle for rolling
% Precomputing the high school track so that it is computed only single
% time. (Computation here means calculating the coordinates corresponding of its path)
trackX = [];
trackY = [];
% Left semicircle
t1 = linspace(pi/2, 3*pi/2, 100);
trackX = [trackX, a*cos(t1) - centerDistance];
trackY = [trackY, a*sin(t1)];
% Bottom line
trackX = [trackX, linspace(-centerDistance, centerDistance, 100)];
trackY = [trackY, -a * ones(1,100)];
% Right semicircle
t2 = linspace(-pi/2, pi/2, 100);
trackX = [trackX, a*cos(t2) + centerDistance];
trackY = [trackY, a*sin(t2)];
% Top line
trackX = [trackX, linspace(centerDistance, -centerDistance, 100)];
trackY = [trackY, a * ones(1,100)];
% For red trace (the actual curve which is desired)
X_trace = [];
Y_trace = [];
while true
clf;
hold on;
axis equal;
axis([-10 10 -7 7]);
% Plot the track (stationary stadium shape)
plot(trackX, trackY, 'k', 'LineWidth', 1.5);
% Parametric angle along track
L = 2*straightLength + 2*pi*a;
pos = mod(theta * (a + b), L); % total distance rolled so far
% Determine (x0, y0), the centre of the rolling circle
if pos < straightLength
% Bottom straight
x0 = -centerDistance + pos;
y0 = -a;
normal = [0, 1];
elseif pos < straightLength + pi*a
% Right semicircle
ang = (pos - straightLength) / a - pi/2;
x0 = centerDistance + a*cos(ang);
y0 = a*sin(ang);
normal = [-cos(ang), -sin(ang)];
elseif pos < straightLength + pi*a + straightLength
% Top straight
x0 = centerDistance - (pos - (straightLength + pi*a));
y0 = a;
normal = [0, -1];
else
% Left semicircle
ang = (pos - (2*straightLength + pi*a)) / a + pi/2;
x0 = -centerDistance + a*cos(ang);
y0 = a*sin(ang);
normal = [-cos(ang), -sin(ang)];
end
% here, the tuple (xc,yc) is the point where the rolling circle is in
% contact with the stationary track. For calculating the center of this
% circle, we have to move a distance of 'a' (the radius of rolling
% circle) away from the curve in normal direction.
% Rolling circle center
xc = x0 + b * normal(1);
yc = y0 + b * normal(2);
% Plot the rolling circle
t_circle = linspace(0, 2*pi, 100);
x_circle = xc + b*cos(t_circle);
y_circle = yc + b*sin(t_circle);
plot(x_circle, y_circle, 'b');
% Dot position inside rolling circle (spirograph point, from where
% the required curve is drawn)
angle_inside = -theta * (a + b)/b;
xd = xc + b*cos(angle_inside);
yd = yc + b*sin(angle_inside);
plot(xd, yd, 'ko', 'MarkerFaceColor','k', 'MarkerSize', 3);
% Adding to trace (the required curve)
X_trace = [X_trace, xd];
Y_trace = [Y_trace, yd];
plot(X_trace, Y_trace, 'r');
theta = theta + dt;
drawnow;
end
end
You can call the function from command line as follows:
stadium_spirograph(3.5, 0.7);

I hope this helps you out!