mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
commit
ae8ccffe71
13 changed files with 3502 additions and 116 deletions
File diff suppressed because it is too large
Load diff
|
@ -115,6 +115,8 @@ class AnimationOnSurroundingRectangle(AnimationGroup):
|
|||
rect = SurroundingRectangle(
|
||||
mobject, **self.surrounding_rectangle_config
|
||||
)
|
||||
if "surrounding_rectangle_config" in kwargs:
|
||||
kwargs.pop("surrounding_rectangle_config")
|
||||
AnimationGroup.__init__(self, self.rect_to_animation(rect, **kwargs))
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ from mobject.types.image_mobject import AbstractImageMobject
|
|||
from mobject.mobject import Mobject
|
||||
from mobject.types.point_cloud_mobject import PMobject
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.value_tracker import ValueTracker
|
||||
from utils.color import color_to_int_rgba
|
||||
from utils.color import rgb_to_hex
|
||||
from utils.config_ops import digest_config
|
||||
|
@ -202,8 +201,7 @@ class Camera(object):
|
|||
|
||||
def extract_mobject_family_members(
|
||||
self, mobjects,
|
||||
only_those_with_points=False,
|
||||
ignore_value_trackers=False):
|
||||
only_those_with_points=False):
|
||||
if only_those_with_points:
|
||||
method = Mobject.family_members_with_points
|
||||
else:
|
||||
|
@ -213,20 +211,16 @@ class Camera(object):
|
|||
method(m)
|
||||
for m in mobjects
|
||||
if not (isinstance(m, VMobject) and m.is_subpath)
|
||||
if not (ignore_value_trackers and isinstance(m, ValueTracker))
|
||||
])
|
||||
))
|
||||
|
||||
def get_mobjects_to_display(
|
||||
self, mobjects,
|
||||
include_submobjects=True,
|
||||
ignore_value_trackers=True,
|
||||
excluded_mobjects=None):
|
||||
if include_submobjects:
|
||||
mobjects = self.extract_mobject_family_members(
|
||||
mobjects,
|
||||
only_those_with_points=True,
|
||||
ignore_value_trackers=ignore_value_trackers,
|
||||
mobjects, only_those_with_points=True,
|
||||
)
|
||||
if excluded_mobjects:
|
||||
all_excluded = self.extract_mobject_family_members(
|
||||
|
@ -347,6 +341,8 @@ class Camera(object):
|
|||
points = self.transform_points_pre_display(
|
||||
vmob, vmob.points
|
||||
)
|
||||
if np.any(np.isnan(points)) or np.any(points == np.inf):
|
||||
points = np.zeros((1, 3))
|
||||
ctx.new_sub_path()
|
||||
ctx.move_to(*points[0][:2])
|
||||
for triplet in zip(points[1::3], points[2::3], points[3::3]):
|
||||
|
|
|
@ -6,6 +6,7 @@ from constants import *
|
|||
|
||||
from camera.camera import Camera
|
||||
from mobject.types.point_cloud_mobject import Point
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.three_d_utils import get_3d_vmob_start_corner
|
||||
from mobject.three_d_utils import get_3d_vmob_start_corner_unit_normal
|
||||
from mobject.three_d_utils import get_3d_vmob_end_corner
|
||||
|
@ -94,22 +95,22 @@ class ThreeDCamera(Camera):
|
|||
vmobject, vmobject.get_fill_rgbas()
|
||||
)
|
||||
|
||||
def display_multiple_vectorized_mobjects(self, vmobjects, pixel_array):
|
||||
def get_mobjects_to_display(self, *args, **kwargs):
|
||||
mobjects = Camera.get_mobjects_to_display(
|
||||
self, *args, **kwargs
|
||||
)
|
||||
rot_matrix = self.get_rotation_matrix()
|
||||
|
||||
def z_key(vmob):
|
||||
def z_key(mob):
|
||||
if not (hasattr(mob, "shade_in_3d") and mob.shade_in_3d):
|
||||
return np.inf
|
||||
# Assign a number to a three dimensional mobjects
|
||||
# based on how close it is to the camera
|
||||
if vmob.shade_in_3d:
|
||||
return np.dot(
|
||||
vmob.get_center(),
|
||||
rot_matrix.T
|
||||
)[2]
|
||||
else:
|
||||
return np.inf
|
||||
Camera.display_multiple_vectorized_mobjects(
|
||||
self, sorted(vmobjects, key=z_key), pixel_array
|
||||
)
|
||||
return np.dot(
|
||||
mob.get_center(),
|
||||
rot_matrix.T
|
||||
)[2]
|
||||
return sorted(mobjects, key=z_key)
|
||||
|
||||
def get_phi(self):
|
||||
return self.phi_tracker.get_value()
|
||||
|
@ -180,7 +181,8 @@ class ThreeDCamera(Camera):
|
|||
factor[lt0] = (distance / (distance - zs[lt0]))
|
||||
else:
|
||||
factor = (distance / (distance - zs))
|
||||
clip_in_place(factor, 0, 10**6)
|
||||
factor[(distance - zs) < 0] = 10**6
|
||||
# clip_in_place(factor, 0, 10**6)
|
||||
points[:, i] *= factor
|
||||
points += frame_center
|
||||
return points
|
||||
|
|
|
@ -193,14 +193,14 @@ class PatreonEndScreen(PatreonThanks):
|
|||
black_rect = Rectangle(
|
||||
fill_color=BLACK,
|
||||
fill_opacity=1,
|
||||
stroke_width=0,
|
||||
stroke_width=3,
|
||||
stroke_color=BLACK,
|
||||
width=FRAME_WIDTH,
|
||||
height=0.6 * FRAME_HEIGHT,
|
||||
)
|
||||
black_rect.to_edge(UP, buff=0)
|
||||
line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT)
|
||||
line.move_to(ORIGIN)
|
||||
self.add(line)
|
||||
|
||||
thanks = TextMobject("Funded by the community, with special thanks to:")
|
||||
thanks.scale(0.9)
|
||||
|
@ -210,7 +210,6 @@ class PatreonEndScreen(PatreonThanks):
|
|||
underline.set_width(thanks.get_width() + MED_SMALL_BUFF)
|
||||
underline.next_to(thanks, DOWN, SMALL_BUFF)
|
||||
thanks.add(underline)
|
||||
self.add(thanks)
|
||||
|
||||
patrons = VGroup(*list(map(TextMobject, self.specific_patrons)))
|
||||
patrons.scale(self.patron_scale_val)
|
||||
|
@ -234,12 +233,10 @@ class PatreonEndScreen(PatreonThanks):
|
|||
|
||||
thanks.align_to(columns, alignment_vect=RIGHT)
|
||||
|
||||
self.add(columns, black_rect, line, thanks)
|
||||
self.play(
|
||||
columns.move_to, 2 * DOWN, DOWN,
|
||||
columns.to_edge, RIGHT,
|
||||
Animation(black_rect),
|
||||
Animation(line),
|
||||
Animation(thanks),
|
||||
rate_func=None,
|
||||
run_time=self.run_time,
|
||||
)
|
||||
|
|
|
@ -554,10 +554,10 @@ class Arrow(Line):
|
|||
self.second_tip.get_anchors()[1:]
|
||||
)
|
||||
self.rect.set_points_as_corners([
|
||||
tip_base + perp_vect * width / 2,
|
||||
start + perp_vect * width / 2,
|
||||
start - perp_vect * width / 2,
|
||||
tip_base - perp_vect * width / 2,
|
||||
start - perp_vect * width / 2,
|
||||
start + perp_vect * width / 2,
|
||||
tip_base + perp_vect * width / 2,
|
||||
])
|
||||
self.stem = self.rect # Alternate name
|
||||
return self
|
||||
|
|
|
@ -170,8 +170,11 @@ class Mobject(Container):
|
|||
def get_updaters(self):
|
||||
return self.updaters
|
||||
|
||||
def add_updater(self, update_function, call_updater=True):
|
||||
self.updaters.append(update_function)
|
||||
def add_updater(self, update_function, index=None, call_updater=True):
|
||||
if index is None:
|
||||
self.updaters.append(update_function)
|
||||
else:
|
||||
self.updaters.insert(index, update_function)
|
||||
if call_updater:
|
||||
self.update(0)
|
||||
return self
|
||||
|
@ -254,7 +257,7 @@ class Mobject(Container):
|
|||
|
||||
def apply_matrix(self, matrix, **kwargs):
|
||||
# Default to applying matrix about the origin, not mobjects center
|
||||
if len(kwargs) == 0:
|
||||
if ("about_point" not in kwargs) and ("about_edge" not in kwargs):
|
||||
kwargs["about_point"] = ORIGIN
|
||||
full_matrix = np.identity(self.dim)
|
||||
matrix = np.array(matrix)
|
||||
|
@ -982,7 +985,8 @@ class Mobject(Container):
|
|||
"""
|
||||
self.align_data(mobject)
|
||||
for sm1, sm2 in zip(self.get_family(), mobject.get_family()):
|
||||
sm1.interpolate(sm1, sm2, 1)
|
||||
sm1.points = np.array(sm2.points)
|
||||
sm1.interpolate_color(sm1, sm2, 1)
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
@ -29,7 +29,10 @@ class DecimalNumber(VMobject):
|
|||
|
||||
shows_zero = np.round(number, self.num_decimal_places) == 0
|
||||
if num_string.startswith("-") and shows_zero:
|
||||
num_string = num_string[1:]
|
||||
if self.include_sign:
|
||||
num_string = "+" + num_string[1:]
|
||||
else:
|
||||
num_string = num_string[1:]
|
||||
|
||||
self.add(*[
|
||||
SingleStringTexMobject(char, **kwargs)
|
||||
|
@ -114,6 +117,7 @@ class DecimalNumber(VMobject):
|
|||
# of animated mobjects
|
||||
mob.points[:] = 0
|
||||
self.number = number
|
||||
return self
|
||||
|
||||
def get_value(self):
|
||||
return self.number
|
||||
|
|
|
@ -128,6 +128,13 @@ class SVGMobject(VMobject):
|
|||
self.ref_to_element[ref]
|
||||
)
|
||||
|
||||
def attribute_to_float(self, attr):
|
||||
stripped_attr = "".join([
|
||||
char for char in attr
|
||||
if char in string.digits + "." + "-"
|
||||
])
|
||||
return float(stripped_attr)
|
||||
|
||||
def polygon_to_mobject(self, polygon_element):
|
||||
# TODO, This seems hacky...
|
||||
path_string = polygon_element.getAttribute("points")
|
||||
|
@ -140,7 +147,9 @@ class SVGMobject(VMobject):
|
|||
|
||||
def circle_to_mobject(self, circle_element):
|
||||
x, y, r = [
|
||||
float(circle_element.getAttribute(key))
|
||||
self.attribute_to_float(
|
||||
circle_element.getAttribute(key)
|
||||
)
|
||||
if circle_element.hasAttribute(key)
|
||||
else 0.0
|
||||
for key in ("cx", "cy", "r")
|
||||
|
@ -149,7 +158,9 @@ class SVGMobject(VMobject):
|
|||
|
||||
def ellipse_to_mobject(self, circle_element):
|
||||
x, y, rx, ry = [
|
||||
float(circle_element.getAttribute(key))
|
||||
self.attribute_to_float(
|
||||
circle_element.getAttribute(key)
|
||||
)
|
||||
if circle_element.hasAttribute(key)
|
||||
else 0.0
|
||||
for key in ("cx", "cy", "rx", "ry")
|
||||
|
@ -183,8 +194,12 @@ class SVGMobject(VMobject):
|
|||
|
||||
if corner_radius == 0:
|
||||
mob = Rectangle(
|
||||
width=float(rect_element.getAttribute("width")),
|
||||
height=float(rect_element.getAttribute("height")),
|
||||
width=self.attribute_to_float(
|
||||
rect_element.getAttribute("width")
|
||||
),
|
||||
height=self.attribute_to_float(
|
||||
rect_element.getAttribute("height")
|
||||
),
|
||||
stroke_width=stroke_width,
|
||||
stroke_color=stroke_color,
|
||||
fill_color=fill_color,
|
||||
|
@ -192,8 +207,12 @@ class SVGMobject(VMobject):
|
|||
)
|
||||
else:
|
||||
mob = RoundedRectangle(
|
||||
width=float(rect_element.getAttribute("width")),
|
||||
height=float(rect_element.getAttribute("height")),
|
||||
width=self.attribute_to_float(
|
||||
rect_element.getAttribute("width")
|
||||
),
|
||||
height=self.attribute_to_float(
|
||||
rect_element.getAttribute("height")
|
||||
),
|
||||
stroke_width=stroke_width,
|
||||
stroke_color=stroke_color,
|
||||
fill_color=fill_color,
|
||||
|
@ -207,9 +226,9 @@ class SVGMobject(VMobject):
|
|||
def handle_transforms(self, element, mobject):
|
||||
x, y = 0, 0
|
||||
try:
|
||||
x = float(element.getAttribute('x'))
|
||||
x = self.attribute_to_float(element.getAttribute('x'))
|
||||
# Flip y
|
||||
y = -float(element.getAttribute('y'))
|
||||
y = -self.attribute_to_float(element.getAttribute('y'))
|
||||
mobject.shift(x * RIGHT + y * UP)
|
||||
except:
|
||||
pass
|
||||
|
|
|
@ -38,11 +38,11 @@ def get_3d_vmob_unit_normal(vmob, point_index):
|
|||
if len(vmob.get_anchors()) <= 2:
|
||||
return np.array(UP)
|
||||
i = point_index
|
||||
im1 = i - 1 if i > 0 else (n_points - 2)
|
||||
ip1 = i + 1 if i < (n_points - 1) else 1
|
||||
im3 = i - 3 if i > 2 else (n_points - 4)
|
||||
ip3 = i + 3 if i < (n_points - 3) else 3
|
||||
unit_normal = get_unit_normal(
|
||||
vmob.points[ip1] - vmob.points[i],
|
||||
vmob.points[im1] - vmob.points[i],
|
||||
vmob.points[ip3] - vmob.points[i],
|
||||
vmob.points[im3] - vmob.points[i],
|
||||
)
|
||||
if get_norm(unit_normal) == 0:
|
||||
return np.array(UP)
|
||||
|
|
|
@ -32,6 +32,7 @@ class ParametricSurface(VGroup):
|
|||
"stroke_color": LIGHT_GREY,
|
||||
"stroke_width": 0.5,
|
||||
"should_make_jagged": False,
|
||||
"pre_function_handle_to_anchor_scale_factor": 0.00001,
|
||||
}
|
||||
|
||||
def __init__(self, func, **kwargs):
|
||||
|
@ -70,6 +71,10 @@ class ParametricSurface(VGroup):
|
|||
faces.add(face)
|
||||
face.u_index = i
|
||||
face.v_index = j
|
||||
face.u1 = u1
|
||||
face.u2 = u2
|
||||
face.v1 = v1
|
||||
face.v2 = v2
|
||||
faces.set_fill(
|
||||
color=self.fill_color,
|
||||
opacity=self.fill_opacity
|
||||
|
|
|
@ -308,6 +308,10 @@ class VMobject(Mobject):
|
|||
self.color_using_background_image(vmobject.get_background_image_file())
|
||||
return self
|
||||
|
||||
def set_shade_in_3d(self, value=True):
|
||||
for submob in self.get_family():
|
||||
submob.shade_in_3d = value
|
||||
|
||||
# Drawing
|
||||
def start_at(self, point):
|
||||
if len(self.points) == 0:
|
||||
|
|
|
@ -2,7 +2,10 @@ import numpy as np
|
|||
|
||||
from constants import OUT
|
||||
from constants import RIGHT
|
||||
from constants import PI
|
||||
from constants import TAU
|
||||
from functools import reduce
|
||||
from utils.iterables import adjacent_pairs
|
||||
|
||||
# Matrix operations
|
||||
|
||||
|
@ -97,12 +100,15 @@ def project_along_vector(point, vector):
|
|||
return np.dot(point, matrix.T)
|
||||
|
||||
|
||||
def normalize(vect):
|
||||
def normalize(vect, fall_back=None):
|
||||
norm = get_norm(vect)
|
||||
if norm > 0:
|
||||
return vect / norm
|
||||
return np.array(vect) / norm
|
||||
else:
|
||||
return np.zeros(len(vect))
|
||||
if fall_back is not None:
|
||||
return fall_back
|
||||
else:
|
||||
return np.zeros(len(vect))
|
||||
|
||||
|
||||
def cross(v1, v2):
|
||||
|
@ -164,3 +170,12 @@ def line_intersection(line1, line2):
|
|||
x = det(d, x_diff) / div
|
||||
y = det(d, y_diff) / div
|
||||
return np.array([x, y, 0])
|
||||
|
||||
|
||||
def get_winding_number(points):
|
||||
total_angle = 0
|
||||
for p1, p2 in adjacent_pairs(points):
|
||||
d_angle = angle_of_vector(p2) - angle_of_vector(p1)
|
||||
d_angle = ((d_angle + PI) % TAU) - PI
|
||||
total_angle += d_angle
|
||||
return total_angle / TAU
|
||||
|
|
Loading…
Add table
Reference in a new issue