0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

manimの作法 その53

Posted at

概要

manimの作法、調べてみた。
複素数、使ってみた。

サンプルコード

from manimlib.imports import *

class FourierCirclesScene(Scene):
	CONFIG = {
		"n_vectors": 10,
		"big_radius": 2,
		"colors": [BLUE_D, BLUE_C, BLUE_E, GREY_BROWN, ],
		"circle_style": {
			"stroke_width": 2,
		},
		"vector_config": {
			"buff": 0,
			"max_tip_length_to_length_ratio": 0.35,
			"tip_length": 0.15,
			"max_stroke_width_to_length_ratio": 10,
			"stroke_width": 2,
		},
		"circle_config": {
			"stroke_width": 1,
		},
		"base_frequency": 1,
		"slow_factor": 0.25,
		"center_point": ORIGIN,
		"parametric_function_step_size": 0.001,
		"drawn_path_color": YELLOW,
		"drawn_path_stroke_width": 2,
	}
	def setup(self):
		self.slow_factor_tracker = ValueTracker(self.slow_factor)
		self.vector_clock = ValueTracker(0)
		self.vector_clock.add_updater(lambda m, dt: m.increment_value(self.get_slow_factor() * dt))
		self.add(self.vector_clock)
	def get_slow_factor(self):
		return self.slow_factor_tracker.get_value()
	def get_vector_time(self):
		return self.vector_clock.get_value()
	def get_freqs(self):
		n = self.n_vectors
		all_freqs = list(range(n // 2, -n // 2, -1))
		all_freqs.sort(key = abs)
		return all_freqs
	def get_coefficients(self):
		return [complex(0) for x in range(self.n_vectors)]
	def get_color_iterator(self):
		return it.cycle(self.colors)
	def get_rotating_vectors(self, freqs = None, coefficients = None):
		vectors = VGroup()
		self.center_tracker = VectorizedPoint(self.center_point)
		if freqs is None:
			freqs = self.get_freqs()
		if coefficients is None:
			coefficients = self.get_coefficients()
		last_vector = None
		for freq, coefficient in zip(freqs, coefficients):
			if last_vector:
				center_func = last_vector.get_end
			else:
				center_func = self.center_tracker.get_location
			vector = self.get_rotating_vector(coefficient = coefficient, freq = freq, center_func = center_func, )
			vectors.add(vector)
			last_vector = vector
		return vectors
	def get_rotating_vector(self, coefficient, freq, center_func):
		vector = Vector(RIGHT, **self.vector_config)
		vector.scale(abs(coefficient))
		if abs(coefficient) == 0:
			phase = 0
		else:
			phase = np.log(coefficient).imag
		vector.rotate(phase, about_point = ORIGIN)
		vector.freq = freq
		vector.coefficient = coefficient
		vector.center_func = center_func
		vector.add_updater(self.update_vector)
		return vector
	def update_vector(self, vector, dt):
		time = self.get_vector_time()
		coef = vector.coefficient
		freq = vector.freq
		phase = np.log(coef).imag
		vector.set_length(abs(coef))
		vector.set_angle(phase + time * freq * TAU)
		vector.shift(vector.center_func() - vector.get_start())
		return vector
	def get_circles(self, vectors):
		return VGroup(*[self.get_circle(vector, color=color) for vector, color in zip(vectors, self.get_color_iterator())])
	def get_circle(self, vector, color = BLUE):
		circle = Circle(color=color, **self.circle_config)
		circle.center_func = vector.get_start
		circle.radius_func = vector.get_length
		circle.add_updater(self.update_circle)
		return circle
	def update_circle(self, circle):
		circle.set_width(2 * circle.radius_func())
		circle.move_to(circle.center_func())
		return circle
	def get_vector_sum_path(self, vectors, color = YELLOW):
		coefs = [v.coefficient for v in vectors]
		freqs = [v.freq for v in vectors]
		center = vectors[0].get_start()
		path = ParametricFunction(lambda t: center + reduce(op.add, [complex_to_R3(coef * np.exp(TAU * 1j * freq * t)) for coef, freq in zip(coefs, freqs)]), t_min=0, t_max=1, color=color, step_size=self.parametric_function_step_size, )
		return path
	def get_drawn_path_alpha(self):
		return self.get_vector_time()
	def get_drawn_path(self, vectors, stroke_width = None, **kwargs):
		if stroke_width is None:
			stroke_width = self.drawn_path_stroke_width
		path = self.get_vector_sum_path(vectors, **kwargs)
		broken_path = CurvesAsSubmobjects(path)
		broken_path.curr_time = 0
		def update_path(path, dt):
			alpha = self.get_drawn_path_alpha()
			n_curves = len(path)
			for a, sp in zip(np.linspace(0, 1, n_curves), path):
				b = alpha - a
				if b < 0:
					width = 0
				else:
					width = stroke_width * (1 - (b % 1))
				sp.set_stroke(width = width)
			path.curr_time += dt
			return path
		broken_path.set_color(self.drawn_path_color)
		broken_path.add_updater(update_path)
		return broken_path
	def get_y_component_wave(self, vectors, left_x = 1, color = PINK, n_copies = 2, right_shift_rate = 5):
		path = self.get_vector_sum_path(vectors)
		wave = ParametricFunction(lambda t: op.add(right_shift_rate * t * LEFT, path.function(t)[1] * UP), t_min = path.t_min, t_max = path.t_max, color = color, )
		wave_copies = VGroup(*[wave.copy() for x in range(n_copies)])
		wave_copies.arrange(RIGHT, buff = 0)
		top_point = wave_copies.get_top()
		wave.creation = ShowCreation(wave, run_time = (1 / self.get_slow_factor()), rate_func = linear, )
		cycle_animation(wave.creation)
		wave.add_updater(lambda m: m.shift((m.get_left()[0] - left_x) * LEFT))
		def update_wave_copies(wcs):
			index = int(wave.creation.total_time * self.get_slow_factor())
			wcs[:index].match_style(wave)
			wcs[index:].set_stroke(width = 0)
			wcs.next_to(wave, RIGHT, buff = 0)
			wcs.align_to(top_point, UP)
		wave_copies.add_updater(update_wave_copies)
		return VGroup(wave, wave_copies)
	def get_wave_y_line(self, vectors, wave):
		return DashedLine(vectors[-1].get_end(), wave[0].get_end(), stroke_width = 1, dash_length = DEFAULT_DASH_LENGTH * 0.5, )
	def get_coefficients_of_path(self, path, n_samples = 10000, freqs = None):
		if freqs is None:
			freqs = self.get_freqs()
		dt = 1 / n_samples
		ts = np.arange(0, 1, dt)
		samples = np.array([path.point_from_proportion(t) for t in ts])
		samples -= self.center_point
		complex_samples = samples[:, 0] + 1j * samples[:, 1]
		result = []
		for freq in freqs:
			riemann_sum = np.array([np.exp(-TAU * 1j * freq * t) * cs for t, cs in zip(ts, complex_samples)]).sum() * dt
			result.append(riemann_sum)
		return result

class test(FourierCirclesScene):
	CONFIG = {
		"n_vectors": 51,
		"center_point": ORIGIN,
		"slow_factor": 0.1,
		"n_cycles": 2,
		"tex": "\\pi",
		"start_drawn": False,
		"max_circle_stroke_width": 1,
	}
	def construct(self):
		self.add_vectors_circles_path()
		for n in range(self.n_cycles):
			self.run_one_cycle()
	def add_vectors_circles_path(self):
		path = self.get_path()
		coefs = self.get_coefficients_of_path(path)
		vectors = self.get_rotating_vectors(coefficients = coefs)
		circles = self.get_circles(vectors)
		self.set_decreasing_stroke_widths(circles)
		drawn_path = self.get_drawn_path(vectors)
		if self.start_drawn:
			self.vector_clock.increment_value(1)
		self.add(path)
		self.add(vectors)
		self.add(circles)
		self.add(drawn_path)
		self.vectors = vectors
		self.circles = circles
		self.path = path
		self.drawn_path = drawn_path
	def run_one_cycle(self):
		time = 1 / self.slow_factor
		self.wait(time)
	def set_decreasing_stroke_widths(self, circles):
		mcsw = self.max_circle_stroke_width
		for k, circle in zip(it.count(1), circles):
			circle.set_stroke(width=max(mcsw / k, mcsw, ))
		return circles
	def get_path(self):
		tex_mob = TexMobject(self.tex)
		tex_mob.set_height(6)
		path = tex_mob.family_members_with_points()[0]
		path.set_fill(opacity = 0)
		path.set_stroke(WHITE, 1)
		return path





生成した動画

以上。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?