aboutsummaryrefslogtreecommitdiffstats
path: root/scenes/v17_cicloide.py
diff options
context:
space:
mode:
authorElvis Claros Castro <elvis@claros.ar>2026-09-26 20:21:47 -0300
committerElvis Claros Castro <elvis@claros.ar>2026-09-26 20:21:47 -0300
commit59355909f2de9236af8168a26c70bcf6caa3b285 (patch)
tree686186e2086f81aa22ad25e78eb29fca3cbadc9a /scenes/v17_cicloide.py
download100cia-videos-59355909f2de9236af8168a26c70bcf6caa3b285.tar.gz
100cia-videos-59355909f2de9236af8168a26c70bcf6caa3b285.zip
Import video pipeline as it was
Diffstat (limited to 'scenes/v17_cicloide.py')
-rw-r--r--scenes/v17_cicloide.py275
1 files changed, 275 insertions, 0 deletions
diff --git a/scenes/v17_cicloide.py b/scenes/v17_cicloide.py
new file mode 100644
index 0000000..5e6d4b9
--- /dev/null
+++ b/scenes/v17_cicloide.py
@@ -0,0 +1,275 @@
+# -*- coding: utf-8 -*-
+"""La braquistocrona: la recta pierde contra la cicloide.
+Todo el movimiento sale de la fisica real (caida libre sin rozamiento)."""
+import sys, os, math
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+from manim import *
+from tiktok import *
+
+TH1 = 4.0553 # angulo final: da D/H = 3, la curva pasa por debajo
+DX = 7.0
+RR = DX / (TH1 - math.sin(TH1))
+HH = RR * (1 - math.cos(TH1))
+X0, Y0 = -3.5, 3.15
+T_C = 1.55 # lo que tarda la cicloide en pantalla
+G = RR * (TH1 / T_C) ** 2 # gravedad ajustada a ese tiempo
+LL = math.hypot(DX, HH)
+AA = G * HH / LL
+T_L = math.sqrt(2 * LL / AA) # 2,18 s: la recta llega 40% mas tarde
+P0 = np.array([X0, Y0, 0.0])
+P1 = np.array([X0 + DX, Y0 - HH, 0.0])
+UU = (P1 - P0) / LL
+VMAX = math.sqrt(4 * G * RR)
+
+RW = 1.12 # rueda de la bici
+XW = -PI * RW
+YW = 0.55
+R2, BX, BY = 1.22, 0.0, 0.30 # cuenco de la tautocrona
+T_TAU = 2.0
+WW = PI / (2 * T_TAU)
+
+
+def cyc(th):
+ return np.array([X0 + RR * (th - math.sin(th)), Y0 - RR * (1 - math.cos(th)), 0.0])
+
+
+def marca(th):
+ return np.array([XW + RW * (th - math.sin(th)), YW + RW * (1 - math.cos(th)), 0.0])
+
+
+def cuenco(th):
+ return np.array([BX + R2 * (th - math.sin(th)) - R2 * PI,
+ BY + 2 * R2 - R2 * (1 - math.cos(th)), 0.0])
+
+
+def poli(f, a, b, col, w=8, n=200):
+ m = VMobject(stroke_color=col, stroke_width=w)
+ m.set_points_as_corners([f(t) for t in np.linspace(a, b, n)])
+ return m
+
+
+class Cicloide(TikTok):
+ NAME = "cicloide"
+
+ def construct(self):
+ self.prepare()
+ chip = self.chip("¿Cuál gana?", AMBAR)
+
+ plata = DashedLine([X0 - 0.8, Y0 + 0.66, 0], [X0 + 1.7, Y0 + 0.66, 0],
+ stroke_width=3, color=GRIS, dash_length=0.1)
+ pc = Dot(radius=0.19, color=AMBAR).move_to([X0 - 0.4, Y0 + 0.66, 0])
+ pr = Dot(radius=0.19, color=CELESTE).move_to([X0 + 0.08, Y0 + 0.66, 0])
+ meta = DashedLine(P1 + UP * 1.35, P1 + DOWN * 0.95, stroke_width=6,
+ color=VERDE, dash_length=0.14)
+ rotm = Text("META", font=FONT, font_size=32, weight=BOLD, color=VERDE)
+ rotm.next_to(meta, UP, buff=0.14)
+
+ leyenda = VGroup(
+ VGroup(Dot(radius=0.15, color=AMBAR),
+ Text("curva", font=FONT, font_size=38, weight=BOLD,
+ color=AMBAR)).arrange(RIGHT, buff=0.22),
+ VGroup(Dot(radius=0.15, color=CELESTE),
+ Text("recta", font=FONT, font_size=38, weight=BOLD,
+ color=CELESTE)).arrange(RIGHT, buff=0.22),
+ ).arrange(RIGHT, buff=1.3).move_to([0, -1.3, 0])
+
+ with self.beat(0) as b:
+ b.play(FadeIn(chip, shift=DOWN * 0.3), run_time=0.4)
+ b.play(Create(plata), run_time=0.4)
+ b.play(FadeIn(pc, scale=0.4), FadeIn(pr, scale=0.4), run_time=0.5)
+ b.play(Create(meta), FadeIn(rotm), FadeIn(leyenda), run_time=0.7)
+
+ recta = Line(P0, P1, stroke_width=8, color=CELESTE)
+ curva = poli(cyc, 0, TH1, AMBAR)
+
+ with self.beat(1) as b:
+ b.play(Create(recta), run_time=0.9)
+ b.play(Create(curva), run_time=max(0.9, b.floor - 1.4))
+
+ inter = Text("?", font=FONT, font_size=115, weight=BOLD,
+ color=WHITE).move_to([0.0, 1.62, 0])
+
+ with self.beat(2) as b:
+ b.play(FadeIn(inter, scale=0.5), run_time=0.5)
+ b.play(Indicate(inter, scale_factor=1.25, color=AMBAR), run_time=0.9)
+
+ rot = Text("el más corto", font=FONT, font_size=40, weight=BOLD,
+ color=CELESTE).move_to([-0.15, 3.02, 0])
+
+ with self.beat(3) as b:
+ b.play(FadeOut(inter), run_time=0.3)
+ b.play(recta.animate.set_stroke(width=12), FadeIn(rot, shift=UP * 0.2),
+ run_time=0.55)
+ b.play(ShowPassingFlash(recta.copy().set_stroke(WHITE, 13), time_width=0.5),
+ run_time=1.0)
+ b.play(recta.animate.set_stroke(width=8), run_time=0.3)
+
+ # --- la carrera --------------------------------------------------------
+ T = ValueTracker(0.0)
+
+ def up_c(m):
+ t = min(T.get_value(), T_C)
+ m.move_to(cyc(min(t * math.sqrt(G / RR), TH1)))
+
+ def up_r(m):
+ t = min(T.get_value(), T_L)
+ m.move_to(P0 + UU * (0.5 * AA * t * t))
+
+ med = VGroup(Circle(radius=0.34, stroke_width=0, fill_color=AMBAR, fill_opacity=1),
+ Text("1º", font=FONT, font_size=34, weight=BOLD, color="#0B0F1A"))
+ med.move_to(P1 + UP * 0.72).set_opacity(0)
+ med.add_updater(lambda m: m.set_opacity(1.0 if T.get_value() >= T_C else 0.0))
+
+ with self.beat(4) as b:
+ b.play(FadeOut(rot), FadeOut(plata), pc.animate.move_to(P0),
+ pr.animate.move_to(P0), run_time=0.5)
+ ya = Text("¡LARGA!", font=FONT, font_size=66, weight=BOLD,
+ color=VERDE).move_to([0, 1.7, 0])
+ b.play(FadeIn(ya, scale=1.7), run_time=0.3)
+ b.play(FadeOut(ya), run_time=0.22)
+ pc.add_updater(up_c)
+ pr.add_updater(up_r)
+ b.add(med)
+ b.play(T.animate.set_value(T_L), run_time=T_L, rate_func=linear)
+ pc.clear_updaters(); pr.clear_updaters(); med.clear_updaters()
+
+ nivel = DashedLine([X0 - 0.2, Y0 - HH, 0], [X0 + DX + 0.2, Y0 - HH, 0],
+ stroke_width=4, color=GRIS, dash_length=0.12)
+ fondo = cyc(PI)
+ nota = Text("pasa por debajo de la meta", font=FONT, font_size=36, weight=BOLD,
+ color=ROSA).move_to([0.3, -0.95, 0])
+ flecha = Arrow(nota.get_top() + RIGHT * 0.9, fondo + DOWN * 0.22, buff=0.06,
+ color=ROSA, stroke_width=6, max_tip_length_to_length_ratio=0.2)
+ gap = VGroup(
+ Text("la recta tardó", font=FONT, font_size=38, weight=BOLD, color=WHITE),
+ Text("40% más", font=FONT, font_size=64, weight=BOLD, color=CELESTE),
+ ).arrange(RIGHT, buff=0.3).move_to([0.0, -2.15, 0])
+
+ with self.beat(5) as b:
+ b.play(FadeOut(leyenda), Create(nivel), run_time=0.45)
+ b.play(FadeIn(nota, shift=UP * 0.2), GrowArrow(flecha), run_time=0.6)
+ b.play(FadeIn(gap, shift=UP * 0.2), run_time=0.55)
+
+ # --- por que gana: velocidad temprana ----------------------------------
+ riel_c = Rectangle(width=6.2, height=0.32, stroke_width=0, fill_color="#1B2233",
+ fill_opacity=1).move_to([0.0, -1.30, 0])
+ riel_r = riel_c.copy().move_to([0.0, -2.20, 0])
+ bar_c = Rectangle(width=0.02, height=0.32, stroke_width=0, fill_color=AMBAR,
+ fill_opacity=1).move_to(riel_c.get_left(), LEFT)
+ bar_r = Rectangle(width=0.02, height=0.32, stroke_width=0, fill_color=CELESTE,
+ fill_opacity=1).move_to(riel_r.get_left(), LEFT)
+ lab_c = Text("curva", font=FONT, font_size=30, weight=BOLD, color=AMBAR)
+ lab_r = Text("recta", font=FONT, font_size=30, weight=BOLD, color=CELESTE)
+ lab_c.next_to(riel_c, LEFT, buff=0.18)
+ lab_r.next_to(riel_r, LEFT, buff=0.18)
+ izq_c, izq_r = riel_c.get_left()[0], riel_r.get_left()[0]
+
+ def barra(m, v, x0, y):
+ w = max(0.02, 6.2 * v / VMAX)
+ m.stretch_to_fit_width(w)
+ m.move_to([x0 + w / 2, y, 0])
+
+ def ub_c(m):
+ t = min(T.get_value(), T_C)
+ th = t * math.sqrt(G / RR)
+ barra(m, math.sqrt(max(0.0, 2 * G * RR * (1 - math.cos(th)))), izq_c, -1.30)
+
+ def ub_r(m):
+ barra(m, AA * min(T.get_value(), T_L), izq_r, -2.20)
+
+ with self.beat(6) as b:
+ b.play(FadeOut(VGroup(nivel, flecha, nota, gap, med)), run_time=0.35)
+ T.set_value(0.0)
+ b.play(pc.animate.move_to(P0), pr.animate.move_to(P0),
+ FadeIn(VGroup(riel_c, riel_r, lab_c, lab_r, bar_c, bar_r)), run_time=0.45)
+ pc.add_updater(up_c); pr.add_updater(up_r)
+ bar_c.add_updater(ub_c); bar_r.add_updater(ub_r)
+ b.play(T.animate.set_value(T_L), run_time=max(1.8, b.floor - 1.1),
+ rate_func=linear)
+ for m in (pc, pr, bar_c, bar_r):
+ m.clear_updaters()
+
+ # --- la rueda de la bici ------------------------------------------------
+ viejo = VGroup(recta, curva, meta, rotm, pc, pr,
+ riel_c, riel_r, lab_c, lab_r, bar_c, bar_r)
+ piso = Line([-3.7, YW, 0], [3.7, YW, 0], stroke_width=6, color="#3A5487")
+ TH = ValueTracker(0.0)
+
+ def dibujar_rueda():
+ th = TH.get_value()
+ c = np.array([XW + RW * th, YW + RW, 0.0])
+ g = VGroup(Circle(radius=RW, stroke_width=6, color=CELESTE).move_to(c))
+ for k in range(3):
+ a = -th - PI / 2 + k * TAU / 3
+ g.add(Line(c, c + RW * np.array([math.cos(a), math.sin(a), 0.0]),
+ stroke_width=4, color="#3A5487"))
+ return g
+
+ rueda = always_redraw(dibujar_rueda)
+ clavo = always_redraw(lambda: Dot(radius=0.14, color=AMBAR).move_to(marca(TH.get_value())))
+ rastro = TracedPath(lambda: marca(TH.get_value()), stroke_color=AMBAR, stroke_width=8)
+ nom = Text("CICLOIDE", font=FONT, font_size=66, weight=BOLD,
+ color=AMBAR).move_to([0, -1.15, 0])
+
+ with self.beat(7) as b:
+ b.play(FadeOut(viejo), run_time=0.4)
+ b.play(Create(piso), run_time=0.3)
+ b.add(rastro, rueda, clavo)
+ b.play(TH.animate.set_value(TAU), run_time=max(1.6, b.floor - 1.5),
+ rate_func=linear)
+ b.remove(rueda, clavo, rastro)
+ arco = poli(marca, 0, TAU, AMBAR)
+ b.add(arco)
+ b.play(FadeIn(nom, shift=UP * 0.2), run_time=0.45)
+
+ # --- la tautocrona ------------------------------------------------------
+ tazon = poli(cuenco, 0, TAU, AMBAR)
+ TH0 = [0.30 * PI, 0.56 * PI, 0.82 * PI]
+ cols = [ROSA, CELESTE, VERDE]
+ bolas = VGroup(*[Dot(radius=r, color=c).move_to(cuenco(t))
+ for t, c, r in zip(TH0, cols, (0.22, 0.175, 0.13))])
+ guias = VGroup(*[DashedLine([-4.1, cuenco(t)[1], 0], cuenco(t), stroke_width=3,
+ color=c, dash_length=0.1)
+ for t, c in zip(TH0, cols)])
+
+ with self.beat(8) as b:
+ b.play(FadeOut(VGroup(nom, piso)), run_time=0.3)
+ b.play(Transform(arco, tazon), run_time=0.9)
+ b.play(LaggedStart(*[FadeIn(d, scale=0.5) for d in bolas], lag_ratio=0.25),
+ run_time=0.8)
+ b.play(Create(guias), run_time=0.5)
+
+ TT = ValueTracker(0.0)
+
+ def soltar(th0):
+ def u(m):
+ c = math.cos(th0 / 2) * math.cos(WW * TT.get_value())
+ m.move_to(cuenco(2 * math.acos(max(-1.0, min(1.0, c)))))
+ return u
+
+ with self.beat(9) as b:
+ b.play(FadeOut(guias), run_time=0.25)
+ for d, t0 in zip(bolas, TH0):
+ d.add_updater(soltar(t0))
+ b.play(TT.animate.set_value(T_TAU), run_time=T_TAU, rate_func=linear)
+ for d in bolas:
+ d.clear_updaters()
+ juntas = Text("las tres, juntas", font=FONT, font_size=58, weight=BOLD,
+ color=VERDE).move_to([0, -1.5, 0])
+ b.play(Flash(bolas[0], color=WHITE, flash_radius=0.9, num_lines=18),
+ FadeIn(juntas, shift=UP * 0.25), run_time=0.7)
+
+ cierre = VGroup(
+ Text("1696", font=FONT, font_size=76, weight=BOLD, color=AMBAR),
+ Text("Bernoulli desafía", font=FONT, font_size=44, weight=BOLD, color=WHITE),
+ Text("a los matemáticos", font=FONT, font_size=44, weight=BOLD, color=WHITE),
+ Text("Newton lo resuelve", font=FONT, font_size=46, weight=BOLD, color=VERDE),
+ Text("esa misma noche", font=FONT, font_size=46, weight=BOLD, color=VERDE),
+ ).arrange(DOWN, buff=0.26).move_to(UP * 0.75)
+
+ with self.beat(10) as b:
+ b.play(FadeOut(VGroup(arco, bolas, juntas)), run_time=0.4)
+ b.play(FadeIn(cierre[:3], shift=UP * 0.25), run_time=0.7)
+ b.play(FadeIn(cierre[3:], shift=UP * 0.25), run_time=0.7)
+
+ self.finish()