mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 17:07:45 +00:00
Merge branch 'master' into add_warnings
This commit is contained in:
commit
4466cfe727
27 changed files with 636 additions and 149 deletions
|
|
@ -4,7 +4,28 @@ Changelog
|
|||
Unreleased
|
||||
----------
|
||||
|
||||
No changes
|
||||
Fixed bugs
|
||||
^^^^^^^^^^
|
||||
|
||||
- `#1592 <https://github.com/3b1b/manim/pull/1592>`__: Fixed ``put_start_and_end_on`` in 3D
|
||||
- `#1601 <https://github.com/3b1b/manim/pull/1601>`__: Fixed ``DecimalNumber``'s scaling issue
|
||||
|
||||
New Features
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- `#1598 <https://github.com/3b1b/manim/pull/1598>`__: Supported the elliptical arc command ``A`` for ``SVGMobject``
|
||||
- `#1607 <https://github.com/3b1b/manim/pull/1607>`__: Added ``FlashyFadeIn``
|
||||
- `#1607 <https://github.com/3b1b/manim/pull/1607>`__: Save triangulation
|
||||
- `#1625 <https://github.com/3b1b/manim/pull/1625>`__: Added new ``Code`` mobject
|
||||
- `bd356da <https://github.com/3b1b/manim/commit/bd356daa99bfe3134fcb192a5f72e0d76d853801>`__: Added ``VCube``
|
||||
- `6d72893 <https://github.com/3b1b/manim/commit/6d7289338234acc6658b9377c0f0084aa1fa7119>`__: Supported ``ValueTracker`` to track vectors
|
||||
|
||||
Refactor
|
||||
^^^^^^^^
|
||||
|
||||
- `#1601 <https://github.com/3b1b/manim/pull/1601>`__: Change back to simpler ``Mobject.scale`` implementation
|
||||
- `b667db2 <https://github.com/3b1b/manim/commit/b667db2d311a11cbbca2a6ff511d2c3cf1675486>`__: Simplify ``Square``
|
||||
- `40290ad <https://github.com/3b1b/manim/commit/40290ada8343f10901fa9151cbdf84689667786d>`__: Removed unused parameter ``triangulation_locked``
|
||||
|
||||
v1.1.0
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -316,17 +316,6 @@ class Camera(object):
|
|||
self.frame.set_height(frame_height)
|
||||
self.frame.set_width(frame_width)
|
||||
|
||||
def pixel_coords_to_space_coords(self, px, py, relative=False):
|
||||
pw, ph = self.fbo.size
|
||||
fw, fh = self.get_frame_shape()
|
||||
fc = self.get_frame_center()
|
||||
if relative:
|
||||
return 2 * np.array([px / pw, py / ph, 0])
|
||||
else:
|
||||
# Only scale wrt one axis
|
||||
scale = fh / ph
|
||||
return fc + scale * np.array([(px - pw / 2), (py - ph / 2), 0])
|
||||
|
||||
# Rendering
|
||||
def capture(self, *mobjects, **kwargs):
|
||||
self.refresh_perspective_uniforms()
|
||||
|
|
@ -423,6 +412,8 @@ class Camera(object):
|
|||
shader[name].value = tid
|
||||
for name, value in it.chain(shader_wrapper.uniforms.items(), self.perspective_uniforms.items()):
|
||||
try:
|
||||
if isinstance(value, np.ndarray):
|
||||
value = tuple(value)
|
||||
shader[name].value = value
|
||||
except KeyError:
|
||||
pass
|
||||
|
|
@ -449,12 +440,13 @@ class Camera(object):
|
|||
}
|
||||
|
||||
def init_textures(self):
|
||||
self.path_to_texture_id = {}
|
||||
self.n_textures = 0
|
||||
self.path_to_texture = {}
|
||||
|
||||
def get_texture_id(self, path):
|
||||
if path not in self.path_to_texture_id:
|
||||
# A way to increase tid's sequentially
|
||||
tid = len(self.path_to_texture_id)
|
||||
if path not in self.path_to_texture:
|
||||
tid = self.n_textures
|
||||
self.n_textures += 1
|
||||
im = Image.open(path).convert("RGBA")
|
||||
texture = self.ctx.texture(
|
||||
size=im.size,
|
||||
|
|
@ -462,8 +454,14 @@ class Camera(object):
|
|||
data=im.tobytes(),
|
||||
)
|
||||
texture.use(location=tid)
|
||||
self.path_to_texture_id[path] = tid
|
||||
return self.path_to_texture_id[path]
|
||||
self.path_to_texture[path] = (tid, texture)
|
||||
return self.path_to_texture[path][0]
|
||||
|
||||
def release_texture(self, path):
|
||||
tid_and_texture = self.path_to_texture.pop(path, None)
|
||||
if tid_and_texture:
|
||||
tid_and_texture[1].release()
|
||||
return self
|
||||
|
||||
|
||||
# Mostly just defined so old scenes don't break
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
from manimlib.constants import *
|
||||
import numpy as np
|
||||
from manimlib.constants import BLUE_D
|
||||
from manimlib.constants import BLUE_B
|
||||
from manimlib.constants import BLUE_E
|
||||
from manimlib.constants import GREY_BROWN
|
||||
from manimlib.constants import WHITE
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.utils.rate_functions import smooth
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
|
||||
|
||||
class AnimatedBoundary(VGroup):
|
||||
|
|
@ -74,25 +79,63 @@ class TracedPath(VMobject):
|
|||
CONFIG = {
|
||||
"stroke_width": 2,
|
||||
"stroke_color": WHITE,
|
||||
"min_distance_to_new_point": 0.1,
|
||||
"time_traced": np.inf,
|
||||
"fill_opacity": 0,
|
||||
"time_per_anchor": 1 / 15,
|
||||
}
|
||||
|
||||
def __init__(self, traced_point_func, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.traced_point_func = traced_point_func
|
||||
self.add_updater(lambda m: m.update_path())
|
||||
self.time = 0
|
||||
self.traced_points = []
|
||||
self.add_updater(lambda m, dt: m.update_path(dt))
|
||||
|
||||
def update_path(self):
|
||||
new_point = self.traced_point_func()
|
||||
if not self.has_points():
|
||||
self.start_new_path(new_point)
|
||||
self.add_line_to(new_point)
|
||||
def update_path(self, dt):
|
||||
if dt == 0:
|
||||
return self
|
||||
point = self.traced_point_func().copy()
|
||||
self.traced_points.append(point)
|
||||
|
||||
if self.time_traced < np.inf:
|
||||
n_relevant_points = int(self.time_traced / dt + 0.5)
|
||||
# n_anchors = int(self.time_traced / self.time_per_anchor)
|
||||
n_tps = len(self.traced_points)
|
||||
if n_tps < n_relevant_points:
|
||||
points = self.traced_points + [point] * (n_relevant_points - n_tps)
|
||||
else:
|
||||
points = self.traced_points[n_tps - n_relevant_points:]
|
||||
# points = [
|
||||
# self.traced_points[max(n_tps - int(alpha * n_relevant_points) - 1, 0)]
|
||||
# for alpha in np.linspace(1, 0, n_anchors)
|
||||
# ]
|
||||
# Every now and then refresh the list
|
||||
if n_tps > 10 * n_relevant_points:
|
||||
self.traced_points = self.traced_points[-n_relevant_points:]
|
||||
else:
|
||||
# Set the end to be the new point
|
||||
self.get_points()[-1] = new_point
|
||||
# sparseness = max(int(self.time_per_anchor / dt), 1)
|
||||
# points = self.traced_points[::sparseness]
|
||||
# points[-1] = self.traced_points[-1]
|
||||
points = self.traced_points
|
||||
|
||||
# Second to last point
|
||||
nppcc = self.n_points_per_curve
|
||||
dist = get_norm(new_point - self.get_points()[-nppcc])
|
||||
if dist >= self.min_distance_to_new_point:
|
||||
self.add_line_to(new_point)
|
||||
if points:
|
||||
self.set_points_smoothly(points)
|
||||
|
||||
self.time += dt
|
||||
return self
|
||||
|
||||
|
||||
class TracingTail(TracedPath):
|
||||
CONFIG = {
|
||||
"stroke_width": (0, 3),
|
||||
"stroke_opacity": (0, 1),
|
||||
"stroke_color": WHITE,
|
||||
"time_traced": 1.0,
|
||||
}
|
||||
|
||||
def __init__(self, mobject_or_func, **kwargs):
|
||||
if isinstance(mobject_or_func, Mobject):
|
||||
func = mobject_or_func.get_center
|
||||
else:
|
||||
func = mobject_or_func
|
||||
super().__init__(func, **kwargs)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from manimlib.mobject.geometry import Rectangle
|
|||
from manimlib.mobject.number_line import NumberLine
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.config_ops import merge_dicts_recursively
|
||||
from manimlib.utils.simple_functions import binary_search
|
||||
from manimlib.utils.space_ops import angle_of_vector
|
||||
|
|
@ -25,13 +26,18 @@ class CoordinateSystem():
|
|||
"""
|
||||
CONFIG = {
|
||||
"dimension": 2,
|
||||
"x_range": np.array([-8.0, 8.0, 1.0]),
|
||||
"y_range": np.array([-4.0, 4.0, 1.0]),
|
||||
"width": None,
|
||||
"height": None,
|
||||
"default_x_range": [-8.0, 8.0, 1.0],
|
||||
"default_y_range": [-4.0, 4.0, 1.0],
|
||||
"width": FRAME_WIDTH,
|
||||
"height": FRAME_HEIGHT,
|
||||
"num_sampled_graph_points_per_tick": 20,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.x_range = np.array(self.default_x_range)
|
||||
self.y_range = np.array(self.default_y_range)
|
||||
|
||||
def coords_to_point(self, *coords):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
|
@ -127,6 +133,7 @@ class CoordinateSystem():
|
|||
**kwargs
|
||||
)
|
||||
graph.underlying_function = function
|
||||
graph.x_range = x_range
|
||||
return graph
|
||||
|
||||
def get_parametric_curve(self, function, **kwargs):
|
||||
|
|
@ -282,7 +289,9 @@ class Axes(VGroup, CoordinateSystem):
|
|||
x_range=None,
|
||||
y_range=None,
|
||||
**kwargs):
|
||||
super().__init__(**kwargs)
|
||||
CoordinateSystem.__init__(self, **kwargs)
|
||||
VGroup.__init__(self, **kwargs)
|
||||
|
||||
if x_range is not None:
|
||||
self.x_range[:len(x_range)] = x_range
|
||||
if y_range is not None:
|
||||
|
|
@ -441,7 +450,7 @@ class NumberPlane(Axes):
|
|||
return lines1, lines2
|
||||
|
||||
def get_lines_parallel_to_axis(self, axis1, axis2):
|
||||
freq = axis1.x_step
|
||||
freq = axis2.x_step
|
||||
ratio = self.faded_line_ratio
|
||||
line = Line(axis1.get_start(), axis1.get_end())
|
||||
dense_freq = (1 + ratio)
|
||||
|
|
@ -501,15 +510,15 @@ class ComplexPlane(NumberPlane):
|
|||
def p2n(self, point):
|
||||
return self.point_to_number(point)
|
||||
|
||||
def get_default_coordinate_values(self):
|
||||
def get_default_coordinate_values(self, skip_first=True):
|
||||
x_numbers = self.get_x_axis().get_tick_range()[1:]
|
||||
y_numbers = self.get_y_axis().get_tick_range()[1:]
|
||||
y_numbers = [complex(0, y) for y in y_numbers if y != 0]
|
||||
return [*x_numbers, *y_numbers]
|
||||
|
||||
def add_coordinate_labels(self, numbers=None, **kwargs):
|
||||
def add_coordinate_labels(self, numbers=None, skip_first=True, **kwargs):
|
||||
if numbers is None:
|
||||
numbers = self.get_default_coordinate_values()
|
||||
numbers = self.get_default_coordinate_values(skip_first)
|
||||
|
||||
self.coordinate_labels = VGroup()
|
||||
for number in numbers:
|
||||
|
|
@ -522,6 +531,15 @@ class ComplexPlane(NumberPlane):
|
|||
axis = self.get_x_axis()
|
||||
value = z.real
|
||||
number_mob = axis.get_number_mobject(value, **kwargs)
|
||||
# For i and -i, remove the "1"
|
||||
if z.imag == 1:
|
||||
number_mob.remove(number_mob[0])
|
||||
if z.imag == -1:
|
||||
number_mob.remove(number_mob[1])
|
||||
number_mob[0].next_to(
|
||||
number_mob[1], LEFT,
|
||||
buff=number_mob[0].get_width() / 4
|
||||
)
|
||||
self.coordinate_labels.add(number_mob)
|
||||
self.add(self.coordinate_labels)
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import numpy as np
|
||||
import math
|
||||
import numbers
|
||||
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
|
|
@ -27,6 +29,7 @@ DEFAULT_ARROW_TIP_LENGTH = 0.35
|
|||
DEFAULT_ARROW_TIP_WIDTH = 0.35
|
||||
|
||||
|
||||
# Deprecate?
|
||||
class TipableVMobject(VMobject):
|
||||
"""
|
||||
Meant for shared functionality between Arc and Line.
|
||||
|
|
@ -404,32 +407,38 @@ class Line(TipableVMobject):
|
|||
self.set_points_by_ends(self.start, self.end, self.buff, self.path_arc)
|
||||
|
||||
def set_points_by_ends(self, start, end, buff=0, path_arc=0):
|
||||
if path_arc:
|
||||
self.set_points(Arc.create_quadratic_bezier_points(path_arc))
|
||||
self.put_start_and_end_on(start, end)
|
||||
else:
|
||||
vect = end - start
|
||||
dist = get_norm(vect)
|
||||
if np.isclose(dist, 0):
|
||||
self.set_points_as_corners([start, end])
|
||||
self.account_for_buff(self.buff)
|
||||
return self
|
||||
if path_arc:
|
||||
neg = path_arc < 0
|
||||
if neg:
|
||||
path_arc = -path_arc
|
||||
start, end = end, start
|
||||
radius = (dist / 2) / math.sin(path_arc / 2)
|
||||
alpha = (PI - path_arc) / 2
|
||||
center = start + radius * normalize(rotate_vector(end - start, alpha))
|
||||
|
||||
raw_arc_points = Arc.create_quadratic_bezier_points(
|
||||
angle=path_arc - 2 * buff / radius,
|
||||
start_angle=angle_of_vector(start - center) + buff / radius,
|
||||
)
|
||||
if neg:
|
||||
raw_arc_points = raw_arc_points[::-1]
|
||||
self.set_points(center + radius * raw_arc_points)
|
||||
else:
|
||||
if buff > 0 and dist > 0:
|
||||
start = start + vect * (buff / dist)
|
||||
end = end - vect * (buff / dist)
|
||||
self.set_points_as_corners([start, end])
|
||||
return self
|
||||
|
||||
def set_path_arc(self, new_value):
|
||||
self.path_arc = new_value
|
||||
self.init_points()
|
||||
|
||||
def account_for_buff(self, buff):
|
||||
if buff == 0:
|
||||
return
|
||||
#
|
||||
if self.path_arc == 0:
|
||||
length = self.get_length()
|
||||
else:
|
||||
length = self.get_arc_length()
|
||||
#
|
||||
if length < 2 * buff:
|
||||
return
|
||||
buff_prop = buff / length
|
||||
self.pointwise_become_partial(self, buff_prop, 1 - buff_prop)
|
||||
return self
|
||||
|
||||
def set_start_and_end_attrs(self, start, end):
|
||||
# If either start or end are Mobjects, this
|
||||
# gives their centers
|
||||
|
|
@ -439,8 +448,8 @@ class Line(TipableVMobject):
|
|||
# Now that we know the direction between them,
|
||||
# we can find the appropriate boundary point from
|
||||
# start and end, if they're mobjects
|
||||
self.start = self.pointify(start, vect) + self.buff * vect
|
||||
self.end = self.pointify(end, -vect) - self.buff * vect
|
||||
self.start = self.pointify(start, vect)
|
||||
self.end = self.pointify(end, -vect)
|
||||
|
||||
def pointify(self, mob_or_point, direction=None):
|
||||
"""
|
||||
|
|
@ -461,8 +470,10 @@ class Line(TipableVMobject):
|
|||
|
||||
def put_start_and_end_on(self, start, end):
|
||||
curr_start, curr_end = self.get_start_and_end()
|
||||
if (curr_start == curr_end).all():
|
||||
self.set_points_by_ends(start, end, self.path_arc)
|
||||
if np.isclose(curr_start, curr_end).all():
|
||||
# Handle null lines more gracefully
|
||||
self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)
|
||||
return self
|
||||
return super().put_start_and_end_on(start, end)
|
||||
|
||||
def get_vector(self):
|
||||
|
|
@ -494,8 +505,8 @@ class Line(TipableVMobject):
|
|||
)
|
||||
return self
|
||||
|
||||
def set_length(self, length):
|
||||
self.scale(length / self.get_length())
|
||||
def set_length(self, length, **kwargs):
|
||||
self.scale(length / self.get_length(), **kwargs)
|
||||
|
||||
|
||||
class DashedLine(Line):
|
||||
|
|
@ -578,6 +589,80 @@ class Elbow(VMobject):
|
|||
|
||||
|
||||
class Arrow(Line):
|
||||
CONFIG = {
|
||||
"stroke_color": GREY_A,
|
||||
"stroke_width": 5,
|
||||
"tip_width_ratio": 4,
|
||||
"width_to_tip_len": 0.0075,
|
||||
"max_tip_length_to_length_ratio": 0.3,
|
||||
"max_width_to_length_ratio": 10,
|
||||
"buff": 0.25,
|
||||
}
|
||||
|
||||
def set_points_by_ends(self, start, end, buff=0, path_arc=0):
|
||||
super().set_points_by_ends(start, end, buff, path_arc)
|
||||
self.insert_tip_anchor()
|
||||
return self
|
||||
|
||||
def init_colors(self):
|
||||
super().init_colors()
|
||||
self.create_tip_with_stroke_width()
|
||||
|
||||
def get_arc_length(self):
|
||||
# Push up into Line?
|
||||
arc_len = get_norm(self.get_vector())
|
||||
if self.path_arc > 0:
|
||||
arc_len *= self.path_arc / (2 * math.sin(self.path_arc / 2))
|
||||
return arc_len
|
||||
|
||||
def insert_tip_anchor(self):
|
||||
prev_end = self.get_end()
|
||||
arc_len = self.get_arc_length()
|
||||
tip_len = self.get_stroke_width() * self.width_to_tip_len * self.tip_width_ratio
|
||||
if tip_len >= self.max_tip_length_to_length_ratio * arc_len:
|
||||
alpha = self.max_tip_length_to_length_ratio
|
||||
else:
|
||||
alpha = tip_len / arc_len
|
||||
self.pointwise_become_partial(self, 0, 1 - alpha)
|
||||
self.add_line_to(prev_end)
|
||||
return self
|
||||
|
||||
def create_tip_with_stroke_width(self):
|
||||
width = min(
|
||||
self.max_stroke_width,
|
||||
self.max_width_to_length_ratio * self.get_length(),
|
||||
)
|
||||
widths_array = np.full(self.get_num_points(), width)
|
||||
nppc = self.n_points_per_curve
|
||||
if len(widths_array) > nppc:
|
||||
widths_array[-nppc:] = [
|
||||
a * self.tip_width_ratio * width
|
||||
for a in np.linspace(1, 0, nppc)
|
||||
]
|
||||
self.set_stroke(width=widths_array)
|
||||
return self
|
||||
|
||||
def reset_tip(self):
|
||||
self.set_points_by_ends(
|
||||
self.get_start(),
|
||||
self.get_end(),
|
||||
path_arc=self.path_arc,
|
||||
)
|
||||
self.create_tip_with_stroke_width()
|
||||
return self
|
||||
|
||||
def set_stroke(self, color=None, width=None, *args, **kwargs):
|
||||
super().set_stroke(color=color, width=width, *args, **kwargs)
|
||||
if isinstance(width, numbers.Number):
|
||||
self.max_stroke_width = width
|
||||
self.reset_tip()
|
||||
return self
|
||||
|
||||
def _handle_scale_side_effects(self, scale_factor):
|
||||
return self.reset_tip()
|
||||
|
||||
|
||||
class FillArrow(Line):
|
||||
CONFIG = {
|
||||
"fill_color": GREY_A,
|
||||
"fill_opacity": 1,
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class Matrix(VMobject):
|
|||
|
||||
def __init__(self, matrix, **kwargs):
|
||||
"""
|
||||
Matrix can either either include numbres, tex_strings,
|
||||
Matrix can either include numbers, tex_strings,
|
||||
or mobjects
|
||||
"""
|
||||
VMobject.__init__(self, **kwargs)
|
||||
|
|
|
|||
|
|
@ -365,14 +365,17 @@ class Mobject(object):
|
|||
self.center()
|
||||
return self
|
||||
|
||||
def replicate(self, n):
|
||||
return self.get_group_class()(
|
||||
*(self.copy() for x in range(n))
|
||||
)
|
||||
|
||||
def get_grid(self, n_rows, n_cols, height=None, **kwargs):
|
||||
"""
|
||||
Returns a new mobject containing multiple copies of this one
|
||||
arranged in a grid
|
||||
"""
|
||||
grid = self.get_group_class()(
|
||||
*(self.copy() for n in range(n_rows * n_cols))
|
||||
)
|
||||
grid = self.replicate(n_rows * n_cols)
|
||||
grid.arrange_in_grid(n_rows, n_cols, **kwargs)
|
||||
if height is not None:
|
||||
grid.set_height(height)
|
||||
|
|
@ -408,8 +411,10 @@ class Mobject(object):
|
|||
for key in self.data:
|
||||
copy_mobject.data[key] = self.data[key].copy()
|
||||
|
||||
# TODO, are uniforms ever numpy arrays?
|
||||
copy_mobject.uniforms = dict(self.uniforms)
|
||||
for key in self.uniforms:
|
||||
if isinstance(self.uniforms[key], np.ndarray):
|
||||
copy_mobject.uniforms[key] = self.uniforms[key].copy()
|
||||
|
||||
copy_mobject.submobjects = []
|
||||
copy_mobject.add(*[sm.copy() for sm in self.submobjects])
|
||||
|
|
@ -572,14 +577,14 @@ class Mobject(object):
|
|||
respect to that point.
|
||||
"""
|
||||
scale_factor = max(scale_factor, min_scale_factor)
|
||||
for mob in self.get_family():
|
||||
mob._handle_scale_side_effects(scale_factor)
|
||||
self.apply_points_function(
|
||||
lambda points: scale_factor * points,
|
||||
about_point=about_point,
|
||||
about_edge=about_edge,
|
||||
works_on_bounding_box=True,
|
||||
)
|
||||
for mob in self.get_family():
|
||||
mob._handle_scale_side_effects(scale_factor)
|
||||
return self
|
||||
|
||||
def _handle_scale_side_effects(self, scale_factor):
|
||||
|
|
@ -773,6 +778,21 @@ class Mobject(object):
|
|||
def set_depth(self, depth, stretch=False, **kwargs):
|
||||
return self.rescale_to_fit(depth, 2, stretch=stretch, **kwargs)
|
||||
|
||||
def set_max_width(self, max_width, **kwargs):
|
||||
if self.get_width() > max_width:
|
||||
self.set_width(max_width, **kwargs)
|
||||
return self
|
||||
|
||||
def set_max_height(self, max_height, **kwargs):
|
||||
if self.get_height() > max_height:
|
||||
self.set_height(max_height, **kwargs)
|
||||
return self
|
||||
|
||||
def set_max_depth(self, max_depth, **kwargs):
|
||||
if self.get_depth() > max_depth:
|
||||
self.set_depth(max_depth, **kwargs)
|
||||
return self
|
||||
|
||||
def set_coord(self, value, dim, direction=ORIGIN):
|
||||
curr = self.get_coord(dim, direction)
|
||||
shift_vect = np.zeros(self.dim)
|
||||
|
|
@ -845,7 +865,7 @@ class Mobject(object):
|
|||
)
|
||||
self.rotate(
|
||||
np.arctan2(curr_vect[2], get_norm(curr_vect[:2])) - np.arctan2(target_vect[2], get_norm(target_vect[:2])),
|
||||
axis = np.array([-target_vect[1], target_vect[0], 0]),
|
||||
axis=np.array([-target_vect[1], target_vect[0], 0]),
|
||||
)
|
||||
self.shift(start - self.get_start())
|
||||
return self
|
||||
|
|
@ -1073,14 +1093,16 @@ class Mobject(object):
|
|||
|
||||
def get_start(self):
|
||||
self.throw_error_if_no_points()
|
||||
return np.array(self.get_points()[0])
|
||||
return self.get_points()[0].copy()
|
||||
|
||||
def get_end(self):
|
||||
self.throw_error_if_no_points()
|
||||
return np.array(self.get_points()[-1])
|
||||
return self.get_points()[-1].copy()
|
||||
|
||||
def get_start_and_end(self):
|
||||
return self.get_start(), self.get_end()
|
||||
self.throw_error_if_no_points()
|
||||
points = self.get_points()
|
||||
return (points[0].copy(), points[-1].copy())
|
||||
|
||||
def point_from_proportion(self, alpha):
|
||||
points = self.get_points()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from manimlib.utils.bezier import interpolate
|
|||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.config_ops import merge_dicts_recursively
|
||||
from manimlib.utils.simple_functions import fdiv
|
||||
from manimlib.utils.space_ops import normalize
|
||||
|
||||
|
||||
class NumberLine(Line):
|
||||
|
|
@ -83,7 +82,7 @@ class NumberLine(Line):
|
|||
ticks = VGroup()
|
||||
for x in self.get_tick_range():
|
||||
size = self.tick_size
|
||||
if x in self.numbers_with_elongated_ticks:
|
||||
if np.isclose(self.numbers_with_elongated_ticks, x).any():
|
||||
size *= self.longer_tick_multiple
|
||||
ticks.add(self.get_tick(x, size))
|
||||
self.add(ticks)
|
||||
|
|
@ -106,11 +105,13 @@ class NumberLine(Line):
|
|||
return interpolate(self.get_start(), self.get_end(), alpha)
|
||||
|
||||
def point_to_number(self, point):
|
||||
start, end = self.get_start_and_end()
|
||||
unit_vect = normalize(end - start)
|
||||
points = self.get_points()
|
||||
start = points[0]
|
||||
end = points[-1]
|
||||
vect = end - start
|
||||
proportion = fdiv(
|
||||
np.dot(point - start, unit_vect),
|
||||
np.dot(end - start, unit_vect),
|
||||
np.dot(point - start, vect),
|
||||
np.dot(end - start, vect),
|
||||
)
|
||||
return interpolate(self.x_min, self.x_max, proportion)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ class DecimalNumber(VMobject):
|
|||
|
||||
# Add non-numerical bits
|
||||
if self.show_ellipsis:
|
||||
self.add(self.string_to_mob("..."))
|
||||
dots = self.string_to_mob("...")
|
||||
dots.arrange(RIGHT, buff=2 * dots[0].get_width())
|
||||
self.add(dots)
|
||||
if self.unit is not None:
|
||||
self.unit_sign = self.string_to_mob(self.unit, SingleStringTex)
|
||||
self.add(self.unit_sign)
|
||||
|
|
@ -128,7 +130,7 @@ class DecimalNumber(VMobject):
|
|||
|
||||
def set_value(self, number):
|
||||
move_to_point = self.get_edge_center(self.edge_to_fix)
|
||||
old_submobjects = self.submobjects
|
||||
old_submobjects = list(self.submobjects)
|
||||
self.set_submobjects_from_number(number)
|
||||
self.move_to(move_to_point, self.edge_to_fix)
|
||||
for sm1, sm2 in zip(self.submobjects, old_submobjects):
|
||||
|
|
|
|||
|
|
@ -184,8 +184,9 @@ class SVGMobject(VMobject):
|
|||
corner_radius = rect_element.getAttribute("rx")
|
||||
|
||||
# input preprocessing
|
||||
fill_opacity = 1
|
||||
if fill_color in ["", "none", "#FFF", "#FFFFFF"] or Color(fill_color) == Color(WHITE):
|
||||
opacity = 0
|
||||
fill_opacity = 0
|
||||
fill_color = BLACK # shdn't be necessary but avoids error msgs
|
||||
if fill_color in ["#000", "#000000"]:
|
||||
fill_color = WHITE
|
||||
|
|
@ -213,7 +214,7 @@ class SVGMobject(VMobject):
|
|||
stroke_width=stroke_width,
|
||||
stroke_color=stroke_color,
|
||||
fill_color=fill_color,
|
||||
fill_opacity=opacity
|
||||
fill_opacity=fill_opacity
|
||||
)
|
||||
else:
|
||||
mob = RoundedRectangle(
|
||||
|
|
@ -321,7 +322,7 @@ class SVGMobject(VMobject):
|
|||
|
||||
class VMobjectFromSVGPathstring(VMobject):
|
||||
CONFIG = {
|
||||
"long_lines": True,
|
||||
"long_lines": False,
|
||||
"should_subdivide_sharp_curves": False,
|
||||
"should_remove_null_curves": False,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from functools import reduce
|
||||
import operator as op
|
||||
import re
|
||||
import itertools as it
|
||||
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import Line
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ from manimlib.utils.directories import get_downloads_dir, get_text_dir
|
|||
from manimpango import PangoUtils, TextSetting, MarkupUtils
|
||||
|
||||
TEXT_MOB_SCALE_FACTOR = 0.0076
|
||||
DEFAULT_LINE_SPACING_SCALE = 0.3
|
||||
DEFAULT_LINE_SPACING_SCALE = 0.6
|
||||
|
||||
|
||||
class Text(SVGMobject):
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ class DotCloud(PMobject):
|
|||
return self
|
||||
|
||||
def set_radii(self, radii):
|
||||
self.data["radii"][:] = resize_preserving_order(radii, len(self.data["radii"]))
|
||||
n_points = len(self.get_points())
|
||||
radii = np.array(radii).reshape((len(radii), 1))
|
||||
self.data["radii"] = resize_preserving_order(radii, n_points)
|
||||
self.refresh_bounding_box()
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ class ImageMobject(Mobject):
|
|||
}
|
||||
|
||||
def __init__(self, filename, **kwargs):
|
||||
path = get_full_raster_image_path(filename)
|
||||
self.set_image_path(get_full_raster_image_path(filename))
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def set_image_path(self, path):
|
||||
self.path = path
|
||||
self.image = Image.open(path)
|
||||
self.texture_paths = {"Texture": path}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def init_data(self):
|
||||
self.data = {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,17 @@ class PMobject(Mobject):
|
|||
def resize_points(self, size, resize_func=resize_array):
|
||||
# TODO
|
||||
for key in self.data:
|
||||
if key == "bounding_box":
|
||||
continue
|
||||
if len(self.data[key]) != size:
|
||||
self.data[key] = resize_array(self.data[key], size)
|
||||
return self
|
||||
|
||||
def set_points(self, points):
|
||||
super().set_points(points)
|
||||
self.resize_points(len(points))
|
||||
return self
|
||||
|
||||
def add_points(self, points, rgbas=None, color=None, opacity=None):
|
||||
"""
|
||||
points must be a Nx3 numpy array, as must rgbas if it is not None
|
||||
|
|
@ -54,6 +61,8 @@ class PMobject(Mobject):
|
|||
for mob in self.family_members_with_points():
|
||||
to_keep = ~np.apply_along_axis(condition, 1, mob.get_points())
|
||||
for key in mob.data:
|
||||
if key == "bounding_box":
|
||||
continue
|
||||
mob.data[key] = mob.data[key][to_keep]
|
||||
return self
|
||||
|
||||
|
|
@ -85,7 +94,9 @@ class PMobject(Mobject):
|
|||
lower_index = int(a * pmobject.get_num_points())
|
||||
upper_index = int(b * pmobject.get_num_points())
|
||||
for key in self.data:
|
||||
self.data[key] = pmobject.data[key][lower_index:upper_index]
|
||||
if key == "bounding_box":
|
||||
continue
|
||||
self.data[key] = pmobject.data[key][lower_index:upper_index].copy()
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ class VMobject(Mobject):
|
|||
if isinstance(width, np.ndarray):
|
||||
arr = width.reshape((len(width), 1))
|
||||
else:
|
||||
arr = np.array([[w] for w in listify(width)])
|
||||
arr = np.array([[w] for w in listify(width)], dtype=float)
|
||||
mob.data['stroke_width'] = arr
|
||||
|
||||
if background is not None:
|
||||
|
|
@ -149,6 +149,7 @@ class VMobject(Mobject):
|
|||
stroke_opacity=None,
|
||||
stroke_rgba=None,
|
||||
stroke_width=None,
|
||||
stroke_background=True,
|
||||
gloss=None,
|
||||
shadow=None,
|
||||
recurse=True):
|
||||
|
|
@ -163,13 +164,17 @@ class VMobject(Mobject):
|
|||
|
||||
if stroke_rgba is not None:
|
||||
self.data['stroke_rgba'] = resize_with_interpolation(stroke_rgba, len(fill_rgba))
|
||||
self.set_stroke(width=stroke_width)
|
||||
self.set_stroke(
|
||||
width=stroke_width,
|
||||
background=stroke_background,
|
||||
)
|
||||
else:
|
||||
self.set_stroke(
|
||||
color=stroke_color,
|
||||
width=stroke_width,
|
||||
opacity=stroke_opacity,
|
||||
recurse=recurse,
|
||||
background=stroke_background,
|
||||
)
|
||||
|
||||
if gloss is not None:
|
||||
|
|
@ -183,6 +188,7 @@ class VMobject(Mobject):
|
|||
"fill_rgba": self.data['fill_rgba'],
|
||||
"stroke_rgba": self.data['stroke_rgba'],
|
||||
"stroke_width": self.data['stroke_width'],
|
||||
"stroke_background": self.draw_stroke_behind_fill,
|
||||
"gloss": self.get_gloss(),
|
||||
"shadow": self.get_shadow(),
|
||||
}
|
||||
|
|
@ -423,7 +429,10 @@ class VMobject(Mobject):
|
|||
|
||||
def set_points_smoothly(self, points, true_smooth=False):
|
||||
self.set_points_as_corners(points)
|
||||
self.make_smooth()
|
||||
if true_smooth:
|
||||
self.make_smooth()
|
||||
else:
|
||||
self.make_approximately_smooth()
|
||||
return self
|
||||
|
||||
def change_anchor_mode(self, mode):
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@ class ValueTracker(Mobject):
|
|||
)
|
||||
|
||||
def get_value(self):
|
||||
return self.data["value"][0, :]
|
||||
result = self.data["value"][0, :]
|
||||
if len(result) == 1:
|
||||
return result[0]
|
||||
return result
|
||||
|
||||
def set_value(self, value):
|
||||
self.data["value"][0, :] = value
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ class Scene(object):
|
|||
from manimlib.window import Window
|
||||
self.window = Window(scene=self, **self.window_config)
|
||||
self.camera_config["ctx"] = self.window.ctx
|
||||
self.camera_config["frame_rate"] = 30 # Where's that 30 from?
|
||||
else:
|
||||
self.window = None
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ and the mouse to interact with the scene. Just press `q` if you want to quit.")
|
|||
self.quit_interaction = False
|
||||
self.lock_static_mobject_data()
|
||||
while not (self.window.is_closing or self.quit_interaction):
|
||||
self.update_frame()
|
||||
self.update_frame(1 / self.camera.frame_rate)
|
||||
if self.window.is_closing:
|
||||
self.window.destroy()
|
||||
if self.quit_interaction:
|
||||
|
|
@ -120,6 +121,9 @@ and the mouse to interact with the scene. Just press `q` if you want to quit.")
|
|||
self.linger_after_completion = False
|
||||
self.update_frame()
|
||||
|
||||
# Save scene state at the point of embedding
|
||||
self.save_state()
|
||||
|
||||
from IPython.terminal.embed import InteractiveShellEmbed
|
||||
shell = InteractiveShellEmbed()
|
||||
# Have the frame update after each command
|
||||
|
|
@ -251,6 +255,18 @@ the window directly. To do so, you need to type `touch()` or `self.interact()`")
|
|||
def get_mobject_copies(self):
|
||||
return [m.copy() for m in self.mobjects]
|
||||
|
||||
def point_to_mobject(self, point, search_set=None, buff=0):
|
||||
"""
|
||||
E.g. if clicking on the scene, this returns the top layer mobject
|
||||
under a given point
|
||||
"""
|
||||
if search_set is None:
|
||||
search_set = self.mobjects
|
||||
for mobject in reversed(search_set):
|
||||
if mobject.is_point_touching(point, buff=buff):
|
||||
return mobject
|
||||
return None
|
||||
|
||||
# Related to skipping
|
||||
def update_skipping_status(self):
|
||||
if self.start_at_animation_number is not None:
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
///// INSERT COLOR_MAP FUNCTION HERE /////
|
||||
|
||||
vec4 add_light(vec4 color,
|
||||
vec3 point,
|
||||
vec3 unit_normal,
|
||||
vec3 light_coords,
|
||||
float gloss,
|
||||
float shadow){
|
||||
///// INSERT COLOR FUNCTION HERE /////
|
||||
// The line above may be replaced by arbitrary code snippets, as per
|
||||
// the method Mobject.set_color_by_code
|
||||
if(gloss == 0.0 && shadow == 0.0) return color;
|
||||
|
||||
// TODO, do we actually want this? It effectively treats surfaces as two-sided
|
||||
if(unit_normal.z < 0){
|
||||
unit_normal *= -1;
|
||||
}
|
||||
|
||||
// TODO, read this in as a uniform?
|
||||
float camera_distance = 6;
|
||||
// Assume everything has already been rotated such that camera is in the z-direction
|
||||
vec3 to_camera = vec3(0, 0, camera_distance) - point;
|
||||
vec3 to_light = light_coords - point;
|
||||
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
|
||||
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
|
||||
float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));
|
||||
float dp2 = dot(normalize(to_light), unit_normal);
|
||||
float darkening = mix(1, max(dp2, 0), shadow);
|
||||
return vec4(
|
||||
darkening * mix(color.rgb, vec3(1.0), shine),
|
||||
color.a
|
||||
);
|
||||
}
|
||||
|
||||
vec4 finalize_color(vec4 color,
|
||||
vec3 point,
|
||||
vec3 unit_normal,
|
||||
vec3 light_coords,
|
||||
float gloss,
|
||||
float shadow){
|
||||
// Put insertion here instead
|
||||
return add_light(color, point, unit_normal, light_coords, gloss, shadow);
|
||||
}
|
||||
15
manimlib/shaders/inserts/complex_functions.glsl
Normal file
15
manimlib/shaders/inserts/complex_functions.glsl
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
vec2 complex_mult(vec2 z, vec2 w){
|
||||
return vec2(z.x * w.x - z.y * w.y, z.x * w.y + z.y * w.x);
|
||||
}
|
||||
|
||||
vec2 complex_div(vec2 z, vec2 w){
|
||||
return complex_mult(z, vec2(w.x, -w.y)) / (w.x * w.x + w.y * w.y);
|
||||
}
|
||||
|
||||
vec2 complex_pow(vec2 z, int n){
|
||||
vec2 result = vec2(1.0, 0.0);
|
||||
for(int i = 0; i < n; i++){
|
||||
result = complex_mult(result, z);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
77
manimlib/shaders/mandelbrot_fractal/frag.glsl
Normal file
77
manimlib/shaders/mandelbrot_fractal/frag.glsl
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 light_source_position;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
uniform float focal_distance;
|
||||
|
||||
uniform vec2 parameter;
|
||||
uniform float opacity;
|
||||
uniform float n_steps;
|
||||
uniform float mandelbrot;
|
||||
|
||||
uniform vec3 color0;
|
||||
uniform vec3 color1;
|
||||
uniform vec3 color2;
|
||||
uniform vec3 color3;
|
||||
uniform vec3 color4;
|
||||
uniform vec3 color5;
|
||||
uniform vec3 color6;
|
||||
uniform vec3 color7;
|
||||
uniform vec3 color8;
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
#INSERT finalize_color.glsl
|
||||
#INSERT complex_functions.glsl
|
||||
|
||||
const int MAX_DEGREE = 5;
|
||||
|
||||
void main() {
|
||||
vec3 color_map[9] = vec3[9](
|
||||
color0, color1, color2, color3,
|
||||
color4, color5, color6, color7, color8
|
||||
);
|
||||
vec3 color;
|
||||
|
||||
vec2 z;
|
||||
vec2 c;
|
||||
|
||||
if(bool(mandelbrot)){
|
||||
c = xyz_coords.xy;
|
||||
z = vec2(0.0, 0.0);
|
||||
}else{
|
||||
c = parameter;
|
||||
z = xyz_coords.xy;
|
||||
}
|
||||
|
||||
float outer_bound = 2.0;
|
||||
bool stable = true;
|
||||
for(int n = 0; n < int(n_steps); n++){
|
||||
z = complex_mult(z, z) + c;
|
||||
if(length(z) > outer_bound){
|
||||
float float_n = float(n);
|
||||
float_n += log(outer_bound) / log(length(z));
|
||||
float_n += 0.5 * length(c);
|
||||
color = float_to_color(sqrt(float_n), 1.5, 8.0, color_map);
|
||||
stable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(stable){
|
||||
color = vec3(0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
frag_color = finalize_color(
|
||||
vec4(color, opacity),
|
||||
xyz_coords,
|
||||
vec3(0.0, 0.0, 1.0),
|
||||
light_source_position,
|
||||
gloss,
|
||||
shadow
|
||||
);
|
||||
}
|
||||
17
manimlib/shaders/mandelbrot_fractal/vert.glsl
Normal file
17
manimlib/shaders/mandelbrot_fractal/vert.glsl
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
out vec3 xyz_coords;
|
||||
|
||||
uniform float scale_factor;
|
||||
uniform vec3 offset;
|
||||
|
||||
#INSERT position_point_into_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
|
||||
void main(){
|
||||
xyz_coords = (point - offset) / scale_factor;
|
||||
gl_Position = get_gl_Position(position_point_into_frame(point));
|
||||
}
|
||||
157
manimlib/shaders/newton_fractal/frag.glsl
Normal file
157
manimlib/shaders/newton_fractal/frag.glsl
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 light_source_position;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
uniform float focal_distance;
|
||||
|
||||
uniform vec4 color0;
|
||||
uniform vec4 color1;
|
||||
uniform vec4 color2;
|
||||
uniform vec4 color3;
|
||||
uniform vec4 color4;
|
||||
|
||||
uniform vec2 coef0;
|
||||
uniform vec2 coef1;
|
||||
uniform vec2 coef2;
|
||||
uniform vec2 coef3;
|
||||
uniform vec2 coef4;
|
||||
uniform vec2 coef5;
|
||||
|
||||
uniform vec2 root0;
|
||||
uniform vec2 root1;
|
||||
uniform vec2 root2;
|
||||
uniform vec2 root3;
|
||||
uniform vec2 root4;
|
||||
|
||||
uniform float n_roots;
|
||||
uniform float n_steps;
|
||||
uniform float julia_highlight;
|
||||
uniform float saturation_factor;
|
||||
uniform float black_for_cycles;
|
||||
uniform float is_parameter_space;
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
#INSERT finalize_color.glsl
|
||||
#INSERT complex_functions.glsl
|
||||
|
||||
const int MAX_DEGREE = 5;
|
||||
const float CLOSE_ENOUGH = 1e-3;
|
||||
|
||||
|
||||
vec2 poly(vec2 z, vec2[MAX_DEGREE + 1] coefs){
|
||||
vec2 result = vec2(0.0);
|
||||
for(int n = 0; n < int(n_roots) + 1; n++){
|
||||
result += complex_mult(coefs[n], complex_pow(z, n));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
vec2 dpoly(vec2 z, vec2[MAX_DEGREE + 1] coefs){
|
||||
vec2 result = vec2(0.0);
|
||||
for(int n = 1; n < int(n_roots) + 1; n++){
|
||||
result += n * complex_mult(coefs[n], complex_pow(z, n - 1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
vec2 seek_root(vec2 z, vec2[MAX_DEGREE + 1] coefs, int max_steps, out float n_iters){
|
||||
float last_len;
|
||||
float curr_len;
|
||||
float threshold = CLOSE_ENOUGH;
|
||||
|
||||
for(int i = 0; i < max_steps; i++){
|
||||
last_len = curr_len;
|
||||
n_iters = float(i);
|
||||
vec2 step = complex_div(poly(z, coefs), dpoly(z, coefs));
|
||||
curr_len = length(step);
|
||||
if(curr_len < threshold){
|
||||
break;
|
||||
}
|
||||
z = z - step;
|
||||
}
|
||||
n_iters -= clamp((threshold - curr_len) / (last_len - curr_len), 0.0, 1.0);
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
vec2[MAX_DEGREE + 1] coefs = vec2[MAX_DEGREE + 1](coef0, coef1, coef2, coef3, coef4, coef5);
|
||||
vec2[MAX_DEGREE] roots = vec2[MAX_DEGREE](root0, root1, root2, root3, root4);
|
||||
vec4[MAX_DEGREE] colors = vec4[MAX_DEGREE](color0, color1, color2, color3, color4);
|
||||
|
||||
vec2 z = xyz_coords.xy;
|
||||
|
||||
if(is_parameter_space > 0){
|
||||
// In this case, pixel should correspond to one of the roots
|
||||
roots[2] = xyz_coords.xy;
|
||||
vec2 r0 = roots[0];
|
||||
vec2 r1 = roots[1];
|
||||
vec2 r2 = roots[2];
|
||||
|
||||
// It is assumed that the polynomial is cubid...
|
||||
coefs[0] = -complex_mult(complex_mult(r0, r1), r2);
|
||||
coefs[1] = complex_mult(r0, r1) + complex_mult(r0, r2) + complex_mult(r1, r2);
|
||||
coefs[2] = -(r0 + r1 + r2);
|
||||
coefs[3] = vec2(1.0, 0.0);
|
||||
|
||||
// Seed value is always center of the roots
|
||||
z = -coefs[2] / 3.0;
|
||||
}
|
||||
|
||||
float n_iters;
|
||||
vec2 found_root = seek_root(z, coefs, int(n_steps), n_iters);
|
||||
|
||||
vec4 color = vec4(0.0);
|
||||
float min_dist = 1e10;
|
||||
float dist;
|
||||
for(int i = 0; i < int(n_roots); i++){
|
||||
dist = distance(roots[i], found_root);
|
||||
if(dist < min_dist){
|
||||
min_dist = dist;
|
||||
color = colors[i];
|
||||
}
|
||||
}
|
||||
color *= 1.0 + (0.01 * saturation_factor) * (n_iters - 5 * saturation_factor);
|
||||
|
||||
if(black_for_cycles > 0 && min_dist > CLOSE_ENOUGH){
|
||||
color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
if(julia_highlight > 0.0){
|
||||
float radius = julia_highlight;
|
||||
vec2[4] samples = vec2[4](
|
||||
z + vec2(radius, 0.0),
|
||||
z + vec2(-radius, 0.0),
|
||||
z + vec2(0.0, radius),
|
||||
z + vec2(0.0, -radius)
|
||||
);
|
||||
for(int i = 0; i < 4; i++){
|
||||
for(int j = 0; j < n_steps; j++){
|
||||
vec2 z = samples[i];
|
||||
z = z - complex_div(poly(z, coefs), dpoly(z, coefs));
|
||||
samples[i] = z;
|
||||
}
|
||||
}
|
||||
float max_dist = 0.0;
|
||||
for(int i = 0; i < 4; i++){
|
||||
max_dist = max(max_dist, distance(samples[i], samples[(i + 1) % 4]));
|
||||
}
|
||||
color *= 1.0 * smoothstep(0, 0.1, max_dist);
|
||||
}
|
||||
|
||||
frag_color = finalize_color(
|
||||
color,
|
||||
xyz_coords,
|
||||
vec3(0.0, 0.0, 1.0),
|
||||
light_source_position,
|
||||
gloss,
|
||||
shadow
|
||||
);
|
||||
}
|
||||
17
manimlib/shaders/newton_fractal/vert.glsl
Normal file
17
manimlib/shaders/newton_fractal/vert.glsl
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
out vec3 xyz_coords;
|
||||
|
||||
uniform float scale_factor;
|
||||
uniform vec3 offset;
|
||||
|
||||
#INSERT position_point_into_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
|
||||
void main(){
|
||||
xyz_coords = (point - offset) / scale_factor;
|
||||
gl_Position = get_gl_Position(position_point_into_frame(point));
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import numpy as np
|
|||
from manimlib.utils.simple_functions import choose
|
||||
from manimlib.utils.space_ops import find_intersection
|
||||
from manimlib.utils.space_ops import cross2d
|
||||
from manimlib.utils.space_ops import midpoint
|
||||
from manimlib.logger import log
|
||||
|
||||
CLOSED_THRESHOLD = 0.001
|
||||
|
|
@ -131,6 +132,8 @@ def get_smooth_quadratic_bezier_handle_points(points):
|
|||
another that would produce a parabola passing through P0, call it smooth_to_left,
|
||||
and use the midpoint between the two.
|
||||
"""
|
||||
if len(points) == 2:
|
||||
return midpoint(*points)
|
||||
smooth_to_right, smooth_to_left = [
|
||||
0.25 * ps[0:-2] + ps[1:-1] - 0.25 * ps[2:]
|
||||
for ps in (points, points[::-1])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import numpy as np
|
||||
import itertools as it
|
||||
import operator as op
|
||||
from functools import reduce
|
||||
import math
|
||||
|
|
@ -14,7 +13,7 @@ from manimlib.utils.iterables import adjacent_pairs
|
|||
|
||||
|
||||
def get_norm(vect):
|
||||
return sum([x**2 for x in vect])**0.5
|
||||
return sum((x**2 for x in vect))**0.5
|
||||
|
||||
|
||||
# Quaternions
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import numpy as np
|
||||
import moderngl_window as mglw
|
||||
from moderngl_window.context.pyglet.window import Window as PygletWindow
|
||||
from moderngl_window.timers.clock import Timer
|
||||
|
|
@ -59,7 +60,17 @@ class Window(PygletWindow):
|
|||
|
||||
# Delegate event handling to scene
|
||||
def pixel_coords_to_space_coords(self, px, py, relative=False):
|
||||
return self.scene.camera.pixel_coords_to_space_coords(px, py, relative)
|
||||
pw, ph = self.size
|
||||
fw, fh = self.scene.camera.get_frame_shape()
|
||||
fc = self.scene.camera.get_frame_center()
|
||||
if relative:
|
||||
return np.array([px / pw, py / ph, 0])
|
||||
else:
|
||||
return np.array([
|
||||
fc[0] + px * fw / pw - fw / 2,
|
||||
fc[1] + py * fh / ph - fh / 2,
|
||||
0
|
||||
])
|
||||
|
||||
def on_mouse_motion(self, x, y, dx, dy):
|
||||
super().on_mouse_motion(x, y, dx, dy)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue