欧拉公式的几何意义
video
https://manim.collegebot.ai/data/hls/500642289342857216/main.mp4
Sence prompt
## Geometric Meaning of Euler's Formula \( e^{i\theta} = \cos\theta + i\sin\theta \)
### 【Scene 1: Title and Introduction】
- **Background & Atmosphere:**
- A deep, dark blue gradient background, fading slightly towards black at the edges, evoking a sense of mathematical depth or space. Subtle, slow-moving, faint particle effects (like dust motes or distant stars) can be added for visual interest.
- Display scene number "01" in the top-right corner (small, white text).
- **Main Content:**
- **Title:** Display "Euler's Formula" prominently in the top center. (Font: Sans-serif, Color: Bright White, Animation: `Write`, Duration: 1.5s).
- **Subtitle:** Below the title, display "Unveiling the Geometric Meaning". (Font: Smaller Sans-serif, Color: Light Cyan, Animation: `FadeIn`, Delay: 0.5s after title finishes, Duration: 1s).
- **Formula:** Centered below the subtitle, display Euler's Formula clearly:
\[
e^{i\theta} = \cos\theta + i\sin\theta
\]
(LaTeX, Color: White, Animation: `ReplacementTransform` from the subtitle text or `Write`, Duration: 2s). Key components like \(e^{i\theta}\), \(\cos\theta\), and \(i\sin\theta\) could briefly flash or change color (e.g., to Yellow) upon appearance to draw attention.
- **Camera & Transitions:**
- Static camera focused on the center of the screen.
- A slight, slow zoom-out (scale factor decreases from 1.1 to 1.0 over 3 seconds) after the formula appears, giving a sense of revealing the subject.
- Smooth transition to the next scene (e.g., `FadeOut` all elements over 0.5s).
---
### 【Scene 2: Introducing the Complex Plane】
- **Background & Layout:**
- Transition to a clean, light gray background (#E0E0E0) or a subtle grid background (`NumberPlane` with faint lines) to facilitate geometric visualization.
- Display scene number "02" in the top-right corner (small, dark gray text).
- **Content Presentation:**
- **Coordinate System:** Create a 2D coordinate system representing the complex plane.
- X-axis labeled "Real Axis" (or "Re"). Range: e.g., -2 to 2.
- Y-axis labeled "Imaginary Axis" (or "Im"). Range: e.g., -2 to 2.
- Axis color: Dark Gray. Label color: Black.
- Animation: `Create` for axes and labels (Duration: 2s).
- **Complex Number:** Introduce a generic complex number \(z = x + iy\).
- Display the formula \(z = x + iy\) (LaTeX) in the top-left corner. (Color: Black, Animation: `Write`, Duration: 1s).
- Plot a point \(P\) representing \(z\) in the plane (e.g., at \(1.5 + 1i\)). (Color: Blue `Dot`).
- Draw dashed lines from \(P\) to the Real axis (at \(x=1.5\)) and Imaginary axis (at \(y=1\)). (Line style: Dashed, Color: Gray).
- Label the projections \(x\) and \(y\) (or \(iy\)) on the axes. (Color: Black).
- Animation: Plot the point (`FadeIn`), then draw projection lines (`Create`), then add labels (`Write`). Total duration: 2.5s.
- **Camera & Transitions:**
- Static camera, ensuring the entire complex plane setup is visible.
- Pause briefly after all elements appear. Smooth transition to Scene 3.
---
### 【Scene 3: Geometry of \(\cos\theta + i\sin\theta\) - The Unit Circle】
- **Background & Layout:**
- Maintain the light gray or grid background and the complex plane axes from Scene 2.
- Display scene number "03" in the top-right corner.
- **Content Presentation:**
- **Unit Circle:** Draw a circle centered at the origin with radius 1.
- `Circle(radius=1)`, Color: Red, Stroke Width: Default or slightly thicker.
- Animation: `Create` (Duration: 1.5s).
- **Angle and Point:**
- Mark an angle \(\theta\) starting from the positive Real axis, counter-clockwise. Use an `Arc` object. (e.g., let \(\theta = \pi/4\) or 60 degrees initially for visual clarity). Label the angle \(\theta\). (Angle arc color: Green, Label color: Black).
- Draw a line segment (radius) from the origin (0,0) to the point \(P\) on the unit circle corresponding to angle \(\theta\). (Line color: Blue).
- Mark the point \(P\) clearly. (`Dot`, Color: Blue).
- Animation: Create angle arc and label (`Create`, `Write`), then draw radius (`Create`), then place dot (`FadeIn`). Total duration: 2.5s.
- **Trigonometric Connection:**
- Drop a perpendicular dashed line from point \(P\) to the Real axis, forming a right-angled triangle. (Line style: Dashed, Color: Gray).
- Label the adjacent side (on the Real axis) as \(\cos\theta\).
- Label the opposite side (parallel to the Imaginary axis) as \(\sin\theta\).
- Label the hypotenuse (the radius) as 1.
- Crucially, label the coordinates of point \(P\) as \((\cos\theta, \sin\theta)\) or, emphasizing the complex nature, label the point \(P\) directly as \(\cos\theta + i\sin\theta\). (LaTeX, Color: Black/Blue).
- Animation: Create projection line (`Create`), then Write labels (\(\cos\theta\), \(\sin\theta\), 1) for the triangle sides (`Write`), then display the coordinates/complex label for P (`Write` or `Transform` if a previous label existed). Total duration: 3s.
- **Text Explanation:** Briefly display text (e.g., top-left): "The term \(\cos\theta + i\sin\theta\) represents a point on the unit circle at angle \(\theta\)." (Color: Black, Animation: `FadeIn`).
- **Camera & Transitions:**
- The camera might slightly zoom in (e.g., scale factor 1.2) to focus on the unit circle and the triangle construction.
- Pause to allow absorption of the geometric relationship. Smooth transition.
---
### 【Scene 4: Connecting \(e^{i\theta}\) - Rotation】
- **Background & Layout:**
- Keep the complex plane, unit circle, point \(P\), angle \(\theta\), and the label \(\cos\theta + i\sin\theta\) from Scene 3 visible.
- Display scene number "04" in the top-right corner.
- **Content Presentation:**
- **Introduce \(e^{i\theta}\):**
- Display the term \(e^{i\theta}\) (LaTeX) near the point \(P\), perhaps slightly above or replacing the \(\cos\theta + i\sin\theta\) label using `ReplacementTransform`. (Color: Purple or another distinct color).
- Animation: `Write` or `ReplacementTransform` (Duration: 1.5s).
- **Geometric Meaning Text:** Display explanatory text, e.g., "Euler's formula states that \(e^{i\theta}\) is _exactly this point_." (Appears near the top).
- Followed by: "Geometrically, multiplying by \(e^{i\theta}\) represents a _rotation_ by angle \(\theta\) in the complex plane." (Appears below the first text).
- (Text Color: Black, Animation: `Write` or `FadeIn` line by line, Duration: 2s per line).
- **Visualizing Rotation (Optional but effective):**
- Show the point 1 (i.e., \(1+0i\)) on the Real axis. (Red `Dot`).
- Animate the radius vector (from Scene 3) starting aligned with the Real axis (pointing to 1) and rotating counter-clockwise by angle \(\theta\) to reach point \(P\). The angle arc \(\theta\) can be redrawn during this rotation.
- Animation: Use `Rotate` animation on the radius vector, coordinated with drawing the `Arc`. (Duration: 2s).
- **Camera & Transitions:**
- Camera remains focused on the unit circle area. If rotation is shown, ensure the starting point (1) and the rotation path are clearly visible.
- Smooth transition.
---
### 【Scene 5: Dynamic Visualization - Varying \(\theta\))】
- **Background & Layout:**
- Maintain the complex plane and unit circle. Remove triangle lines/labels if cluttered, keep point P and radius.
- Display scene number "05" in the top-right corner.
- Display the full Euler formula \(e^{i\theta} = \cos\theta + i\sin\theta\) statically in a corner (e.g., top-left) for reference. (Color: Black).
- **Content Presentation:**
- **Animating Theta:** Use a `ValueTracker` for the angle \(\theta\), letting it vary smoothly, e.g., from 0 to \(2\pi\) or \(-\pi\) to \(\pi\).
- **Moving Point:** The point \(P\) on the unit circle moves dynamically as \(\theta\) changes. Its position is always \((\cos\theta, \sin\theta)\).
- **Rotating Radius:** The radius vector from the origin to \(P\) rotates along with the point.
- **Angle Arc:** The angle arc indicating \(\theta\) updates dynamically.
- **Trace Path:** Optionally, use `TracedPath` for point \(P\) to draw the unit circle as it moves.
- **Labels:** Display the current value of \(\theta\) (using `DecimalNumber` or formatted `MathTex` linked to the `ValueTracker`) and update the label \(e^{i\theta}\) next to the point P (or just keep it symbolic).
- Animation: This is a continuous animation driven by `add_updater` methods linked to the `ValueTracker`. The animation should run for several seconds (e.g., 5-8 seconds) to show a full cycle or significant movement. Colors: Point P (Blue), Radius (Blue), Angle Arc (Green), Trace (Red).
- **Camera & Transitions:**
- Static camera, perfectly framing the unit circle and the dynamic elements.
- Fade out elements at the end of the animation sequence.
---
### 【Scene 6: Summary and Significance】
- **Background & Atmosphere:**
- Return to the deep blue gradient background from Scene 1, or use a solid dark background.
- Display scene number "06" in the top-right corner (White text).
- **Content Presentation:**
- **Title:** Display "Geometric Summary" at the top. (Color: Bright White, Animation: `FadeIn`).
- **Core Idea:** Centered text:
- "\(e^{i\theta}\) represents a point on the unit circle in the complex plane."
- "The angle from the positive real axis to this point is \(\theta\)."
- "Its coordinates are \((\cos\theta, \sin\theta)\)."
- (Text color: White, Animation: Appear line by line using `Write` or `FadeIn`, Duration: 1.5s per line).
- **Formula Recap:** Display \(e^{i\theta} = \cos\theta + i\sin\theta\) again below the summary text. (LaTeX, Color: Yellow/Gold, Animation: `FadeIn`).
- **Concluding Thought:** Add a final text line like "A fundamental link between analysis, trigonometry, and geometry." (Color: Light Cyan, Animation: `FadeIn`).
- **Camera & Transitions:**
- Start slightly zoomed in on the title, then slowly zoom out (scale factor decreases e.g., from 1.2 to 1.0) over the duration of the text appearing, revealing all summary points and the final formula.
- Hold the final frame for a couple of seconds before fading out.
---
### 【Overall Requirements & Manim Rules】
- **Visual Style:** Maintain consistency. Use high-resolution rendering. Ensure LaTeX formulas are crisp and clear. Use the specified color palettes (Dark/Spacey for Intro/Outro, Light/Clear for explanation). Use contrasting colors for highlights.
- **Transitions:** Use smooth transitions between scenes (`FadeIn`/`FadeOut`, allow ~0.5s buffer).
- **Timing:** Coordinate camera movements and element animations carefully to guide the viewer's focus. Ensure adequate pauses for comprehension.
- **Manim Rules (from previous example):**
1. **Consistent Background:** Use `self.camera.background_color` in `setup()` for consistency _or_ manage background transitions deliberately. Ensure text/element colors have good contrast against the _current_ background.
2. **Element Tracking:** Use `VGroup` for elements created in each scene/section and ensure a cleanup function (`clear_section` or similar) reliably removes _all_ temporary elements (including transformed ones) unless intended to persist.
3. **Text Wrapping:** Apply `width` parameter to `Text` and `MarkupText` objects, especially for longer explanations or labels near edges, to prevent overflow. Use `.next_to()` with appropriate `buff` and alignment.
4. **Layout Management:** For scenes with distinct regions (like text vs. diagram), define boundaries/anchors and position content `VGroup`s within them. Use `width` on text within these regions.
5. **Correct Element Creation:** Ensure distinct `Mobject` instances are created for different concepts, using unique variable names to avoid displaying incorrect or duplicated visuals.
code
# -*- coding: utf-8 -*-
import os
import numpy as np
import requests
from contextlib import contextmanager
from manim import *
import hashlib
# from moviepy.editor import AudioFileClip # Use Manim's built-in or ensure moviepy is installed if needed
from moviepy.audio.io.AudioFileClip import AudioFileClip # More specific import if needed
import manimpango # For font checking
import random
# --- Font Check ---
DEFAULT_FONT = "Arial" # Use a common default font
available_fonts = manimpango.list_fonts()
final_font = None
if DEFAULT_FONT in available_fonts:
print(f"Font '{DEFAULT_FONT}' found.")
final_font = DEFAULT_FONT
else:
print(f"Warning: Font '{DEFAULT_FONT}' not found. Trying fallback fonts...")
fallback_fonts = ["Helvetica", "Verdana", "DejaVu Sans", "Liberation Sans"]
found_fallback = False
for font in fallback_fonts:
if font in available_fonts:
print(f"Switched to fallback font: '{font}'")
final_font = font
found_fallback = True
break
if not found_fallback:
print(f"Warning: Neither '{DEFAULT_FONT}' nor fallback fonts found. Using Manim default.")
# final_font remains None
# --- Custom Colors (Adjusted for White Background) ---
MY_WHITE = "#FFFFFF" # Background color
MY_BLACK = "#000000" # Define black
MY_DARK_TEXT = "#1F2937" # Primary text color (Dark Gray/Almost Black)
MY_DARK_GRAY = "#444444" # Axes color
MY_BLUE = "#0077CC" # Point P, radius, highlights
MY_RED = "#CC3333" # Unit circle, trace, highlights
MY_GREEN = "#339933" # Angle arc, highlights
MY_PURPLE = "#8833AA" # e^i theta label, highlights
MY_YELLOW = "#B8860B" # Formula recap highlight (Dark Goldenrod - visible on white)
MY_GRAY = "#888888" # Dashed lines
MY_LIGHT_CYAN = "#008B8B" # Concluding thought (Dark Cyan)
# MY_LIGHT_GRAY_BG = "#E0E0E0" # No longer needed for background
# --- Helper Functions for Color Handling ---
def hex_to_rgb(hex_color_str):
"""Converts a hex color string (e.g., '#RRGGBB') to an RGB tuple (0-255)."""
if hasattr(hex_color_str, 'to_hex'):
hex_color_str = hex_color_str.to_hex()
hex_color_str = str(hex_color_str).lstrip('#')
if len(hex_color_str) == 6:
try:
return tuple(int(hex_color_str[i:i+2], 16) for i in (0, 2, 4))
except ValueError:
print(f"Warning: Invalid hex color format '{hex_color_str}'. Defaulting to black.")
return (0, 0, 0)
else:
print(f"Warning: Non-hex color '{hex_color_str}' encountered. Defaulting to black for luminance check.")
return (0, 0, 0)
def calculate_luminance(rgb):
"""Calculates perceived luminance (0-1) from an RGB tuple (0-255)."""
if not isinstance(rgb, (tuple, list)) or len(rgb) != 3:
return 0
r, g, b = [x / 255.0 for x in rgb]
return 0.2126 * r + 0.7152 * g + 0.0722 * b
# --- TTS Caching Setup ---
CACHE_DIR = r"#(output_path)/audio" # Use placeholder
os.makedirs(CACHE_DIR, exist_ok=True)
class CustomVoiceoverTracker:
"""Tracks audio path and duration for TTS."""
def __init__(self, audio_path, duration):
self.audio_path = audio_path
self.duration = duration
def get_cache_filename(text):
"""Generates a unique filename based on the text hash."""
text_hash = hashlib.md5(text.encode('utf-8')).hexdigest()
return os.path.join(CACHE_DIR, f"{text_hash}.mp3")
@contextmanager
def custom_voiceover_tts(text, token="123456", base_url="https://uni-ai.fly.dev/api/manim/tts"):
"""Fetches TTS audio, caches it, and provides path and duration."""
cache_file = get_cache_filename(text)
audio_file = cache_file
if os.path.exists(cache_file):
pass
else:
try:
input_text_encoded = requests.utils.quote(text)
url = f"{base_url}?token={token}&input={input_text_encoded}"
response = requests.get(url, stream=True, timeout=60)
response.raise_for_status()
with open(cache_file, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk: f.write(chunk)
audio_file = cache_file
except requests.exceptions.RequestException as e:
print(f"TTS API request failed: {e}")
tracker = CustomVoiceoverTracker(None, 0)
yield tracker
return
except Exception as e:
print(f"An error occurred during TTS processing: {e}")
if os.path.exists(cache_file): os.remove(cache_file)
tracker = CustomVoiceoverTracker(None, 0)
yield tracker
return
duration = 0
if audio_file and os.path.exists(audio_file):
try:
# Use moviepy to get duration
with AudioFileClip(audio_file) as clip:
duration = clip.duration
# print(f"Audio duration: {duration:.2f}s")
except Exception as e:
print(f"Error processing audio file {audio_file} with moviepy: {e}")
# Fallback or alternative method if needed
# For simplicity, we'll just report the error and continue
audio_file = None # Mark as unusable if duration check failed
duration = 0
else:
audio_file = None
tracker = CustomVoiceoverTracker(audio_file, duration)
try:
yield tracker
finally:
pass
# -----------------------------
# CombinedScene: Euler's Formula Geometry
# -----------------------------
class CombinedScene(MovingCameraScene):
"""
Explains the geometric meaning of Euler's formula e^(i*theta).
Uses English narration.
Background is consistently white.
"""
def setup(self):
MovingCameraScene.setup(self)
if final_font:
Text.set_default(font=final_font)
self.current_scene_num_mob = None
self.section_elements = VGroup()
# --- Set consistent background color HERE ---
self.camera.background_color = MY_WHITE
# Store references
self.axes = None
self.axes_labels = None
self.unit_circle = None
self.point_P = None
self.radius_line = None
self.angle_arc = None
self.angle_label = None
self.p_label_complex = None
self.e_label = None
def update_scene_number(self, number_str):
"""Fades out the old scene number and fades in the new one."""
# Background is always white, so text is always dark
text_color = MY_DARK_TEXT
new_scene_num = Text(number_str, font_size=24, color=text_color).to_corner(UR, buff=MED_LARGE_BUFF).set_z_index(100)
animations = [FadeIn(new_scene_num, run_time=0.5)]
if self.current_scene_num_mob:
if self.current_scene_num_mob is not None:
animations.append(FadeOut(self.current_scene_num_mob, run_time=0.5))
self.play(*animations)
self.current_scene_num_mob = new_scene_num
def clear_section(self, keep_refs=None):
"""Clears elements added to self.section_elements."""
elements_to_remove = VGroup()
elements_to_keep_in_group = VGroup()
keep_mobjects = []
if keep_refs:
for ref_name in keep_refs:
mob = getattr(self, ref_name, None)
if mob:
keep_mobjects.append(mob)
for mob in self.section_elements:
is_kept = False
for keep_mob in keep_mobjects:
if mob is keep_mob:
is_kept = True
break
if isinstance(keep_mob, VGroup) and mob in keep_mob.submobjects:
is_kept = True
break
if is_kept:
elements_to_keep_in_group.add(mob)
else:
elements_to_remove.add(mob)
for mob in elements_to_remove:
if mob is not None and hasattr(mob, 'get_updaters') and mob.get_updaters():
mob.clear_updaters()
valid_elements_to_remove = [elem for elem in elements_to_remove if elem is not None]
if valid_elements_to_remove:
self.play(FadeOut(Group(*valid_elements_to_remove)), run_time=0.5)
self.section_elements = elements_to_keep_in_group
self.wait(0.1)
def construct(self):
self.play_section_01()
self.clear_section()
self.play_section_02()
self.clear_section(keep_refs=['axes', 'axes_labels'])
self.play_section_03()
self.clear_section(keep_refs=['axes', 'axes_labels', 'unit_circle', 'point_P', 'radius_line', 'angle_arc', 'angle_label', 'p_label_complex'])
self.play_section_04()
self.clear_section(keep_refs=['axes', 'axes_labels', 'unit_circle', 'point_P', 'radius_line', 'angle_arc', 'angle_label', 'e_label'])
self.play_section_05()
self.clear_section()
if self.axes and self.axes in self.mobjects: self.remove(self.axes)
if self.axes_labels and self.axes_labels in self.mobjects: self.remove(self.axes_labels)
self.play_section_06()
self.clear_section()
self.wait(2)
if self.current_scene_num_mob:
self.play(FadeOut(self.current_scene_num_mob))
# --- Section 1: Title and Introduction ---
def play_section_01(self):
"""Scene 1: Title and Introduction"""
# Background is set in setup()
self.update_scene_number("01")
title = Text("Euler's Formula", font_size=60, color=MY_DARK_TEXT)
title.to_edge(UP, buff=1.0)
subtitle = Text("Unveiling the Geometric Meaning", font_size=36, color=MY_DARK_TEXT)
subtitle.next_to(title, DOWN, buff=0.5)
formula = MathTex(r"e^{i\theta}", r"=", r"\cos\theta", r"+", r"i\sin\theta", font_size=60, color=MY_DARK_TEXT)
formula.next_to(subtitle, DOWN, buff=0.8)
self.section_elements.add(title, subtitle, formula)
voice_text_01 = "Welcome! Today we explore one of the most beautiful equations in mathematics: Euler's Formula. It connects exponentiation, complex numbers, and trigonometry in a profound way. Let's uncover its geometric meaning."
with custom_voiceover_tts(voice_text_01) as tracker:
if tracker.audio_path and tracker.duration > 0:
self.add_sound(tracker.audio_path)
else:
print("Warning: Narration 1 TTS failed.")
subtitle_voice = Text(voice_text_01, font_size=28, color=MY_DARK_TEXT, width=config.frame_width - 2, should_center=True).to_edge(DOWN, buff=MED_SMALL_BUFF)
self.play(FadeIn(subtitle_voice), run_time=0.5)
self.play(Write(title), run_time=1.5)
self.play(FadeIn(subtitle), run_time=1.0)
self.wait(0.5)
self.play(Write(formula[1]), Write(formula[3]), run_time=0.5) # = and +
self.play(
Write(formula[0]), # e^i theta
run_time=1.5
)
self.play(formula[0].animate.set_color(MY_RED), run_time=0.2)
self.play(
Write(formula[2]), # cos
run_time=1.5
)
self.play(formula[2].animate.set_color(MY_RED), run_time=0.2)
self.play(
Write(formula[4]), # i sin
run_time=1.5
)
self.play(formula[4].animate.set_color(MY_RED), run_time=0.2)
# keep show
formula[0].set_color(MY_DARK_TEXT)
formula[2].set_color(MY_DARK_TEXT)
formula[4].set_color(MY_DARK_TEXT)
# Synchronization
# Adjusted duration calculation (removed the 0.2s reset times)
anim_duration = 0.5 + 1.5 + 1.0 + 0.5 + 0.5 + 1.5 + 1.5 + 1.5
wait_time = max(0, tracker.duration - anim_duration - 0.5) if tracker.duration > 0 else 1.0
if wait_time > 0: self.wait(wait_time)
self.play(FadeOut(subtitle_voice), run_time=0.5)
# --- Section 2: Introducing the Complex Plane ---
def play_section_02(self):
"""Scene 2: Introducing the Complex Plane"""
# REMOVED: Background color change - uses default white now
self.update_scene_number("02")
self.axes = Axes(
x_range=[-2.5, 2.5, 1], y_range=[-2.5, 2.5, 1],
x_length=6, y_length=6,
axis_config={"color": MY_DARK_GRAY, "include_numbers": True, "stroke_width": 2},
tips=False
)
x_label = Text("Real Axis (Re)", font_size=24, color=MY_DARK_TEXT).next_to(self.axes.x_axis.get_right(), DOWN)
y_label = Text("Imaginary Axis (Im)", font_size=24, color=MY_DARK_TEXT).next_to(self.axes.y_axis.get_top(), RIGHT).shift(LEFT*0.5)
self.axes_labels = VGroup(x_label, y_label)
z_formula = MathTex("z = x + iy", font_size=36, color=MY_DARK_TEXT).to_corner(UL, buff=MED_LARGE_BUFF)
x_val, y_val = 1.5, 1.0
point_P = Dot(self.axes.c2p(x_val, y_val), color=MY_BLUE, radius=0.08)
proj_line_x = DashedLine(self.axes.c2p(x_val, 0), self.axes.c2p(x_val, y_val), color=MY_GRAY)
proj_line_y = DashedLine(self.axes.c2p(0, y_val), self.axes.c2p(x_val, y_val), color=MY_GRAY)
proj_label_x = MathTex("x", font_size=30, color=MY_DARK_TEXT).next_to(self.axes.c2p(x_val, 0), DOWN)
proj_label_y = MathTex("iy", font_size=30, color=MY_DARK_TEXT).next_to(self.axes.c2p(0, y_val), LEFT)
projections = VGroup(proj_line_x, proj_line_y, proj_label_x, proj_label_y)
self.section_elements.add(self.axes, self.axes_labels, z_formula, point_P, projections)
voice_text_02 = "To understand the geometry, we need the complex plane. The horizontal axis represents real numbers, and the vertical axis represents imaginary numbers. A complex number, z equals x plus i y, corresponds to a point P with coordinates (x, y)."
with custom_voiceover_tts(voice_text_02) as tracker:
if tracker.audio_path and tracker.duration > 0:
self.add_sound(tracker.audio_path)
else:
print("Warning: Narration 2 TTS failed.")
subtitle_voice = Text(voice_text_02, font_size=28, color=MY_DARK_TEXT, width=config.frame_width - 2, should_center=True).to_edge(DOWN, buff=MED_SMALL_BUFF)
self.play(FadeIn(subtitle_voice), run_time=0.5)
self.play(Create(self.axes), Write(self.axes_labels), run_time=2.0)
self.play(Write(z_formula), run_time=1.0)
self.play(FadeIn(point_P), run_time=0.5)
self.play(Create(proj_line_x), Create(proj_line_y), run_time=1.0)
self.play(Write(proj_label_x), Write(proj_label_y), run_time=1.0)
anim_duration = 0.5 + 2.0 + 1.0 + 0.5 + 1.0 + 1.0
wait_time = max(0, tracker.duration - anim_duration - 0.5) if tracker.duration > 0 else 1.0
if wait_time > 0: self.wait(wait_time)
self.play(FadeOut(subtitle_voice), run_time=0.5)
# --- Section 3: Geometry of cos(theta) + i sin(theta) ---
def play_section_03(self):
"""Scene 3: Unit Circle Geometry"""
# Background is default white
self.update_scene_number("03")
if not self.axes:
print("Error: Axes not found from previous section!")
return
self.add(self.axes, self.axes_labels)
radius_val = self.axes.x_axis.n2p(1)[0] - self.axes.x_axis.n2p(0)[0]
self.unit_circle = Circle(radius=radius_val, color=MY_RED, stroke_width=3)
self.unit_circle.move_to(self.axes.c2p(0,0))
theta_value = PI / 3
point_P_coord = self.axes.c2p(np.cos(theta_value), np.sin(theta_value))
self.point_P = Dot(point_P_coord, color=MY_BLUE, radius=0.08)
self.radius_line = Line(self.axes.c2p(0,0), point_P_coord, color=MY_BLUE, stroke_width=2)
self.angle_arc = Arc(radius=0.4, start_angle=0, angle=theta_value, arc_center=self.axes.c2p(0,0), color=MY_GREEN)
self.angle_label = MathTex(r"\theta", font_size=36, color=MY_GREEN).move_to(
Arc(radius=0.6, start_angle=0, angle=theta_value, arc_center=self.axes.c2p(0,0)).point_from_proportion(0.5)
)
proj_line = DashedLine(point_P_coord, self.axes.c2p(np.cos(theta_value), 0), color=MY_GRAY)
cos_label = MathTex(r"\cos\theta", font_size=30, color=MY_DARK_TEXT).next_to(self.axes.c2p(np.cos(theta_value)/2, 0), DOWN, buff=SMALL_BUFF)
sin_label = MathTex(r"\sin\theta", font_size=30, color=MY_DARK_TEXT).next_to(self.axes.c2p(np.cos(theta_value), np.sin(theta_value)/2), RIGHT, buff=SMALL_BUFF)
hyp_label = Text("1", font_size=30, color=MY_RED).move_to(self.radius_line.get_center() + normalize(self.radius_line.get_vector())@np.array([[0,-1,0],[1,0,0],[0,0,1]])*0.2)
self.p_label_complex = MathTex(r"\cos\theta + i\sin\theta", font_size=36, color=MY_BLUE).next_to(self.point_P, UR, buff=SMALL_BUFF)
explanation = Text("cos(θ) + i sin(θ) is a point on the unit circle.", font_size=28, color=MY_DARK_TEXT)
explanation.to_corner(UL, buff=MED_LARGE_BUFF)
triangle_elements = VGroup(proj_line, cos_label, sin_label, hyp_label)
self.section_elements.add(self.unit_circle, self.angle_arc, self.angle_label, self.radius_line, self.point_P, triangle_elements, self.p_label_complex, explanation)
voice_text_03 = "Now, let's focus on the unit circle - a circle with radius 1 centered at the origin. Consider a point P on this circle. If we draw a line from the origin to P, it forms an angle theta with the positive real axis. Using basic trigonometry, the x-coordinate of P is cosine theta, and the y-coordinate is sine theta. Therefore, the complex number representing point P is exactly cosine theta plus i sine theta."
with custom_voiceover_tts(voice_text_03) as tracker:
if tracker.audio_path and tracker.duration > 0:
self.add_sound(tracker.audio_path)
else:
print("Warning: Narration 3 TTS failed.")
subtitle_voice = Text(voice_text_03, font_size=28, color=MY_DARK_TEXT, width=config.frame_width - 2, should_center=True).to_edge(DOWN, buff=MED_SMALL_BUFF)
self.play(FadeIn(subtitle_voice), run_time=0.5)
self.play(Create(self.unit_circle), run_time=1.5)
self.play(Create(self.angle_arc), Write(self.angle_label), Create(self.radius_line), FadeIn(self.point_P), run_time=2.5)
self.play(Create(proj_line), Write(cos_label), Write(sin_label), Write(hyp_label), run_time=3.0)
self.play(Write(self.p_label_complex), run_time=1.5)
self.play(FadeIn(explanation), run_time=1.0)
anim_duration = 0.5 + 1.5 + 2.5 + 3.0 + 1.5 + 1.0
wait_time = max(0, tracker.duration - anim_duration - 0.5) if tracker.duration > 0 else 1.0
if wait_time > 0: self.wait(wait_time)
self.play(FadeOut(subtitle_voice), run_time=0.5)
# --- Section 4: Connecting e^(i*theta) - Rotation ---
def play_section_04(self):
"""Scene 4: Connecting e^(i*theta) - Rotation"""
# Background is default white
self.update_scene_number("04")
if not all([self.axes, self.axes_labels, self.unit_circle, self.point_P, self.radius_line, self.angle_arc, self.angle_label, self.p_label_complex]):
print("Error: Missing elements from Scene 3!")
return
self.add(self.axes, self.axes_labels, self.unit_circle, self.angle_arc, self.angle_label, self.radius_line, self.point_P, self.p_label_complex)
self.e_label = MathTex(r"e^{i\theta}", font_size=40, color=MY_PURPLE)
self.e_label.move_to(self.p_label_complex.get_center())
expl1 = Text("Euler's formula states that e^(iθ) is exactly this point.", font_size=28, color=MY_DARK_TEXT)
expl2 = Text("Multiplying by e^(iθ) means rotating by angle θ.", font_size=28, color=MY_DARK_TEXT)
explanation_group = VGroup(expl1, expl2).arrange(DOWN, buff=MED_SMALL_BUFF).to_corner(UL, buff=MED_LARGE_BUFF)
point_1 = Dot(self.axes.c2p(1, 0), color=MY_RED, radius=0.08)
label_1 = MathTex("1", font_size=30, color=MY_RED).next_to(point_1, DR, buff=SMALL_BUFF)
rotating_radius = Line(self.axes.c2p(0,0), self.axes.c2p(1,0), color=MY_BLUE, stroke_width=2)
self.section_elements.add(self.e_label, explanation_group, point_1, label_1, rotating_radius)
self.section_elements.add(self.axes, self.axes_labels, self.unit_circle, self.angle_arc, self.angle_label, self.radius_line, self.point_P, self.p_label_complex)
voice_text_04 = "This is where Euler's formula comes in! It tells us that the complex exponential, e to the power of i theta, is precisely equal to this point, cosine theta plus i sine theta. Geometrically, this means e to the i theta represents a point on the unit circle at angle theta. Multiplying any complex number by e to the i theta rotates that number by angle theta around the origin. Watch how the point '1' rotates by theta to reach our point P."
with custom_voiceover_tts(voice_text_04) as tracker:
if tracker.audio_path and tracker.duration > 0:
self.add_sound(tracker.audio_path)
else:
print("Warning: Narration 4 TTS failed.")
subtitle_voice = Text(voice_text_04, font_size=28, color=MY_DARK_TEXT, width=config.frame_width - 2, should_center=True).to_edge(DOWN, buff=MED_SMALL_BUFF)
self.play(FadeIn(subtitle_voice), run_time=0.5)
self.play(ReplacementTransform(self.p_label_complex, self.e_label), run_time=1.5)
self.play(Write(expl1), run_time=2.0)
self.play(Write(expl2), run_time=2.0)
self.wait(1.0)
self.play(FadeIn(point_1), Write(label_1), run_time=1.0)
self.play(Create(rotating_radius), run_time=1.0)
theta_value = PI / 3
self.play(Rotate(rotating_radius, angle=theta_value, about_point=self.axes.c2p(0,0)), run_time=2.0)
self.wait(1.0)
anim_duration = 0.5 + 1.5 + 2.0 + 2.0 + 1.0 + 1.0 + 1.0 + 2.0 + 1.0
wait_time = max(0, tracker.duration - anim_duration - 0.5) if tracker.duration > 0 else 1.0
if wait_time > 0: self.wait(wait_time)
self.play(FadeOut(subtitle_voice), run_time=0.5)
# --- Section 5: Dynamic Visualization ---
def play_section_05(self):
"""Scene 5: Dynamic Visualization"""
# Background is default white
self.update_scene_number("05")
if not all([self.axes, self.axes_labels, self.unit_circle]):
print("Warning: Missing elements from Scene 4! Rebuilding...")
self.axes = Axes(x_range=[-2.5, 2.5, 1], y_range=[-2.5, 2.5, 1], x_length=6, y_length=6, axis_config={"color": MY_DARK_GRAY, "include_numbers": True, "stroke_width": 2}, tips=False)
x_label = Text("Real Axis (Re)", font_size=24, color=MY_DARK_TEXT).next_to(self.axes.x_axis.get_right(), DOWN)
y_label = Text("Imaginary Axis (Im)", font_size=24, color=MY_DARK_TEXT).next_to(self.axes.y_axis.get_top(), RIGHT).shift(LEFT*0.5)
self.axes_labels = VGroup(x_label, y_label)
radius_val = self.axes.x_axis.n2p(1)[0] - self.axes.x_axis.n2p(0)[0]
self.unit_circle = Circle(radius=radius_val, color=MY_RED, stroke_width=3).move_to(self.axes.c2p(0,0))
self.add(self.axes, self.axes_labels, self.unit_circle)
# Add rebuilt elements to section_elements so they get cleared later
self.section_elements.add(self.axes, self.axes_labels, self.unit_circle)
else:
self.add(self.axes, self.axes_labels, self.unit_circle)
# Add carried-over elements to section_elements so they get cleared later
self.section_elements.add(self.axes, self.axes_labels, self.unit_circle)
formula_ref = MathTex(r"e^{i\theta} = \cos\theta + i\sin\theta", font_size=36, color=MY_DARK_TEXT)
formula_ref.to_corner(UL, buff=MED_LARGE_BUFF)
self.section_elements.add(formula_ref)
theta_tracker = ValueTracker(0)
# Recreate dynamic elements or ensure they are correctly referenced
self.point_P = Dot(color=MY_BLUE, radius=0.08)
self.radius_line = Line(self.axes.c2p(0,0), self.axes.c2p(1,0), color=MY_BLUE, stroke_width=2)
self.angle_arc = Arc(radius=0.4, start_angle=0, angle=theta_tracker.get_value(), arc_center=self.axes.c2p(0,0), color=MY_GREEN)
theta_value_text = DecimalNumber(theta_tracker.get_value() * 180/PI, num_decimal_places=0, unit=r"^\circ", font_size=30, color=MY_GREEN)
self.e_label = MathTex(r"e^{i\theta}", font_size=36, color=MY_PURPLE)
self.point_P.add_updater(lambda m: m.move_to(self.axes.c2p(np.cos(theta_tracker.get_value()), np.sin(theta_tracker.get_value()))))
self.radius_line.add_updater(lambda m: m.put_start_and_end_on(self.axes.c2p(0,0), self.point_P.get_center()))
self.angle_arc.add_updater(lambda m: m.become(Arc(radius=0.4, start_angle=0, angle=theta_tracker.get_value(), arc_center=self.axes.c2p(0,0), color=MY_GREEN)))
theta_value_text.add_updater(lambda m: m.set_value(theta_tracker.get_value() * 180/PI).next_to(self.angle_arc, RIGHT, buff=SMALL_BUFF))
self.e_label.add_updater(lambda m: m.next_to(self.point_P, UR, buff=SMALL_BUFF))
trace = TracedPath(self.point_P.get_center, stroke_color=MY_RED, stroke_width=3, stroke_opacity=0.7)
dynamic_elements = VGroup(self.point_P, self.radius_line, self.angle_arc, theta_value_text, self.e_label, trace)
self.add(dynamic_elements)
self.section_elements.add(dynamic_elements)
voice_text_05 = "Let's see this in action! As the angle theta changes, the point e to the i theta moves smoothly around the unit circle. Its position always corresponds to cosine theta plus i sine theta. This beautifully illustrates the rotational nature encoded in Euler's formula."
with custom_voiceover_tts(voice_text_05) as tracker:
if tracker.audio_path and tracker.duration > 0:
self.add_sound(tracker.audio_path)
else:
print("Warning: Narration 5 TTS failed.")
subtitle_voice = Text(voice_text_05, font_size=28, color=MY_DARK_TEXT, width=config.frame_width - 2, should_center=True).to_edge(DOWN, buff=MED_SMALL_BUFF)
self.play(FadeIn(subtitle_voice), run_time=0.5)
self.play(FadeIn(formula_ref), run_time=1.0)
animation_duration = max(6.0, tracker.duration - 2.0) if tracker.duration > 2.0 else 6.0
self.play(theta_tracker.animate.set_value(TAU), run_time=animation_duration, rate_func=linear)
self.play(FadeOut(subtitle_voice), run_time=0.5)
# Clear updaters BEFORE clearing the section
dynamic_elements.clear_updaters()
# Note: persistent elements like axes were added to section_elements earlier
# --- Section 6: Summary and Significance ---
def play_section_06(self):
"""Scene 6: Summary and Significance"""
# REMOVED: Background color change - uses default white now
self.update_scene_number("06")
summary_title = Text("Geometric Summary", font_size=48, color=MY_DARK_TEXT)
summary_title.to_edge(UP, buff=1.0)
point1 = Text("• e^(iθ) represents a point on the unit circle.", font_size=32, color=MY_DARK_TEXT, width=config.frame_width - 4)
point2 = Text("• θ is the angle from the positive real axis.", font_size=32, color=MY_DARK_TEXT, width=config.frame_width - 4)
point3 = Text("• Its coordinates are (cos θ, sin θ).", font_size=32, color=MY_DARK_TEXT, width=config.frame_width - 4)
summary_points = VGroup(point1, point2, point3).arrange(DOWN, buff=MED_LARGE_BUFF * 0.8, aligned_edge=LEFT)
summary_points.next_to(summary_title, DOWN, buff=LARGE_BUFF * 0.8)
formula_recap = MathTex(r"e^{i\theta} = \cos\theta + i\sin\theta", font_size=48, color=MY_YELLOW)
formula_recap.next_to(summary_points, DOWN, buff=LARGE_BUFF * 0.8)
conclusion = Text("A fundamental link between analysis, trigonometry, and geometry.", font_size=32, color=MY_DARK_TEXT, width=config.frame_width - 4)
conclusion.next_to(formula_recap, DOWN, buff=LARGE_BUFF * 0.8)
self.section_elements.add(summary_title, summary_points, formula_recap, conclusion)
voice_text_06 = "In summary, Euler's formula tells us that e to the i theta is a point on the unit circle in the complex plane. The angle theta determines its position, and its coordinates are given by cosine theta and sine theta. This elegant equation provides a powerful bridge between exponential functions, trigonometry, and the geometry of rotations."
with custom_voiceover_tts(voice_text_06) as tracker:
if tracker.audio_path and tracker.duration > 0:
self.add_sound(tracker.audio_path)
else:
print("Warning: Narration 6 TTS failed.")
subtitle_voice = Text(voice_text_06, font_size=28, color=MY_DARK_TEXT, width=config.frame_width - 2, should_center=True).to_edge(DOWN, buff=MED_SMALL_BUFF)
self.play(FadeIn(subtitle_voice), run_time=0.5)
self.play(FadeIn(summary_title), run_time=1.5)
self.play(AnimationGroup(*[FadeIn(p, shift=UP*0.1) for p in summary_points], lag_ratio=0.4), run_time=3.0)
self.play(FadeIn(formula_recap), run_time=1.5)
self.play(FadeIn(conclusion), run_time=1.5)
anim_duration = 0.5 + 1.5 + 3.0 + 1.5 + 1.5
wait_time = max(0, tracker.duration - anim_duration - 0.5) if tracker.duration > 0 else 1.0
if wait_time > 0: self.wait(wait_time)
self.play(FadeOut(subtitle_voice), run_time=0.5)
# --- Main execution block ---
if __name__ == "__main__":
config.pixel_height = 1080
config.pixel_width = 1920
config.frame_rate = 30
config.output_file = "CombinedScene"
config.disable_caching = True
# Set output directory using placeholder
config.media_dir = r"#(output_path)" # IMPORTANT: Use the placeholder
scene = CombinedScene()
scene.render()
print(f"Scene rendering finished. Output in: {config.media_dir}")