mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 20:43:56 +08:00
update
This commit is contained in:
@ -68,48 +68,6 @@ class OpeningManimExample(Scene):
|
|||||||
self.wait(2)
|
self.wait(2)
|
||||||
|
|
||||||
|
|
||||||
class InteractiveDevlopment(Scene):
|
|
||||||
def construct(self):
|
|
||||||
circle = Circle()
|
|
||||||
circle.set_fill(BLUE, opacity=0.5)
|
|
||||||
circle.set_stroke(BLUE_E, width=4)
|
|
||||||
square = Square()
|
|
||||||
|
|
||||||
self.play(ShowCreation(square))
|
|
||||||
self.wait()
|
|
||||||
|
|
||||||
# This opens an iPython termnial where you can keep writing
|
|
||||||
# lines as if they were part of this construct method
|
|
||||||
self.embed()
|
|
||||||
# Try copying and pasting some of the lines below into
|
|
||||||
# the interactive shell
|
|
||||||
self.play(ReplacementTransform(square, circle))
|
|
||||||
self.wait()
|
|
||||||
self.play(circle.stretch, 4, 0)
|
|
||||||
self.play(Rotate(circle, 90 * DEGREES))
|
|
||||||
self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25)
|
|
||||||
|
|
||||||
text = Text("""
|
|
||||||
In general, using the interactive shell
|
|
||||||
is very helpful when developing new scenes
|
|
||||||
""")
|
|
||||||
self.play(Write(text))
|
|
||||||
|
|
||||||
# In the interactive shell, you can just type
|
|
||||||
# play, add, remove, clear, wait, save_state and restore,
|
|
||||||
# instead of self.play, self.add, self.remove, etc.
|
|
||||||
|
|
||||||
# To interact with the window, type touch(). You can then
|
|
||||||
# scroll in the window, or zoom by holding down 'z' while scrolling,
|
|
||||||
# and change camera perspective by holding down 'd' while moving
|
|
||||||
# the mouse. Press 'r' to reset to the standard camera position.
|
|
||||||
# Press 'q' to stop interacting with the window and go back to
|
|
||||||
# typing new commands into the shell.
|
|
||||||
|
|
||||||
# In principle you can customize a scene
|
|
||||||
always(circle.move_to, self.mouse_point)
|
|
||||||
|
|
||||||
|
|
||||||
class AnimatingMethods(Scene):
|
class AnimatingMethods(Scene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
grid = Tex(r"\pi").get_grid(10, 10, height=4)
|
grid = Tex(r"\pi").get_grid(10, 10, height=4)
|
||||||
@ -122,7 +80,10 @@ class AnimatingMethods(Scene):
|
|||||||
# to the left, but the following line animates that motion.
|
# to the left, but the following line animates that motion.
|
||||||
self.play(grid.shift, 2 * LEFT)
|
self.play(grid.shift, 2 * LEFT)
|
||||||
# The same applies for any method, including those setting colors.
|
# The same applies for any method, including those setting colors.
|
||||||
|
self.play(grid.set_color, YELLOW)
|
||||||
|
self.wait()
|
||||||
self.play(grid.set_submobject_colors_by_gradient, BLUE, GREEN)
|
self.play(grid.set_submobject_colors_by_gradient, BLUE, GREEN)
|
||||||
|
self.wait()
|
||||||
self.play(grid.set_height, TAU - MED_SMALL_BUFF)
|
self.play(grid.set_height, TAU - MED_SMALL_BUFF)
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
@ -362,6 +323,166 @@ class UpdatersExample(Scene):
|
|||||||
self.wait(4 * PI)
|
self.wait(4 * PI)
|
||||||
|
|
||||||
|
|
||||||
|
class GraphExample(Scene):
|
||||||
|
def construct(self):
|
||||||
|
axes = Axes((-3, 10), (-1, 8))
|
||||||
|
axes.add_coordinate_labels()
|
||||||
|
|
||||||
|
self.play(Write(axes, lag_ratio=0.01, run_time=1))
|
||||||
|
|
||||||
|
# Axes.get_graph will return the graph of a function
|
||||||
|
sin_graph = axes.get_graph(
|
||||||
|
lambda x: 2 * math.sin(x),
|
||||||
|
color=BLUE,
|
||||||
|
)
|
||||||
|
# By default, it draws it so as to somewhat smoothly interpolate
|
||||||
|
# between sampled points (x, f(x)). If the graph is meant to have
|
||||||
|
# a corner, though, you can set use_smoothing to False
|
||||||
|
relu_graph = axes.get_graph(
|
||||||
|
lambda x: max(x, 0),
|
||||||
|
use_smoothing=False,
|
||||||
|
color=YELLOW,
|
||||||
|
)
|
||||||
|
# For discontinuous functions, you can specify the point of
|
||||||
|
# discontinuity so that it does not try to draw over the gap.
|
||||||
|
step_graph = axes.get_graph(
|
||||||
|
lambda x: 2.0 if x > 3 else 1.0,
|
||||||
|
discontinuities=[3],
|
||||||
|
color=GREEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Axes.get_graph_label takes in either a string or a mobject.
|
||||||
|
# If it's a string, it treats it as a LaTeX expression. By default
|
||||||
|
# it places the label next to the graph near the right side, and
|
||||||
|
# has it match the color of the graph
|
||||||
|
sin_label = axes.get_graph_label(sin_graph, "\\sin(x)")
|
||||||
|
relu_label = axes.get_graph_label(relu_graph, Text("ReLU"))
|
||||||
|
step_label = axes.get_graph_label(step_graph, Text("Step"), x=4)
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
ShowCreation(sin_graph),
|
||||||
|
FadeIn(sin_label, RIGHT),
|
||||||
|
)
|
||||||
|
self.wait(2)
|
||||||
|
self.play(
|
||||||
|
ReplacementTransform(sin_graph, relu_graph),
|
||||||
|
FadeTransform(sin_label, relu_label),
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
ReplacementTransform(relu_graph, step_graph),
|
||||||
|
FadeTransform(relu_label, step_label),
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
parabola = axes.get_graph(lambda x: 0.25 * x**2)
|
||||||
|
parabola.set_stroke(BLUE)
|
||||||
|
self.play(
|
||||||
|
FadeOut(step_graph),
|
||||||
|
FadeOut(step_label),
|
||||||
|
ShowCreation(parabola)
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
# You can use axes.input_to_graph_point, abbreviated
|
||||||
|
# to axes.i2gp, to find a particular point on a graph
|
||||||
|
dot = Dot(color=RED)
|
||||||
|
dot.move_to(axes.i2gp(2, parabola))
|
||||||
|
self.play(FadeIn(dot, scale=0.5))
|
||||||
|
|
||||||
|
# A value tracker lets us animate a parameter, usually
|
||||||
|
# with the intent of having other mobjects update based
|
||||||
|
# on the parameter
|
||||||
|
x_tracker = ValueTracker(2)
|
||||||
|
f_always(
|
||||||
|
dot.move_to,
|
||||||
|
lambda: axes.i2gp(x_tracker.get_value(), parabola)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.play(x_tracker.set_value, 4, run_time=3)
|
||||||
|
self.play(x_tracker.set_value, -2, run_time=3)
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
|
||||||
|
class CoordinateSystemExample(Scene):
|
||||||
|
def construct(self):
|
||||||
|
axes = Axes(
|
||||||
|
# x-axis ranges from -1 to 10, with a default step size of 1
|
||||||
|
x_range=(-1, 10),
|
||||||
|
# y-axis ranges from -2 to 10 with a step size of 0.5
|
||||||
|
y_range=(-2, 2, 0.5),
|
||||||
|
# The axes will be stretched so as to match the specified
|
||||||
|
# height and width
|
||||||
|
height=6,
|
||||||
|
width=10,
|
||||||
|
# Axes is made of two NumberLine mobjects. You can specify
|
||||||
|
# their configuration with axis_config
|
||||||
|
axis_config={
|
||||||
|
"stroke_color": GREY_A,
|
||||||
|
"stroke_width": 2,
|
||||||
|
},
|
||||||
|
# Alternatively, you can specify configuration for just one
|
||||||
|
# of them, like this.
|
||||||
|
y_axis_config={
|
||||||
|
"include_tip": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Keyword arguments of add_coordinate_labels can be used to
|
||||||
|
# configure the DecimalNumber mobjects which it creates and
|
||||||
|
# adds to the axes
|
||||||
|
axes.add_coordinate_labels(
|
||||||
|
font_size=20,
|
||||||
|
num_decimal_places=1,
|
||||||
|
)
|
||||||
|
self.add(axes)
|
||||||
|
|
||||||
|
# Axes descends from the CoordinateSystem class, meaning
|
||||||
|
# you can call call axes.coords_to_point, abbreviated to
|
||||||
|
# axes.c2p, to associate a set of coordinates with a point,
|
||||||
|
# like so:
|
||||||
|
dot = Dot(color=RED)
|
||||||
|
dot.move_to(axes.c2p(0, 0))
|
||||||
|
self.play(FadeIn(dot, scale=0.5))
|
||||||
|
self.play(dot.move_to, axes.c2p(3, 2))
|
||||||
|
self.wait()
|
||||||
|
self.play(dot.move_to, axes.c2p(5, 0.5))
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
# Similarly, you can call axes.point_to_coords, or axes.p2c
|
||||||
|
# print(axes.p2c(dot.get_center()))
|
||||||
|
|
||||||
|
# We can draw lines from the axes to better mark the coordinates
|
||||||
|
# of a given point.
|
||||||
|
# Here, the always_redraw command means that on each new frame
|
||||||
|
# the lines will be redrawn
|
||||||
|
h_line = always_redraw(lambda: axes.get_h_line(dot.get_left()))
|
||||||
|
v_line = always_redraw(lambda: axes.get_v_line(dot.get_bottom()))
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
ShowCreation(h_line),
|
||||||
|
ShowCreation(v_line),
|
||||||
|
)
|
||||||
|
self.play(dot.move_to, axes.c2p(3, -2))
|
||||||
|
self.wait()
|
||||||
|
self.play(dot.move_to, axes.c2p(1, 1))
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
# If we tie the dot to a particular set of coordinates, notice
|
||||||
|
# that as we move the axes around it respects the coordinate
|
||||||
|
# system defined by them.
|
||||||
|
f_always(dot.move_to, lambda: axes.c2p(1, 1))
|
||||||
|
self.play(
|
||||||
|
axes.scale, 0.75,
|
||||||
|
axes.to_corner, UL,
|
||||||
|
run_time=2,
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(FadeOut(VGroup(axes, dot, h_line, v_line)))
|
||||||
|
|
||||||
|
# Other coordinate systems you can play around with include
|
||||||
|
# ThreeDAxes, NumberPlane, and ComplexPlane.
|
||||||
|
|
||||||
|
|
||||||
class SurfaceExample(Scene):
|
class SurfaceExample(Scene):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"camera_class": ThreeDCamera,
|
"camera_class": ThreeDCamera,
|
||||||
@ -453,6 +574,52 @@ class SurfaceExample(Scene):
|
|||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveDevlopment(Scene):
|
||||||
|
def construct(self):
|
||||||
|
circle = Circle()
|
||||||
|
circle.set_fill(BLUE, opacity=0.5)
|
||||||
|
circle.set_stroke(BLUE_E, width=4)
|
||||||
|
square = Square()
|
||||||
|
|
||||||
|
self.play(ShowCreation(square))
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
# This opens an iPython termnial where you can keep writing
|
||||||
|
# lines as if they were part of this construct method.
|
||||||
|
# In particular, 'square', 'circle' and 'self' will all be
|
||||||
|
# part of the local namespace in that terminal.
|
||||||
|
self.embed()
|
||||||
|
|
||||||
|
# Try copying and pasting some of the lines below into
|
||||||
|
# the interactive shell
|
||||||
|
self.play(ReplacementTransform(square, circle))
|
||||||
|
self.wait()
|
||||||
|
self.play(circle.stretch, 4, 0)
|
||||||
|
self.play(Rotate(circle, 90 * DEGREES))
|
||||||
|
self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25)
|
||||||
|
|
||||||
|
text = Text("""
|
||||||
|
In general, using the interactive shell
|
||||||
|
is very helpful when developing new scenes
|
||||||
|
""")
|
||||||
|
self.play(Write(text))
|
||||||
|
|
||||||
|
# In the interactive shell, you can just type
|
||||||
|
# play, add, remove, clear, wait, save_state and restore,
|
||||||
|
# instead of self.play, self.add, self.remove, etc.
|
||||||
|
|
||||||
|
# To interact with the window, type touch(). You can then
|
||||||
|
# scroll in the window, or zoom by holding down 'z' while scrolling,
|
||||||
|
# and change camera perspective by holding down 'd' while moving
|
||||||
|
# the mouse. Press 'r' to reset to the standard camera position.
|
||||||
|
# Press 'q' to stop interacting with the window and go back to
|
||||||
|
# typing new commands into the shell.
|
||||||
|
|
||||||
|
# In principle you can customize a scene to be responsive to
|
||||||
|
# mouse and keyboard interactions
|
||||||
|
always(circle.move_to, self.mouse_point)
|
||||||
|
|
||||||
|
|
||||||
class ControlsExample(Scene):
|
class ControlsExample(Scene):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.textbox = Textbox()
|
self.textbox = Textbox()
|
||||||
|
@ -5,6 +5,7 @@ from manimlib.constants import *
|
|||||||
from manimlib.mobject.functions import ParametricCurve
|
from manimlib.mobject.functions import ParametricCurve
|
||||||
from manimlib.mobject.geometry import Arrow
|
from manimlib.mobject.geometry import Arrow
|
||||||
from manimlib.mobject.geometry import Line
|
from manimlib.mobject.geometry import Line
|
||||||
|
from manimlib.mobject.geometry import DashedLine
|
||||||
from manimlib.mobject.geometry import Rectangle
|
from manimlib.mobject.geometry import Rectangle
|
||||||
from manimlib.mobject.number_line import NumberLine
|
from manimlib.mobject.number_line import NumberLine
|
||||||
from manimlib.mobject.svg.tex_mobject import Tex
|
from manimlib.mobject.svg.tex_mobject import Tex
|
||||||
@ -24,8 +25,8 @@ class CoordinateSystem():
|
|||||||
"""
|
"""
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"dimension": 2,
|
"dimension": 2,
|
||||||
"x_range": [-8, 8, 1],
|
"x_range": np.array([-8, 8, 1]),
|
||||||
"y_range": [-4, 4, 1],
|
"y_range": np.array([-4, 4, 1]),
|
||||||
"width": None,
|
"width": None,
|
||||||
"height": None,
|
"height": None,
|
||||||
"num_sampled_graph_points_per_tick": 5,
|
"num_sampled_graph_points_per_tick": 5,
|
||||||
@ -88,12 +89,26 @@ class CoordinateSystem():
|
|||||||
)
|
)
|
||||||
return self.axis_labels
|
return self.axis_labels
|
||||||
|
|
||||||
|
def get_line_from_axis_to_point(self, index, point,
|
||||||
|
line_func=DashedLine,
|
||||||
|
color=GREY_A,
|
||||||
|
stroke_width=2):
|
||||||
|
axis = self.get_axis(index)
|
||||||
|
line = line_func(axis.get_projection(point), point)
|
||||||
|
line.set_stroke(color, stroke_width)
|
||||||
|
return line
|
||||||
|
|
||||||
|
def get_v_line(self, point, **kwargs):
|
||||||
|
return self.get_line_from_axis_to_point(0, point, **kwargs)
|
||||||
|
|
||||||
|
def get_h_line(self, point, **kwargs):
|
||||||
|
return self.get_line_from_axis_to_point(1, point, **kwargs)
|
||||||
|
|
||||||
# Useful for graphing
|
# Useful for graphing
|
||||||
def get_graph(self, function, x_range=None, **kwargs):
|
def get_graph(self, function, x_range=None, **kwargs):
|
||||||
t_range = list(self.x_range)
|
t_range = np.array(self.x_range, dtype=float)
|
||||||
if x_range is not None:
|
if x_range is not None:
|
||||||
for i in range(len(x_range)):
|
t_range[:len(x_range)] = x_range
|
||||||
t_range[i] = x_range[i]
|
|
||||||
# For axes, the third coordinate of x_range indicates
|
# For axes, the third coordinate of x_range indicates
|
||||||
# tick frequency. But for functions, it indicates a
|
# tick frequency. But for functions, it indicates a
|
||||||
# sample frequency
|
# sample frequency
|
||||||
@ -134,7 +149,7 @@ class CoordinateSystem():
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def itgp(self, x, graph):
|
def i2gp(self, x, graph):
|
||||||
"""
|
"""
|
||||||
Alias for input_to_graph_point
|
Alias for input_to_graph_point
|
||||||
"""
|
"""
|
||||||
@ -147,6 +162,7 @@ class CoordinateSystem():
|
|||||||
direction=RIGHT,
|
direction=RIGHT,
|
||||||
buff=MED_SMALL_BUFF,
|
buff=MED_SMALL_BUFF,
|
||||||
color=None):
|
color=None):
|
||||||
|
if isinstance(label, str):
|
||||||
label = Tex(label)
|
label = Tex(label)
|
||||||
if color is None:
|
if color is None:
|
||||||
label.match_color(graph)
|
label.match_color(graph)
|
||||||
@ -154,8 +170,10 @@ class CoordinateSystem():
|
|||||||
# Searching from the right, find a point
|
# Searching from the right, find a point
|
||||||
# whose y value is in bounds
|
# whose y value is in bounds
|
||||||
max_y = FRAME_Y_RADIUS - label.get_height()
|
max_y = FRAME_Y_RADIUS - label.get_height()
|
||||||
for x0 in np.arange(*self.x_range)[-1::-1]:
|
max_x = FRAME_X_RADIUS - label.get_width()
|
||||||
if abs(self.itgp(x0, graph)[1]) < max_y:
|
for x0 in np.arange(*self.x_range)[::-1]:
|
||||||
|
pt = self.i2gp(x0, graph)
|
||||||
|
if abs(pt[0]) < max_x and abs(pt[1]) < max_y:
|
||||||
x = x0
|
x = x0
|
||||||
break
|
break
|
||||||
if x is None:
|
if x is None:
|
||||||
@ -170,11 +188,11 @@ class CoordinateSystem():
|
|||||||
label.shift_onto_screen()
|
label.shift_onto_screen()
|
||||||
return label
|
return label
|
||||||
|
|
||||||
def get_vertical_line_to_graph(self, x, graph, line_func=Line):
|
def get_v_line_to_graph(self, x, graph, **kwargs):
|
||||||
return line_func(
|
return self.get_v_line(self.i2gp(x, graph), **kwargs)
|
||||||
self.coords_to_point(x, 0),
|
|
||||||
self.input_to_graph_point(x, graph),
|
def get_h_line_to_graph(self, x, graph, **kwargs):
|
||||||
)
|
return self.get_h_line(self.i2gp(x, graph), **kwargs)
|
||||||
|
|
||||||
# For calculus
|
# For calculus
|
||||||
def angle_of_tangent(self, x, graph, dx=EPSILON):
|
def angle_of_tangent(self, x, graph, dx=EPSILON):
|
||||||
@ -221,7 +239,7 @@ class CoordinateSystem():
|
|||||||
else:
|
else:
|
||||||
raise Exception("Invalid input sample type")
|
raise Exception("Invalid input sample type")
|
||||||
height = get_norm(
|
height = get_norm(
|
||||||
self.itgp(sample, graph) - self.c2p(sample, 0)
|
self.i2gp(sample, graph) - self.c2p(sample, 0)
|
||||||
)
|
)
|
||||||
rect = Rectangle(width=x1 - x0, height=height)
|
rect = Rectangle(width=x1 - x0, height=height)
|
||||||
rect.move_to(self.c2p(x0, 0), DL)
|
rect.move_to(self.c2p(x0, 0), DL)
|
||||||
@ -244,21 +262,25 @@ class Axes(VGroup, CoordinateSystem):
|
|||||||
CONFIG = {
|
CONFIG = {
|
||||||
"axis_config": {
|
"axis_config": {
|
||||||
"include_tip": True,
|
"include_tip": True,
|
||||||
|
"numbers_to_exclude": [0],
|
||||||
},
|
},
|
||||||
"x_axis_config": {},
|
"x_axis_config": {},
|
||||||
"y_axis_config": {
|
"y_axis_config": {
|
||||||
"line_to_number_direction": LEFT,
|
"line_to_number_direction": LEFT,
|
||||||
},
|
},
|
||||||
|
"height": FRAME_HEIGHT - 2,
|
||||||
|
"width": FRAME_WIDTH - 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, x_range=None, y_range=None, **kwargs):
|
def __init__(self,
|
||||||
VGroup.__init__(self, **kwargs)
|
x_range=None,
|
||||||
|
y_range=None,
|
||||||
|
**kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
if x_range is not None:
|
if x_range is not None:
|
||||||
for i in range(len(x_range)):
|
self.x_range[:len(x_range)] = x_range
|
||||||
self.x_range[i] = x_range[i]
|
|
||||||
if y_range is not None:
|
if y_range is not None:
|
||||||
for i in range(len(y_range)):
|
self.y_range[:len(x_range)] = y_range
|
||||||
self.y_range[i] = y_range[i]
|
|
||||||
|
|
||||||
self.x_axis = self.create_axis(
|
self.x_axis = self.create_axis(
|
||||||
self.x_range, self.x_axis_config, self.width,
|
self.x_range, self.x_axis_config, self.width,
|
||||||
@ -300,24 +322,21 @@ class Axes(VGroup, CoordinateSystem):
|
|||||||
def add_coordinate_labels(self,
|
def add_coordinate_labels(self,
|
||||||
x_values=None,
|
x_values=None,
|
||||||
y_values=None,
|
y_values=None,
|
||||||
excluding=[0],
|
|
||||||
**kwargs):
|
**kwargs):
|
||||||
axes = self.get_axes()
|
axes = self.get_axes()
|
||||||
self.coordinate_labels = VGroup()
|
self.coordinate_labels = VGroup()
|
||||||
for axis, values in zip(axes, [x_values, y_values]):
|
for axis, values in zip(axes, [x_values, y_values]):
|
||||||
numbers = axis.add_numbers(
|
labels = axis.add_numbers(values, **kwargs)
|
||||||
values, excluding=excluding, **kwargs
|
self.coordinate_labels.add(labels)
|
||||||
)
|
|
||||||
self.coordinate_labels.add(numbers)
|
|
||||||
return self.coordinate_labels
|
return self.coordinate_labels
|
||||||
|
|
||||||
|
|
||||||
class ThreeDAxes(Axes):
|
class ThreeDAxes(Axes):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"dimension": 3,
|
"dimension": 3,
|
||||||
"x_range": (-6, 6, 1),
|
"x_range": np.array([-6, 6, 1]),
|
||||||
"y_range": (-5, 5, 1),
|
"y_range": np.array([-5, 5, 1]),
|
||||||
"z_range": (-4, 4, 1),
|
"z_range": np.array([-4, 4, 1]),
|
||||||
"z_axis_config": {},
|
"z_axis_config": {},
|
||||||
"z_normal": DOWN,
|
"z_normal": DOWN,
|
||||||
"depth": None,
|
"depth": None,
|
||||||
@ -365,6 +384,8 @@ class NumberPlane(Axes):
|
|||||||
"stroke_width": 2,
|
"stroke_width": 2,
|
||||||
"stroke_opacity": 1,
|
"stroke_opacity": 1,
|
||||||
},
|
},
|
||||||
|
"height": None,
|
||||||
|
"width": None,
|
||||||
# Defaults to a faded version of line_config
|
# Defaults to a faded version of line_config
|
||||||
"faded_line_style": None,
|
"faded_line_style": None,
|
||||||
"faded_line_ratio": 1,
|
"faded_line_ratio": 1,
|
||||||
|
@ -9,6 +9,7 @@ class ParametricCurve(VMobject):
|
|||||||
"epsilon": 1e-8,
|
"epsilon": 1e-8,
|
||||||
# TODO, automatically figure out discontinuities
|
# TODO, automatically figure out discontinuities
|
||||||
"discontinuities": [],
|
"discontinuities": [],
|
||||||
|
"use_smoothing": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, t_func, t_range=None, **kwargs):
|
def __init__(self, t_func, t_range=None, **kwargs):
|
||||||
@ -39,6 +40,7 @@ class ParametricCurve(VMobject):
|
|||||||
points = np.array([self.t_func(t) for t in t_range])
|
points = np.array([self.t_func(t) for t in t_range])
|
||||||
self.start_new_path(points[0])
|
self.start_new_path(points[0])
|
||||||
self.add_points_as_corners(points[1:])
|
self.add_points_as_corners(points[1:])
|
||||||
|
if self.use_smoothing:
|
||||||
self.make_approximately_smooth()
|
self.make_approximately_smooth()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -472,6 +472,14 @@ class Line(TipableVMobject):
|
|||||||
def get_angle(self):
|
def get_angle(self):
|
||||||
return angle_of_vector(self.get_vector())
|
return angle_of_vector(self.get_vector())
|
||||||
|
|
||||||
|
def get_projection(self, point):
|
||||||
|
"""
|
||||||
|
Return projection of a point onto the line
|
||||||
|
"""
|
||||||
|
unit_vect = self.get_unit_vector()
|
||||||
|
start = self.get_start()
|
||||||
|
return start + np.dot(point - start, unit_vect) * unit_vect
|
||||||
|
|
||||||
def get_slope(self):
|
def get_slope(self):
|
||||||
return np.tan(self.get_angle())
|
return np.tan(self.get_angle())
|
||||||
|
|
||||||
|
@ -149,17 +149,22 @@ class NumberLine(Line):
|
|||||||
num_mob.shift(num_mob[0].get_width() * LEFT / 2)
|
num_mob.shift(num_mob[0].get_width() * LEFT / 2)
|
||||||
return num_mob
|
return num_mob
|
||||||
|
|
||||||
def add_numbers(self, x_values=None, excluding=None, **kwargs):
|
def add_numbers(self, x_values=None, excluding=None, font_size=24, **kwargs):
|
||||||
if x_values is None:
|
if x_values is None:
|
||||||
x_values = self.get_tick_range()
|
x_values = self.get_tick_range()
|
||||||
if excluding is not None:
|
|
||||||
x_values = list_difference_update(x_values, excluding)
|
|
||||||
|
|
||||||
self.numbers = VGroup()
|
kwargs["font_size"] = font_size
|
||||||
|
|
||||||
|
numbers = VGroup()
|
||||||
for x in x_values:
|
for x in x_values:
|
||||||
self.numbers.add(self.get_number_mobject(x, **kwargs))
|
if x in self.numbers_to_exclude:
|
||||||
self.add(self.numbers)
|
continue
|
||||||
return self.numbers
|
if excluding is not None and x in excluding:
|
||||||
|
continue
|
||||||
|
numbers.add(self.get_number_mobject(x, **kwargs))
|
||||||
|
self.add(numbers)
|
||||||
|
self.numbers = numbers
|
||||||
|
return numbers
|
||||||
|
|
||||||
|
|
||||||
class UnitInterval(NumberLine):
|
class UnitInterval(NumberLine):
|
||||||
|
@ -156,10 +156,6 @@ class Square3D(Surface):
|
|||||||
|
|
||||||
class Cube(SGroup):
|
class Cube(SGroup):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
# "fill_color": BLUE,
|
|
||||||
# "fill_opacity": 1,
|
|
||||||
# "stroke_width": 1,
|
|
||||||
# "stroke_color": BLACK,
|
|
||||||
"color": BLUE,
|
"color": BLUE,
|
||||||
"opacity": 1,
|
"opacity": 1,
|
||||||
"gloss": 0.5,
|
"gloss": 0.5,
|
||||||
@ -174,7 +170,6 @@ class Cube(SGroup):
|
|||||||
face.apply_matrix(z_to_vector(vect))
|
face.apply_matrix(z_to_vector(vect))
|
||||||
self.add(face)
|
self.add(face)
|
||||||
self.set_height(self.side_length)
|
self.set_height(self.side_length)
|
||||||
# self.set_color(self.color, self.opacity, self.gloss)
|
|
||||||
|
|
||||||
|
|
||||||
class Prism(Cube):
|
class Prism(Cube):
|
||||||
|
@ -101,37 +101,51 @@ class Surface(Mobject):
|
|||||||
return normalize_along_axis(normals, 1)
|
return normalize_along_axis(normals, 1)
|
||||||
|
|
||||||
def pointwise_become_partial(self, smobject, a, b, axis=None):
|
def pointwise_become_partial(self, smobject, a, b, axis=None):
|
||||||
|
assert(isinstance(smobject, Surface))
|
||||||
if axis is None:
|
if axis is None:
|
||||||
axis = self.prefered_creation_axis
|
axis = self.prefered_creation_axis
|
||||||
assert(isinstance(smobject, Surface))
|
|
||||||
if a <= 0 and b >= 1:
|
if a <= 0 and b >= 1:
|
||||||
self.match_points(smobject)
|
self.match_points(smobject)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
nu, nv = smobject.resolution
|
nu, nv = smobject.resolution
|
||||||
self.set_points(np.vstack([
|
self.set_points(np.vstack([
|
||||||
self.get_partial_points_array(arr, a, b, (nu, nv, 3), axis=axis)
|
self.get_partial_points_array(arr.copy(), a, b, (nu, nv, 3), axis=axis)
|
||||||
for arr in smobject.get_surface_points_and_nudged_points()
|
for arr in smobject.get_surface_points_and_nudged_points()
|
||||||
]))
|
]))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_partial_points_array(self, points, a, b, resolution, axis):
|
def get_partial_points_array(self, points, a, b, resolution, axis):
|
||||||
|
if len(points) == 0:
|
||||||
|
return points
|
||||||
nu, nv = resolution[:2]
|
nu, nv = resolution[:2]
|
||||||
points = points.reshape(resolution)
|
points = points.reshape(resolution)
|
||||||
max_index = resolution[axis] - 1
|
max_index = resolution[axis] - 1
|
||||||
lower_index, lower_residue = integer_interpolate(0, max_index, a)
|
lower_index, lower_residue = integer_interpolate(0, max_index, a)
|
||||||
upper_index, upper_residue = integer_interpolate(0, max_index, b)
|
upper_index, upper_residue = integer_interpolate(0, max_index, b)
|
||||||
if axis == 0:
|
if axis == 0:
|
||||||
points[:lower_index] = interpolate(points[lower_index], points[lower_index + 1], lower_residue)
|
points[:lower_index] = interpolate(
|
||||||
points[upper_index:] = interpolate(points[upper_index], points[upper_index + 1], upper_residue)
|
points[lower_index],
|
||||||
|
points[lower_index + 1],
|
||||||
|
lower_residue
|
||||||
|
)
|
||||||
|
points[upper_index + 1:] = interpolate(
|
||||||
|
points[upper_index],
|
||||||
|
points[upper_index + 1],
|
||||||
|
upper_residue
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
tuples = [
|
shape = (nu, 1, resolution[2])
|
||||||
(points[:, :lower_index], lower_index, lower_residue),
|
points[:, :lower_index] = interpolate(
|
||||||
(points[:, upper_index:], upper_index, upper_residue),
|
points[:, lower_index],
|
||||||
]
|
points[:, lower_index + 1],
|
||||||
for to_change, index, residue in tuples:
|
lower_residue
|
||||||
col = interpolate(points[:, index], points[:, index + 1], residue)
|
).reshape(shape)
|
||||||
to_change[:] = col.reshape((nu, 1, *resolution[2:]))
|
points[:, upper_index + 1:] = interpolate(
|
||||||
|
points[:, upper_index],
|
||||||
|
points[:, upper_index + 1],
|
||||||
|
upper_residue
|
||||||
|
).reshape(shape)
|
||||||
return points.reshape((nu * nv, *resolution[2:]))
|
return points.reshape((nu * nv, *resolution[2:]))
|
||||||
|
|
||||||
def sort_faces_back_to_front(self, vect=OUT):
|
def sort_faces_back_to_front(self, vect=OUT):
|
||||||
|
@ -56,7 +56,7 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){
|
|||||||
// float get_reduced_control_points(vec3 b0, vec3 b1, vec3 b2, out vec3 new_points[3]){
|
// float get_reduced_control_points(vec3 b0, vec3 b1, vec3 b2, out vec3 new_points[3]){
|
||||||
float get_reduced_control_points(in vec3 points[3], out vec3 new_points[3]){
|
float get_reduced_control_points(in vec3 points[3], out vec3 new_points[3]){
|
||||||
float length_threshold = 1e-6;
|
float length_threshold = 1e-6;
|
||||||
float angle_threshold = 1e-3;
|
float angle_threshold = 5e-2;
|
||||||
|
|
||||||
vec3 p0 = points[0];
|
vec3 p0 = points[0];
|
||||||
vec3 p1 = points[1];
|
vec3 p1 = points[1];
|
||||||
|
Reference in New Issue
Block a user