mirror of
https://github.com/3b1b/manim.git
synced 2025-09-19 04:41:56 +00:00
Merge pull request #2161 from 3b1b/video-work
Adjustments to fill antialiasing
This commit is contained in:
commit
12d39ef37c
21 changed files with 70 additions and 55 deletions
|
@ -49,7 +49,7 @@ class Animation(object):
|
|||
self.lag_ratio = lag_ratio
|
||||
self.suspend_mobject_updating = suspend_mobject_updating
|
||||
|
||||
assert(isinstance(mobject, Mobject))
|
||||
assert isinstance(mobject, Mobject)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
|
|
@ -133,7 +133,7 @@ class Succession(AnimationGroup):
|
|||
super().__init__(*animations, lag_ratio=lag_ratio, **kwargs)
|
||||
|
||||
def begin(self) -> None:
|
||||
assert(len(self.animations) > 0)
|
||||
assert len(self.animations) > 0
|
||||
self.active_animation = self.animations[0]
|
||||
self.active_animation.begin()
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ class DrawBorderThenFill(Animation):
|
|||
fill_animation_config: dict = {},
|
||||
**kwargs
|
||||
):
|
||||
assert(isinstance(vmobject, VMobject))
|
||||
assert isinstance(vmobject, VMobject)
|
||||
self.sm_to_index = {hash(sm): 0 for sm in vmobject.get_family()}
|
||||
self.stroke_width = stroke_width
|
||||
self.stroke_color = stroke_color
|
||||
|
|
|
@ -19,7 +19,7 @@ class ChangingDecimal(Animation):
|
|||
suspend_mobject_updating: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
assert(isinstance(decimal_mob, DecimalNumber))
|
||||
assert isinstance(decimal_mob, DecimalNumber)
|
||||
self.number_update_func = number_update_func
|
||||
super().__init__(
|
||||
decimal_mob,
|
||||
|
|
|
@ -179,7 +179,7 @@ class ApplyMethod(Transform):
|
|||
"Whoops, looks like you accidentally invoked "
|
||||
"the method you want to animate"
|
||||
)
|
||||
assert(isinstance(method.__self__, Mobject))
|
||||
assert isinstance(method.__self__, Mobject)
|
||||
|
||||
def create_target(self) -> Mobject:
|
||||
method = self.method
|
||||
|
|
|
@ -99,7 +99,7 @@ class Camera(object):
|
|||
self.light_source = Point(self.light_source_position)
|
||||
|
||||
def use_window_fbo(self, use: bool = True):
|
||||
assert(self.window is not None)
|
||||
assert self.window is not None
|
||||
if use:
|
||||
self.fbo = self.window_fbo
|
||||
else:
|
||||
|
|
|
@ -20,12 +20,12 @@ class EventDispatcher(object):
|
|||
self.draggable_object_listners: list[EventListener] = []
|
||||
|
||||
def add_listner(self, event_listner: EventListener):
|
||||
assert(isinstance(event_listner, EventListener))
|
||||
assert isinstance(event_listner, EventListener)
|
||||
self.event_listners[event_listner.event_type].append(event_listner)
|
||||
return self
|
||||
|
||||
def remove_listner(self, event_listner: EventListener):
|
||||
assert(isinstance(event_listner, EventListener))
|
||||
assert isinstance(event_listner, EventListener)
|
||||
try:
|
||||
while event_listner in self.event_listners[event_listner.event_type]:
|
||||
self.event_listners[event_listner.event_type].remove(event_listner)
|
||||
|
@ -56,7 +56,7 @@ class EventDispatcher(object):
|
|||
|
||||
if event_type == EventType.MouseDragEvent:
|
||||
for listner in self.draggable_object_listners:
|
||||
assert(isinstance(listner, EventListener))
|
||||
assert isinstance(listner, EventListener)
|
||||
propagate_event = listner.callback(listner.mobject, event_data)
|
||||
if propagate_event is not None and propagate_event is False:
|
||||
return propagate_event
|
||||
|
|
|
@ -642,6 +642,7 @@ class Arrow(Line):
|
|||
end: Vect3 | Mobject,
|
||||
stroke_color: ManimColor = GREY_A,
|
||||
stroke_width: float = 5,
|
||||
flat_stroke: bool = True,
|
||||
buff: float = 0.25,
|
||||
tip_width_ratio: float = 5,
|
||||
tip_len_to_width: float = 0.0075,
|
||||
|
@ -659,6 +660,7 @@ class Arrow(Line):
|
|||
start, end,
|
||||
stroke_color=stroke_color,
|
||||
stroke_width=stroke_width,
|
||||
flat_stroke=flat_stroke,
|
||||
buff=buff,
|
||||
**kwargs
|
||||
)
|
||||
|
|
|
@ -37,7 +37,7 @@ class MotionMobject(Mobject):
|
|||
"""
|
||||
def __init__(self, mobject: Mobject, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
assert(isinstance(mobject, Mobject))
|
||||
assert isinstance(mobject, Mobject)
|
||||
self.mobject = mobject
|
||||
self.mobject.add_mouse_drag_listner(self.mob_on_mouse_drag)
|
||||
# To avoid locking it as static mobject
|
||||
|
@ -58,7 +58,7 @@ class Button(Mobject):
|
|||
|
||||
def __init__(self, mobject: Mobject, on_click: Callable[[Mobject]], **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
assert(isinstance(mobject, Mobject))
|
||||
assert isinstance(mobject, Mobject)
|
||||
self.on_click = on_click
|
||||
self.mobject = mobject
|
||||
self.mobject.add_mouse_press_listner(self.mob_on_mouse_press)
|
||||
|
@ -119,7 +119,7 @@ class EnableDisableButton(ControlMobject):
|
|||
self.add_mouse_press_listner(self.on_mouse_press)
|
||||
|
||||
def assert_value(self, value: bool) -> None:
|
||||
assert(isinstance(value, bool))
|
||||
assert isinstance(value, bool)
|
||||
|
||||
def set_value_anim(self, value: bool) -> None:
|
||||
if value:
|
||||
|
@ -168,7 +168,7 @@ class Checkbox(ControlMobject):
|
|||
self.add_mouse_press_listner(self.on_mouse_press)
|
||||
|
||||
def assert_value(self, value: bool) -> None:
|
||||
assert(isinstance(value, bool))
|
||||
assert isinstance(value, bool)
|
||||
|
||||
def toggle_value(self) -> None:
|
||||
super().set_value(not self.get_value())
|
||||
|
@ -252,7 +252,7 @@ class LinearNumberSlider(ControlMobject):
|
|||
super().__init__(value, self.bar, self.slider, self.slider_axis, **kwargs)
|
||||
|
||||
def assert_value(self, value: float) -> None:
|
||||
assert(self.min_value <= value <= self.max_value)
|
||||
assert self.min_value <= value <= self.max_value
|
||||
|
||||
def set_value_anim(self, value: float) -> None:
|
||||
prop = (value - self.min_value) / (self.max_value - self.min_value)
|
||||
|
@ -351,7 +351,7 @@ class ColorSliders(Group):
|
|||
grid.move_to(self.selected_color_box)
|
||||
|
||||
for idx, square in enumerate(grid):
|
||||
assert(isinstance(square, Square))
|
||||
assert isinstance(square, Square)
|
||||
square.set_stroke(width=0.0, opacity=0.0)
|
||||
square.set_fill(colors[idx % len(colors)], 1.0)
|
||||
|
||||
|
|
|
@ -110,7 +110,6 @@ class Mobject(object):
|
|||
self.shader_code_replacements: dict[str, str] = dict()
|
||||
|
||||
self.init_data()
|
||||
self._data_defaults = np.ones(1, dtype=self.data.dtype)
|
||||
self.init_uniforms()
|
||||
self.init_updaters()
|
||||
self.init_event_listners()
|
||||
|
@ -126,15 +125,16 @@ class Mobject(object):
|
|||
return self.__class__.__name__
|
||||
|
||||
def __add__(self, other: Mobject) -> Mobject:
|
||||
assert(isinstance(other, Mobject))
|
||||
assert isinstance(other, Mobject)
|
||||
return self.get_group_class()(self, other)
|
||||
|
||||
def __mul__(self, other: int) -> Mobject:
|
||||
assert(isinstance(other, int))
|
||||
assert isinstance(other, int)
|
||||
return self.replicate(other)
|
||||
|
||||
def init_data(self, length: int = 0):
|
||||
self.data = np.zeros(length, dtype=self.shader_dtype)
|
||||
self._data_defaults = np.ones(1, dtype=self.data.dtype)
|
||||
|
||||
def init_uniforms(self):
|
||||
self.uniforms: UniformDict = {
|
||||
|
@ -228,7 +228,7 @@ class Mobject(object):
|
|||
# Only these methods should directly affect points
|
||||
@affects_data
|
||||
def set_data(self, data: np.ndarray) -> Self:
|
||||
assert(data.dtype == self.data.dtype)
|
||||
assert data.dtype == self.data.dtype
|
||||
self.resize_points(len(data))
|
||||
self.data[:] = data
|
||||
return self
|
||||
|
|
|
@ -18,9 +18,9 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
def assert_is_mobject_method(method):
|
||||
assert(inspect.ismethod(method))
|
||||
assert inspect.ismethod(method)
|
||||
mobject = method.__self__
|
||||
assert(isinstance(mobject, Mobject))
|
||||
assert isinstance(mobject, Mobject)
|
||||
|
||||
|
||||
def always(method, *args, **kwargs):
|
||||
|
|
|
@ -30,6 +30,7 @@ class DecimalNumber(VMobject):
|
|||
color: ManimColor = WHITE,
|
||||
stroke_width: float = 0,
|
||||
fill_opacity: float = 1.0,
|
||||
fill_border_width: float = 0.5,
|
||||
num_decimal_places: int = 2,
|
||||
include_sign: bool = False,
|
||||
group_with_commas: bool = True,
|
||||
|
@ -57,6 +58,7 @@ class DecimalNumber(VMobject):
|
|||
color=color,
|
||||
stroke_width=stroke_width,
|
||||
fill_opacity=fill_opacity,
|
||||
fill_border_width=fill_border_width,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ class SampleSpace(Rectangle):
|
|||
direction: np.ndarray = LEFT,
|
||||
**kwargs
|
||||
) -> VGroup:
|
||||
assert(hasattr(self, "horizontal_parts"))
|
||||
assert hasattr(self, "horizontal_parts")
|
||||
parts = self.horizontal_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, direction, **kwargs)
|
||||
|
||||
|
@ -162,7 +162,7 @@ class SampleSpace(Rectangle):
|
|||
labels: str,
|
||||
**kwargs
|
||||
) -> VGroup:
|
||||
assert(hasattr(self, "vertical_parts"))
|
||||
assert hasattr(self, "vertical_parts")
|
||||
parts = self.vertical_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, UP, **kwargs)
|
||||
|
||||
|
@ -171,7 +171,7 @@ class SampleSpace(Rectangle):
|
|||
labels: str,
|
||||
**kwargs
|
||||
) -> VGroup:
|
||||
assert(hasattr(self, "vertical_parts"))
|
||||
assert hasattr(self, "vertical_parts")
|
||||
parts = self.vertical_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, DOWN, **kwargs)
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ class Surface(Mobject):
|
|||
b: float,
|
||||
axis: int | None = None
|
||||
) -> Self:
|
||||
assert(isinstance(smobject, Surface))
|
||||
assert isinstance(smobject, Surface)
|
||||
if axis is None:
|
||||
axis = self.prefered_creation_axis
|
||||
if a <= 0 and b >= 1:
|
||||
|
|
|
@ -4,6 +4,7 @@ from functools import wraps
|
|||
|
||||
import moderngl
|
||||
import numpy as np
|
||||
import operator as op
|
||||
|
||||
from manimlib.constants import GREY_A, GREY_C, GREY_E
|
||||
from manimlib.constants import BLACK
|
||||
|
@ -101,7 +102,7 @@ class VMobject(Mobject):
|
|||
use_simple_quadratic_approx: bool = False,
|
||||
# Measured in pixel widths
|
||||
anti_alias_width: float = 1.5,
|
||||
fill_border_width: float = 0.5,
|
||||
fill_border_width: float = 0.0,
|
||||
use_winding_fill: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
|
@ -190,10 +191,13 @@ class VMobject(Mobject):
|
|||
recurse: bool = True
|
||||
) -> Self:
|
||||
self.set_rgba_array_by_color(color, opacity, 'fill_rgba', recurse)
|
||||
if border_width is None:
|
||||
border_width = 0 if self.get_fill_opacity() < 1 else 0.5
|
||||
for mob in self.get_family(recurse):
|
||||
mob.data["fill_border_width"] = border_width
|
||||
if opacity is not None and 0 < opacity < 1 and border_width is None:
|
||||
border_width = 0
|
||||
if border_width is not None:
|
||||
self.border_width = border_width
|
||||
for mob in self.get_family(recurse):
|
||||
data = mob.data if mob.has_points() > 0 else mob._data_defaults
|
||||
data["fill_border_width"] = border_width
|
||||
self.note_changed_fill()
|
||||
return self
|
||||
|
||||
|
@ -243,6 +247,7 @@ class VMobject(Mobject):
|
|||
fill_color: ManimColor | Iterable[ManimColor] | None = None,
|
||||
fill_opacity: float | Iterable[float] | None = None,
|
||||
fill_rgba: Vect4 | None = None,
|
||||
fill_border_width: float | None = None,
|
||||
stroke_color: ManimColor | Iterable[ManimColor] | None = None,
|
||||
stroke_opacity: float | Iterable[float] | None = None,
|
||||
stroke_rgba: Vect4 | None = None,
|
||||
|
@ -259,6 +264,7 @@ class VMobject(Mobject):
|
|||
mob.set_fill(
|
||||
color=fill_color,
|
||||
opacity=fill_opacity,
|
||||
border_width=fill_border_width,
|
||||
recurse=False
|
||||
)
|
||||
|
||||
|
@ -290,6 +296,7 @@ class VMobject(Mobject):
|
|||
data = self.data if self.get_num_points() > 0 else self._data_defaults
|
||||
return {
|
||||
"fill_rgba": data['fill_rgba'].copy(),
|
||||
"fill_border_width": data['fill_border_width'].copy(),
|
||||
"stroke_rgba": data['stroke_rgba'].copy(),
|
||||
"stroke_width": data['stroke_width'].copy(),
|
||||
"stroke_background": self.stroke_behind,
|
||||
|
@ -439,23 +446,19 @@ class VMobject(Mobject):
|
|||
def apply_depth_test(
|
||||
self,
|
||||
anti_alias_width: float = 0,
|
||||
fill_border_width: float = 0,
|
||||
recurse: bool = True
|
||||
) -> Self:
|
||||
super().apply_depth_test(recurse)
|
||||
self.set_anti_alias_width(anti_alias_width)
|
||||
self.set_fill(border_width=fill_border_width)
|
||||
return self
|
||||
|
||||
def deactivate_depth_test(
|
||||
self,
|
||||
anti_alias_width: float = 1.0,
|
||||
fill_border_width: float = 0.5,
|
||||
recurse: bool = True
|
||||
) -> Self:
|
||||
super().deactivate_depth_test(recurse)
|
||||
self.set_anti_alias_width(anti_alias_width)
|
||||
self.set_fill(border_width=fill_border_width)
|
||||
return self
|
||||
|
||||
@Mobject.affects_family_data
|
||||
|
@ -475,7 +478,7 @@ class VMobject(Mobject):
|
|||
if len(anchors) == 0:
|
||||
self.clear_points()
|
||||
return self
|
||||
assert(len(anchors) == len(handles) + 1)
|
||||
assert len(anchors) == len(handles) + 1
|
||||
points = resize_array(self.get_points(), 2 * len(anchors) - 1)
|
||||
points[0::2] = anchors
|
||||
points[1::2] = handles
|
||||
|
@ -1059,7 +1062,7 @@ class VMobject(Mobject):
|
|||
return self
|
||||
|
||||
def pointwise_become_partial(self, vmobject: VMobject, a: float, b: float) -> Self:
|
||||
assert(isinstance(vmobject, VMobject))
|
||||
assert isinstance(vmobject, VMobject)
|
||||
vm_points = vmobject.get_points()
|
||||
self.data["joint_product"] = vmobject.data["joint_product"]
|
||||
if a <= 0 and b >= 1:
|
||||
|
@ -1261,7 +1264,7 @@ class VMobject(Mobject):
|
|||
return wrapper
|
||||
|
||||
def set_points(self, points: Vect3Array, refresh_joints: bool = True) -> Self:
|
||||
assert(len(points) == 0 or len(points) % 2 == 1)
|
||||
assert len(points) == 0 or len(points) % 2 == 1
|
||||
super().set_points(points)
|
||||
self.refresh_triangulation()
|
||||
if refresh_joints:
|
||||
|
@ -1271,7 +1274,7 @@ class VMobject(Mobject):
|
|||
|
||||
@triggers_refreshed_triangulation
|
||||
def append_points(self, points: Vect3Array) -> Self:
|
||||
assert(len(points) % 2 == 0)
|
||||
assert len(points) % 2 == 0
|
||||
super().append_points(points)
|
||||
return self
|
||||
|
||||
|
@ -1393,7 +1396,12 @@ class VMobject(Mobject):
|
|||
else:
|
||||
fill_datas.append(submob.data[fill_names])
|
||||
fill_indices.append(submob.get_triangulation())
|
||||
if (not submob._has_stroke) or submob.stroke_behind:
|
||||
|
||||
draw_border_width = op.and_(
|
||||
submob.data['fill_border_width'][0] > 0,
|
||||
(not submob._has_stroke) or submob.stroke_behind,
|
||||
)
|
||||
if draw_border_width:
|
||||
# Add fill border
|
||||
submob.get_joint_products()
|
||||
names = list(stroke_names)
|
||||
|
|
|
@ -333,7 +333,7 @@ class Scene(object):
|
|||
|
||||
self.camera.capture(*self.render_groups)
|
||||
|
||||
if self.window:
|
||||
if self.window and not self.skip_animations:
|
||||
vt = self.time - self.virtual_animation_start_time
|
||||
rt = time.time() - self.real_animation_start_time
|
||||
time.sleep(max(vt - rt, 0))
|
||||
|
@ -868,7 +868,7 @@ class Scene(object):
|
|||
point: Vect3,
|
||||
d_point: Vect3
|
||||
) -> None:
|
||||
assert(self.window is not None)
|
||||
assert self.window is not None
|
||||
self.mouse_point.move_to(point)
|
||||
|
||||
event_data = {"point": point, "d_point": d_point}
|
||||
|
|
|
@ -219,7 +219,7 @@ class ShaderWrapper(object):
|
|||
self.set_ctx_clip_plane(self.use_clip_plane())
|
||||
|
||||
def render(self):
|
||||
assert(self.vao is not None)
|
||||
assert self.vao is not None
|
||||
self.vao.render()
|
||||
|
||||
def update_program_uniforms(self, camera_uniforms: UniformDict):
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
#version 330
|
||||
|
||||
// Value between -1 and 1
|
||||
in float scaled_signed_dist_to_curve;
|
||||
in float anti_alias_prop;
|
||||
in float dist_to_curve;
|
||||
in float half_stroke_width;
|
||||
in float half_anti_alias_width;
|
||||
in vec4 color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
if(anti_alias_prop < 0) discard;
|
||||
if(half_stroke_width == 0) discard;
|
||||
frag_color = color;
|
||||
|
||||
// sdf for the region around the curve we wish to color.
|
||||
float signed_dist_to_region = abs(scaled_signed_dist_to_curve) - 1.0;
|
||||
frag_color.a *= smoothstep(0, -anti_alias_prop, signed_dist_to_region);
|
||||
float signed_dist_to_region = abs(dist_to_curve) - half_stroke_width;
|
||||
frag_color.a *= smoothstep(half_anti_alias_width, -half_anti_alias_width, signed_dist_to_region);
|
||||
}
|
|
@ -16,8 +16,9 @@ in float v_stroke_width[3];
|
|||
in vec4 v_color[3];
|
||||
|
||||
out vec4 color;
|
||||
out float anti_alias_prop;
|
||||
out float scaled_signed_dist_to_curve;
|
||||
out float dist_to_curve;
|
||||
out float half_stroke_width;
|
||||
out float half_anti_alias_width;
|
||||
|
||||
// Codes for joint types
|
||||
const int NO_JOINT = 0;
|
||||
|
@ -167,8 +168,8 @@ void emit_point_with_width(
|
|||
|
||||
// Set styling
|
||||
color = finalize_color(joint_color, point, unit_normal);
|
||||
float aaw = anti_alias_width * pixel_size;
|
||||
anti_alias_prop = (width == 0) ? -1.0 : 2 * aaw / (width + 2 * aaw);
|
||||
half_anti_alias_width = 0.5 * anti_alias_width * pixel_size;
|
||||
half_stroke_width = 0.5 * width;
|
||||
|
||||
// Figure out the step from the point to the corners of the
|
||||
// triangle strip around the polyline
|
||||
|
@ -178,8 +179,8 @@ void emit_point_with_width(
|
|||
// The frag shader will receive a value from -1 to 1,
|
||||
// reflecting where in the stroke that point is
|
||||
for (int sign = -1; sign <= 1; sign += 2){
|
||||
scaled_signed_dist_to_curve = sign;
|
||||
emit_gl_Position(point + 0.5 * (width + aaw) * sign * step);
|
||||
dist_to_curve = sign * (half_stroke_width + half_anti_alias_width);
|
||||
emit_gl_Position(point + dist_to_curve * step);
|
||||
EmitVertex();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ out vec4 v_joint_product;
|
|||
out float v_stroke_width;
|
||||
out vec4 v_color;
|
||||
|
||||
const float STROKE_WIDTH_CONVERSION = 0.015;
|
||||
const float STROKE_WIDTH_CONVERSION = 0.01;
|
||||
|
||||
void main(){
|
||||
verts = point;
|
||||
|
|
|
@ -148,7 +148,8 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]:
|
|||
along with the rgb value which is meant to be discarded.
|
||||
"""
|
||||
cam_config = get_configuration(parse_cli())['camera_config']
|
||||
size = (cam_config['pixel_width'], cam_config['pixel_height'])
|
||||
# Double the size so as to effectively to 4x multi-sample antialiasing
|
||||
size = (2 * cam_config['pixel_width'], 2 * cam_config['pixel_height'])
|
||||
|
||||
# Important to make sure dtype is floating point (not fixed point)
|
||||
# so that alpha values can be negative and are not clipped
|
||||
|
|
Loading…
Add table
Reference in a new issue