I am using Manim/Python to draw a clock face, then illustrate the idea of fractions of an hour. The minute hand sweeps from 12 to 3 o'clock (ie, 15 minutes) and as it draws, I want it to ink in as it goes, creating a wedge. I can't find out how to make it leave a trail. My usual source (ChatGPT) can't do it somehow (it keeps giving me failure after failure, drawing the clock face and the sweep of the minute hand, but not painting the arc as it goes).
A filled wedge between the minute hands on a clock can be created using a filled angle and an updater function. The filled angle creates the wedge. The updater function moves your minute hands and updates the wedge accordingly.
For simplicity, let's take a circle as the clock and two lines as the minute hand at 12 and 3pm. The line at 12 will be a placeholder for us to compute the angle and we will not move it over time.
The wedge you're looking for is the polygon spanned by the clock's center (where the minute hands touch) and the circular arc between the endpoints of the lines on the clock's outer frame.
from manim import *
class ClockDrawer(Scene):
def construct(self):
radius = 2
clock = Circle(radius = radius)
# minute hand at 12
l1 = Line(ORIGIN, 2 * UP).set_color(GREEN)
# minute hand at 3
l2 = (
Line(ORIGIN, 2*RIGHT)
.set_color(GREEN)
)
# Compute the angles between both hands. We want to fill between the
# inner corner of the angle (radius = 0, where the hands touch) and
# the outer corner of the angle (radius = radius, the outer edge of the clock)
angle_inner = Angle(l1, l2, other_angle=True, radius=0).set_color(GREEN)
angle_outer = Angle(l1, l2, other_angle=True, radius=radius).set_color(GREEN)
coord_angle_inner = angle_inner.points
coord_angle_outer = angle_outer.points
# combine coordinates to a polygon: (inner arc path, outer arc path,
# first coordinate of the inner arc to close the polygon)
pnts = np.concatenate([coord_angle_inner,
coord_angle_outer,
coord_angle_inner[0].reshape(1, 3)])
# fill in the polygon
mfill = VMobject().set_color(ORANGE)
mfill.set_points_as_corners(pnts).set_fill(GREEN, opacity=1)
self.add(l1, l2) # add minute hands
self.add(mfill) # fill angle
self.add(clock)
and the resulting wedge looks like this:
Now to make this angle fill gradually as time passes (i.e. line2 moves from 12 to 3), we need an updater function
The updater function (1) rotates the second minute hand and (2) fills in the new angle.
An updater functions takes a moving object (mob
) and a time difference (dt
) as inputs. In our case, mob
is the filled polygon, so that the code structure looks looks like this:
# a moving object, whose corner points we update
mfill = VMobject().set_color(ORANGE).set_fill(GREEN, opacity=1)
def update_mfill(mob, dt):
# define your updates here in
# dependence of the time steps dt
# then set the new wedge corners
mob.set_points_as_corners(pnts)
# equip the moving object with the updater
mfill.add_updater(update_mfill)
# the updater will run for this duration:
self.wait(8)
To update the second minute hand we can use the rotate function:
inv_speed = 10
l2.rotate(- dt * PI/inv_speed, about_point=ORIGIN)
I added a speed parameter here, where higher values make the minute hand travel slower.
We also need to make sure that the hand stops at 3 o'clock. We can check the current angle between the minute hands and then exit the updater:
v1 = l1.get_unit_vector()
v2 = l2.get_unit_vector()
current_angle = angle_between_vectors(v1, v2)
if current_angle >= PI / 2:
# some exit condition if rotated by 90 degree = 3 o'clock
Combined in an updater and with the code from before, we get the desired clock:
from manim import *
class ClockDrawer(Scene):
def construct(self):
# Use a circle as a clock, you can add
# additional styling here
radius = 2
clock = Circle(radius=radius)
# Minute hand at 12 (this helps us to
# compute the angle)
l1 = Line(ORIGIN, 2 * UP).set_color(GREEN)
# Minute hands that travels to 3 and starts at 12
l2 = Line(ORIGIN, 2 * UP).set_color(GREEN)
# Wedge between minute hands
mfill = VMobject().set_color(ORANGE).set_fill(GREEN, opacity=1)
# Updater function to rotate l2 and update wedge
def update_mfill(mob, dt):
# Rotate second minute hand by a small negative angle
# (meaning we go clockwise)
inv_speed = 10
l2.rotate(- dt * PI/inv_speed, about_point=ORIGIN)
# check the current angle between the minute hands
v1 = l1.get_unit_vector()
v2 = l2.get_unit_vector()
current_angle = angle_between_vectors(v1, v2)
# don't fill wedge if angle is too small
# (if the arc is 0, manim throws an error)
if current_angle < 1e-12:
return
# Stop at 90 degrees (PI/4 radians)
# = 3 o clock
if current_angle >= PI / 2:
# remove updater when the the second
# minute hand is at desired position
mfill.remove_updater(update_mfill)
return
# Update angles for outer and inner arc
angle_inner = Angle(l1, l2, other_angle=True, radius=0)
angle_outer = Angle(l1, l2, other_angle=True, radius=radius)
coord_angle_inner = angle_inner.points
coord_angle_outer = angle_outer.points
# Combine points
pnts = np.concatenate([
coord_angle_inner,
coord_angle_outer,
coord_angle_inner[0].reshape(1, 3)
])
# Update wedge
mob.set_points_as_corners(pnts)
# Attach updater to mfill
mfill.add_updater(update_mfill)
self.add(clock, l1, l2, mfill)
self.wait(8)
Here are snapshots of 3 points in time for this animation: