mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Better alignment of sub_mbojects
This commit is contained in:
parent
a9f620e250
commit
bd3783586a
8 changed files with 92 additions and 64 deletions
|
@ -15,9 +15,9 @@ class Transform(Animation):
|
||||||
"path_func" : straight_path
|
"path_func" : straight_path
|
||||||
}
|
}
|
||||||
def __init__(self, mobject, ending_mobject, **kwargs):
|
def __init__(self, mobject, ending_mobject, **kwargs):
|
||||||
mobject = instantiate(mobject)
|
mobject =mobject
|
||||||
#Copy ending_mobject so as to not mess with caller
|
#Copy ending_mobject so as to not mess with caller
|
||||||
ending_mobject = instantiate(ending_mobject).copy()
|
ending_mobject = ending_mobject.copy()
|
||||||
digest_config(self, kwargs, locals())
|
digest_config(self, kwargs, locals())
|
||||||
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
|
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
|
||||||
if count2 == 0:
|
if count2 == 0:
|
||||||
|
|
28
camera.py
28
camera.py
|
@ -9,7 +9,7 @@ import progressbar
|
||||||
import aggdraw
|
import aggdraw
|
||||||
|
|
||||||
from helpers import *
|
from helpers import *
|
||||||
from mobject import PointCloudMobject, VectorizedMobject
|
from mobject import PMobject, VMobject
|
||||||
|
|
||||||
class Camera(object):
|
class Camera(object):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
@ -72,9 +72,9 @@ class Camera(object):
|
||||||
for mob in mobjects
|
for mob in mobjects
|
||||||
])
|
])
|
||||||
for mobject in mobjects:
|
for mobject in mobjects:
|
||||||
if isinstance(mobject, VectorizedMobject):
|
if isinstance(mobject, VMobject):
|
||||||
self.display_vectorized(mobject)
|
self.display_vectorized(mobject)
|
||||||
elif isinstance(mobject, PointCloudMobject):
|
elif isinstance(mobject, PMobject):
|
||||||
self.display_point_cloud(
|
self.display_point_cloud(
|
||||||
mobject.points, mobject.rgbs,
|
mobject.points, mobject.rgbs,
|
||||||
self.adjusted_thickness(mobject.stroke_width)
|
self.adjusted_thickness(mobject.stroke_width)
|
||||||
|
@ -95,34 +95,34 @@ class Camera(object):
|
||||||
self.pixel_array[covered] = rgb
|
self.pixel_array[covered] = rgb
|
||||||
|
|
||||||
|
|
||||||
def display_vectorized(self, vect_mobject):
|
def display_vectorized(self, vmobject):
|
||||||
if vect_mobject.is_subpath:
|
if vmobject.is_subpath:
|
||||||
#Subpath vectorized mobjects are taken care
|
#Subpath vectorized mobjects are taken care
|
||||||
#of by their parent
|
#of by their parent
|
||||||
return
|
return
|
||||||
im = Image.fromarray(self.pixel_array, mode = "RGB")
|
im = Image.fromarray(self.pixel_array, mode = "RGB")
|
||||||
canvas = aggdraw.Draw(im)
|
canvas = aggdraw.Draw(im)
|
||||||
pen, fill = self.get_pen_and_fill(vect_mobject)
|
pen, fill = self.get_pen_and_fill(vmobject)
|
||||||
pathstring = self.get_pathstring(vect_mobject)
|
pathstring = self.get_pathstring(vmobject)
|
||||||
symbol = aggdraw.Symbol(pathstring)
|
symbol = aggdraw.Symbol(pathstring)
|
||||||
canvas.symbol((0, 0), symbol, pen, fill)
|
canvas.symbol((0, 0), symbol, pen, fill)
|
||||||
canvas.flush()
|
canvas.flush()
|
||||||
self.pixel_array[:,:] = np.array(im)
|
self.pixel_array[:,:] = np.array(im)
|
||||||
|
|
||||||
def get_pen_and_fill(self, vect_mobject):
|
def get_pen_and_fill(self, vmobject):
|
||||||
pen = aggdraw.Pen(
|
pen = aggdraw.Pen(
|
||||||
vect_mobject.get_stroke_color().get_hex_l(),
|
vmobject.get_stroke_color().get_hex_l(),
|
||||||
vect_mobject.stroke_width
|
vmobject.stroke_width
|
||||||
)
|
)
|
||||||
fill = aggdraw.Brush(
|
fill = aggdraw.Brush(
|
||||||
vect_mobject.get_fill_color().get_hex_l(),
|
vmobject.get_fill_color().get_hex_l(),
|
||||||
opacity = int(255*vect_mobject.get_fill_opacity())
|
opacity = int(255*vmobject.get_fill_opacity())
|
||||||
)
|
)
|
||||||
return (pen, fill)
|
return (pen, fill)
|
||||||
|
|
||||||
def get_pathstring(self, vect_mobject):
|
def get_pathstring(self, vmobject):
|
||||||
result = ""
|
result = ""
|
||||||
for mob in [vect_mobject]+vect_mobject.subpath_mobjects:
|
for mob in [vmobject]+vmobject.subpath_mobjects:
|
||||||
points = mob.points
|
points = mob.points
|
||||||
if len(points) == 0:
|
if len(points) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -5,5 +5,5 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
from mobject import Mobject
|
from mobject import Mobject
|
||||||
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PointCloudMobject
|
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject
|
||||||
from vectorized_mobject import VectorizedMobject
|
from vectorized_mobject import VMobject
|
|
@ -6,9 +6,9 @@ from random import random
|
||||||
|
|
||||||
from helpers import *
|
from helpers import *
|
||||||
from mobject import Mobject
|
from mobject import Mobject
|
||||||
from point_cloud_mobject import PointCloudMobject
|
from point_cloud_mobject import PMobject
|
||||||
|
|
||||||
class ImageMobject(PointCloudMobject):
|
class ImageMobject(PMobject):
|
||||||
"""
|
"""
|
||||||
Automatically filters out black pixels
|
Automatically filters out black pixels
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -17,11 +17,11 @@ class Mobject(object):
|
||||||
"""
|
"""
|
||||||
#Number of numbers used to describe a point (3 for pos, 3 for normal vector)
|
#Number of numbers used to describe a point (3 for pos, 3 for normal vector)
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color" : WHITE,
|
"color" : WHITE,
|
||||||
"stroke_width" : DEFAULT_POINT_THICKNESS,
|
"stroke_width" : DEFAULT_POINT_THICKNESS,
|
||||||
"name" : None,
|
"name" : None,
|
||||||
"display_mode" : "points", #TODO, REMOVE
|
"dim" : 3,
|
||||||
"dim" : 3,
|
"target" : None
|
||||||
}
|
}
|
||||||
def __init__(self, *sub_mobjects, **kwargs):
|
def __init__(self, *sub_mobjects, **kwargs):
|
||||||
digest_config(self, kwargs)
|
digest_config(self, kwargs)
|
||||||
|
@ -320,11 +320,8 @@ class Mobject(object):
|
||||||
|
|
||||||
### Getters ###
|
### Getters ###
|
||||||
|
|
||||||
def get_num_points(self, including_submobjects = False):
|
def get_num_points(self):
|
||||||
if including_submobjects:
|
return len(self.points)
|
||||||
return self.reduce_across_dimension(len, sum, 0)
|
|
||||||
else:
|
|
||||||
return len(self.points)
|
|
||||||
|
|
||||||
def get_critical_point(self, direction):
|
def get_critical_point(self, direction):
|
||||||
result = np.zeros(self.dim)
|
result = np.zeros(self.dim)
|
||||||
|
@ -406,23 +403,13 @@ class Mobject(object):
|
||||||
|
|
||||||
## Alignment
|
## Alignment
|
||||||
def align_data(self, mobject):
|
def align_data(self, mobject):
|
||||||
|
self.align_sub_mobjects(mobject)
|
||||||
self.align_points(mobject)
|
self.align_points(mobject)
|
||||||
#Recurse
|
#Recurse
|
||||||
diff = len(self.sub_mobjects) - len(mobject.sub_mobjects)
|
|
||||||
if diff != 0:
|
|
||||||
if diff < 0:
|
|
||||||
larger, smaller = mobject, self
|
|
||||||
elif diff > 0:
|
|
||||||
larger, smaller = self, mobject
|
|
||||||
for sub_mob in larger.sub_mobjects[-abs(diff):]:
|
|
||||||
point_mob = sub_mob.get_point_mobject(
|
|
||||||
smaller.get_center()
|
|
||||||
)
|
|
||||||
smaller.add(point_mob)
|
|
||||||
for m1, m2 in zip(self.sub_mobjects, mobject.sub_mobjects):
|
for m1, m2 in zip(self.sub_mobjects, mobject.sub_mobjects):
|
||||||
m1.align_data(m2)
|
m1.align_data(m2)
|
||||||
|
|
||||||
def get_point_mobject(self, center):
|
def get_point_mobject(self, center = None):
|
||||||
"""
|
"""
|
||||||
The simplest mobject to be transformed to or from self.
|
The simplest mobject to be transformed to or from self.
|
||||||
Should by a point of the appropriate type
|
Should by a point of the appropriate type
|
||||||
|
@ -442,6 +429,40 @@ class Mobject(object):
|
||||||
def align_points_with_larger(self, larger_mobject):
|
def align_points_with_larger(self, larger_mobject):
|
||||||
raise Exception("Not implemented")
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
|
def align_sub_mobjects(self, mobject):
|
||||||
|
#If one is empty, and the other is not,
|
||||||
|
#push it into its submobject list
|
||||||
|
self_has_points, mob_has_points = [
|
||||||
|
mob.get_num_points() > 0
|
||||||
|
for mob in self, mobject
|
||||||
|
]
|
||||||
|
if self_has_points and not mob_has_points:
|
||||||
|
self.push_self_into_sub_mobjects()
|
||||||
|
elif mob_has_points and not self_has_points:
|
||||||
|
mob.push_self_into_sub_mobjects()
|
||||||
|
self_count = len(self.sub_mobjects)
|
||||||
|
mob_count = len(mobject.sub_mobjects)
|
||||||
|
diff = abs(self_count-mob_count)
|
||||||
|
if self_count < mob_count:
|
||||||
|
self.add_n_more_sub_mobjects(diff)
|
||||||
|
elif mob_count < self_count:
|
||||||
|
mobject.add_n_more_sub_mobjects(diff)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def push_self_into_sub_mobjects(self):
|
||||||
|
copy = self.copy()
|
||||||
|
copy.sub_mobjects = []
|
||||||
|
self.points = np.zeros((0, self.dim))
|
||||||
|
self.add(copy)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def add_n_more_sub_mobjects(self, n):
|
||||||
|
if n > 0 and len(self.sub_mobjects) == 0:
|
||||||
|
self.add(self.copy())
|
||||||
|
for i in range(n):
|
||||||
|
self.add(self.sub_mobjects[i].copy())
|
||||||
|
return self
|
||||||
|
|
||||||
def interpolate(self, mobject1, mobject2, alpha, path_func):
|
def interpolate(self, mobject1, mobject2, alpha, path_func):
|
||||||
"""
|
"""
|
||||||
Turns target_mobject into an interpolation between mobject1
|
Turns target_mobject into an interpolation between mobject1
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from .mobject import Mobject
|
from .mobject import Mobject
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
class PointCloudMobject(Mobject):
|
class PMobject(Mobject):
|
||||||
def init_colors(self):
|
def init_colors(self):
|
||||||
self.rgbs = np.zeros((0, 3))
|
self.rgbs = np.zeros((0, 3))
|
||||||
return self
|
return self
|
||||||
|
@ -112,14 +112,14 @@ class PointCloudMobject(Mobject):
|
||||||
|
|
||||||
# Alignment
|
# Alignment
|
||||||
def align_points_with_larger(self, larger_mobject):
|
def align_points_with_larger(self, larger_mobject):
|
||||||
assert(isinstance(larger_mobject, PointCloudMobject))
|
assert(isinstance(larger_mobject, PMobject))
|
||||||
self.apply_over_attr_arrays(
|
self.apply_over_attr_arrays(
|
||||||
lambda a : streth_array_to_length(
|
lambda a : streth_array_to_length(
|
||||||
a, larger_mobject.get_num_points()
|
a, larger_mobject.get_num_points()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_point_mobject(self, center):
|
def get_point_mobject(self, center = None):
|
||||||
if center is None:
|
if center is None:
|
||||||
center = self.get_center()
|
center = self.get_center()
|
||||||
return Point(center)
|
return Point(center)
|
||||||
|
@ -141,7 +141,7 @@ class PointCloudMobject(Mobject):
|
||||||
|
|
||||||
|
|
||||||
#TODO, Make the two implementations bellow non-redundant
|
#TODO, Make the two implementations bellow non-redundant
|
||||||
class Mobject1D(PointCloudMobject):
|
class Mobject1D(PMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"density" : DEFAULT_POINT_DENSITY_1D,
|
"density" : DEFAULT_POINT_DENSITY_1D,
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ class Mobject1D(PointCloudMobject):
|
||||||
]
|
]
|
||||||
self.add_points(points, color = color)
|
self.add_points(points, color = color)
|
||||||
|
|
||||||
class Mobject2D(PointCloudMobject):
|
class Mobject2D(PMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"density" : DEFAULT_POINT_DENSITY_2D,
|
"density" : DEFAULT_POINT_DENSITY_2D,
|
||||||
}
|
}
|
||||||
|
@ -175,11 +175,11 @@ class Mobject2D(PointCloudMobject):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Point(PointCloudMobject):
|
class Point(PMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color" : BLACK,
|
"color" : BLACK,
|
||||||
}
|
}
|
||||||
def __init__(self, location = ORIGIN, **kwargs):
|
def __init__(self, location = ORIGIN, **kwargs):
|
||||||
PointCloudMobject.__init__(self, **kwargs)
|
PMobject.__init__(self, **kwargs)
|
||||||
self.add_points([location])
|
self.add_points([location])
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from mobject import Mobject
|
from mobject import Mobject
|
||||||
from point_cloud_mobject import PointCloudMobject
|
from point_cloud_mobject import PMobject
|
||||||
from image_mobject import ImageMobject
|
from image_mobject import ImageMobject
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
#TODO, Cleanup and refactor this file.
|
#TODO, Cleanup and refactor this file.
|
||||||
|
|
||||||
class TexMobject(PointCloudMobject):
|
class TexMobject(PMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"template_tex_file" : TEMPLATE_TEX_FILE,
|
"template_tex_file" : TEMPLATE_TEX_FILE,
|
||||||
"color" : WHITE,
|
"color" : WHITE,
|
||||||
|
|
|
@ -4,13 +4,14 @@ from .mobject import Mobject
|
||||||
|
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
class VectorizedMobject(Mobject):
|
class VMobject(Mobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"fill_color" : BLACK,
|
"fill_color" : BLACK,
|
||||||
"fill_opacity" : 0.0,
|
"fill_opacity" : 0.0,
|
||||||
#Indicates that it will not be displayed, but
|
#Indicates that it will not be displayed, but
|
||||||
#that it should count in parent mobject's path
|
#that it should count in parent mobject's path
|
||||||
"is_subpath" : False,
|
"is_subpath" : False,
|
||||||
|
"closed" : True,
|
||||||
}
|
}
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.subpath_mobjects = []
|
self.subpath_mobjects = []
|
||||||
|
@ -18,8 +19,8 @@ class VectorizedMobject(Mobject):
|
||||||
|
|
||||||
## Colors
|
## Colors
|
||||||
def init_colors(self):
|
def init_colors(self):
|
||||||
self.set_stroke(color = self.color)
|
self.set_stroke(self.color, self.stroke_width)
|
||||||
self.set_fill(color = self.fill_color)
|
self.set_fill(self.fill_color, self.fill_opacity)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_family_attr(self, attr, value):
|
def set_family_attr(self, attr, value):
|
||||||
|
@ -102,7 +103,7 @@ class VectorizedMobject(Mobject):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_points(self, points):
|
def set_points(self, points):
|
||||||
self.points = points
|
self.points = np.array(points)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_anchor_points(self, points, mode = "smooth"):
|
def set_anchor_points(self, points, mode = "smooth"):
|
||||||
|
@ -120,7 +121,7 @@ class VectorizedMobject(Mobject):
|
||||||
|
|
||||||
def change_mode(self, mode):
|
def change_mode(self, mode):
|
||||||
anchors, h1, h2 = self.get_anchors_and_handles()
|
anchors, h1, h2 = self.get_anchors_and_handles()
|
||||||
self.set_points(anchors, mode = mode)
|
self.set_anchor_points(anchors, mode = mode)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def make_smooth(self):
|
def make_smooth(self):
|
||||||
|
@ -131,7 +132,7 @@ class VectorizedMobject(Mobject):
|
||||||
|
|
||||||
def add_subpath(self, points):
|
def add_subpath(self, points):
|
||||||
"""
|
"""
|
||||||
A VectorizedMobject is meant to represnt
|
A VMobject is meant to represnt
|
||||||
a single "path", in the svg sense of the word.
|
a single "path", in the svg sense of the word.
|
||||||
However, one such path may really consit of separate
|
However, one such path may really consit of separate
|
||||||
continuous components if there is a move_to command.
|
continuous components if there is a move_to command.
|
||||||
|
@ -140,7 +141,7 @@ class VectorizedMobject(Mobject):
|
||||||
but will be tracked in a separate special list for when
|
but will be tracked in a separate special list for when
|
||||||
it comes time to display.
|
it comes time to display.
|
||||||
"""
|
"""
|
||||||
subpath_mobject = VectorizedMobject(
|
subpath_mobject = VMobject(
|
||||||
is_subpath = True
|
is_subpath = True
|
||||||
)
|
)
|
||||||
subpath_mobject.set_points(points)
|
subpath_mobject.set_points(points)
|
||||||
|
@ -176,7 +177,13 @@ class VectorizedMobject(Mobject):
|
||||||
## Alignment
|
## Alignment
|
||||||
|
|
||||||
def align_points_with_larger(self, larger_mobject):
|
def align_points_with_larger(self, larger_mobject):
|
||||||
assert(isinstance(larger_mobject, VectorizedMobject))
|
assert(isinstance(larger_mobject, VMobject))
|
||||||
|
num_anchors = self.get_num_anchor_points()
|
||||||
|
if num_anchors <= 1:
|
||||||
|
point = self.points[0] if len(self.points) else np.zeros(3)
|
||||||
|
self.points = np.zeros(larger_mobject.points.shape)
|
||||||
|
self.points[:,:] = point
|
||||||
|
return self
|
||||||
points = np.array([self.points[0]])
|
points = np.array([self.points[0]])
|
||||||
target_len = larger_mobject.get_num_anchor_points()-1
|
target_len = larger_mobject.get_num_anchor_points()-1
|
||||||
num_curves = self.get_num_anchor_points()-1
|
num_curves = self.get_num_anchor_points()-1
|
||||||
|
@ -199,7 +206,7 @@ class VectorizedMobject(Mobject):
|
||||||
self.set_points(points)
|
self.set_points(points)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_point_mobject(self, center):
|
def get_point_mobject(self, center = None):
|
||||||
if center is None:
|
if center is None:
|
||||||
center = self.get_center()
|
center = self.get_center()
|
||||||
return VectorizedPoint(center)
|
return VectorizedPoint(center)
|
||||||
|
@ -219,7 +226,7 @@ class VectorizedMobject(Mobject):
|
||||||
))
|
))
|
||||||
|
|
||||||
def become_partial(self, mobject, a, b):
|
def become_partial(self, mobject, a, b):
|
||||||
assert(isinstance(mobject, VectorizedMobject))
|
assert(isinstance(mobject, VMobject))
|
||||||
#Partial curve includes three portions:
|
#Partial curve includes three portions:
|
||||||
#-A middle section, which matches the curve exactly
|
#-A middle section, which matches the curve exactly
|
||||||
#-A start, which is some ending portion of an inner cubic
|
#-A start, which is some ending portion of an inner cubic
|
||||||
|
@ -246,18 +253,18 @@ class VectorizedMobject(Mobject):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class VectorizedPoint(VectorizedMobject):
|
class VectorizedPoint(VMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color" : BLACK,
|
"color" : BLACK,
|
||||||
}
|
}
|
||||||
def __init__(self, location = ORIGIN, **kwargs):
|
def __init__(self, location = ORIGIN, **kwargs):
|
||||||
VectorizedMobject.__init__(self, **kwargs)
|
VMobject.__init__(self, **kwargs)
|
||||||
self.set_points([location])
|
self.set_points([location])
|
||||||
|
|
||||||
class VectorizedMobjectFromSVGPathstring(VectorizedMobject):
|
class VMobjectFromSVGPathstring(VMobject):
|
||||||
def __init__(self, path_string, **kwargs):
|
def __init__(self, path_string, **kwargs):
|
||||||
digest_locals(self)
|
digest_locals(self)
|
||||||
VectorizedMobject.__init__(self, **kwargs)
|
VMobject.__init__(self, **kwargs)
|
||||||
|
|
||||||
def get_path_commands(self):
|
def get_path_commands(self):
|
||||||
return [
|
return [
|
||||||
|
|
Loading…
Add table
Reference in a new issue