mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
Video work (#2318)
* Only use -no-pdf for xelatex rendering * Instead of tracking du and dv points on surface, track points off the surface in the normal direction This means that surface shading will not necessarily work well for arbitrary transformations of the surface. But the existing solution was flimsy anyway, and caused annoying issues with singularity points. * Have density of anchor points on arcs depend on arc length * Allow for specifying true normals and orientation of Sphere * Change miter threshold on stroke shader * Add get_start_and_end to DashedLine * Add min_total_width option to DecimalNumber * Have BackgroundRectangle.set_style absorb (and ignore) added configuration Note, this feels suboptimal * Add LineBrace * Update font_size adjustment in Tex
This commit is contained in:
parent
7a7bf83f11
commit
db421e3981
10 changed files with 65 additions and 41 deletions
|
@ -208,12 +208,16 @@ class Arc(TipableVMobject):
|
|||
start_angle: float = 0,
|
||||
angle: float = TAU / 4,
|
||||
radius: float = 1.0,
|
||||
n_components: int = 8,
|
||||
n_components: Optional[int] = None,
|
||||
arc_center: Vect3 = ORIGIN,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if n_components is None:
|
||||
# 16 components for a full circle
|
||||
n_components = int(15 * (abs(angle) / TAU)) + 1
|
||||
|
||||
self.set_points(quadratic_bezier_points_for_arc(angle, n_components))
|
||||
self.rotate(start_angle, about_point=ORIGIN)
|
||||
self.scale(radius, about_point=ORIGIN)
|
||||
|
@ -597,6 +601,9 @@ class DashedLine(Line):
|
|||
else:
|
||||
return Line.get_end(self)
|
||||
|
||||
def get_start_and_end(self) -> Tuple[Vect3, Vect3]:
|
||||
return self.get_start(), self.get_end()
|
||||
|
||||
def get_first_handle(self) -> Vect3:
|
||||
return self.submobjects[0].get_points()[1]
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ class DecimalNumber(VMobject):
|
|||
fill_opacity: float = 1.0,
|
||||
fill_border_width: float = 0.5,
|
||||
num_decimal_places: int = 2,
|
||||
min_total_width: Optional[int] = 0,
|
||||
include_sign: bool = False,
|
||||
group_with_commas: bool = True,
|
||||
digit_buff_per_font_unit: float = 0.001,
|
||||
|
@ -54,6 +55,7 @@ class DecimalNumber(VMobject):
|
|||
self.num_decimal_places = num_decimal_places
|
||||
self.include_sign = include_sign
|
||||
self.group_with_commas = group_with_commas
|
||||
self.min_total_width = min_total_width
|
||||
self.digit_buff_per_font_unit = digit_buff_per_font_unit
|
||||
self.show_ellipsis = show_ellipsis
|
||||
self.unit = unit
|
||||
|
@ -167,6 +169,7 @@ class DecimalNumber(VMobject):
|
|||
"include_sign",
|
||||
"group_with_commas",
|
||||
"num_decimal_places",
|
||||
"min_total_width",
|
||||
]
|
||||
])
|
||||
config.update(kwargs)
|
||||
|
@ -176,6 +179,7 @@ class DecimalNumber(VMobject):
|
|||
config.get("field_name", ""),
|
||||
":",
|
||||
"+" if config["include_sign"] else "",
|
||||
"0" + str(config.get("min_total_width", "")) if config.get("min_total_width") else "",
|
||||
"," if config["group_with_commas"] else "",
|
||||
f".{ndp}f" if ndp > 0 else "d",
|
||||
"}",
|
||||
|
|
|
@ -79,7 +79,8 @@ class BackgroundRectangle(SurroundingRectangle):
|
|||
stroke_width: float | None = None,
|
||||
fill_color: ManimColor | None = None,
|
||||
fill_opacity: float | None = None,
|
||||
family: bool = True
|
||||
family: bool = True,
|
||||
**kwargs
|
||||
) -> Self:
|
||||
# Unchangeable style, except for fill_opacity
|
||||
VMobject.set_style(
|
||||
|
|
|
@ -6,7 +6,7 @@ import copy
|
|||
import numpy as np
|
||||
|
||||
from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFF, SMALL_BUFF
|
||||
from manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, DL, DR, UL
|
||||
from manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, DL, DR, UL, UP
|
||||
from manimlib.constants import PI
|
||||
from manimlib.animation.composition import AnimationGroup
|
||||
from manimlib.animation.fading import FadeIn
|
||||
|
@ -174,3 +174,12 @@ class BraceLabel(VMobject):
|
|||
|
||||
class BraceText(BraceLabel):
|
||||
label_constructor: type = TexText
|
||||
|
||||
|
||||
class LineBrace(Brace):
|
||||
def __init__(self, line: Line, direction=UP, **kwargs):
|
||||
angle = line.get_angle()
|
||||
line.rotate(-angle)
|
||||
super().__init__(line, direction, **kwargs)
|
||||
line.rotate(angle)
|
||||
self.rotate(angle, about_point=line.get_center())
|
||||
|
|
|
@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
|||
from manimlib.typing import ManimColor, Span, Selector, Self
|
||||
|
||||
|
||||
SCALE_FACTOR_PER_FONT_POINT = 0.001
|
||||
TEX_MOB_SCALE_FACTOR = 0.001
|
||||
|
||||
|
||||
class Tex(StringMobject):
|
||||
|
@ -49,7 +49,6 @@ class Tex(StringMobject):
|
|||
if not tex_string.strip():
|
||||
tex_string = R"\\"
|
||||
|
||||
self.font_size = font_size
|
||||
self.tex_string = tex_string
|
||||
self.alignment = alignment
|
||||
self.template = template
|
||||
|
@ -64,13 +63,16 @@ class Tex(StringMobject):
|
|||
)
|
||||
|
||||
self.set_color_by_tex_to_color_map(self.tex_to_color_map)
|
||||
self.scale(SCALE_FACTOR_PER_FONT_POINT * font_size)
|
||||
self.scale(TEX_MOB_SCALE_FACTOR * font_size)
|
||||
|
||||
self.font_size = font_size # Important for this to go after the scale call
|
||||
|
||||
def get_svg_string_by_content(self, content: str) -> str:
|
||||
return latex_to_svg(content, self.template, self.additional_preamble, short_tex=self.tex_string)
|
||||
|
||||
def _handle_scale_side_effects(self, scale_factor: float) -> Self:
|
||||
self.font_size *= scale_factor
|
||||
if hasattr(self, "font_size"):
|
||||
self.font_size *= scale_factor
|
||||
return self
|
||||
|
||||
# Parsing
|
||||
|
|
|
@ -97,20 +97,27 @@ class Sphere(Surface):
|
|||
v_range: Tuple[float, float] = (0, PI),
|
||||
resolution: Tuple[int, int] = (101, 51),
|
||||
radius: float = 1.0,
|
||||
true_normals: bool = True,
|
||||
clockwise=False,
|
||||
**kwargs,
|
||||
):
|
||||
self.radius = radius
|
||||
self.clockwise = clockwise
|
||||
super().__init__(
|
||||
u_range=u_range,
|
||||
v_range=v_range,
|
||||
resolution=resolution,
|
||||
**kwargs
|
||||
)
|
||||
# Add bespoke normal specification to avoid issue at poles
|
||||
if true_normals:
|
||||
self.data['d_normal_point'] = self.data['point'] * ((radius + self.normal_nudge) / radius)
|
||||
|
||||
def uv_func(self, u: float, v: float) -> np.ndarray:
|
||||
sign = -1 if self.clockwise else +1
|
||||
return self.radius * np.array([
|
||||
math.cos(u) * math.sin(v),
|
||||
math.sin(u) * math.sin(v),
|
||||
math.cos(sign * u) * math.sin(v),
|
||||
math.sin(sign * u) * math.sin(v),
|
||||
-math.cos(v)
|
||||
])
|
||||
|
||||
|
|
|
@ -30,11 +30,10 @@ class Surface(Mobject):
|
|||
shader_folder: str = "surface"
|
||||
data_dtype: np.dtype = np.dtype([
|
||||
('point', np.float32, (3,)),
|
||||
('du_point', np.float32, (3,)),
|
||||
('dv_point', np.float32, (3,)),
|
||||
('d_normal_point', np.float32, (3,)),
|
||||
('rgba', np.float32, (4,)),
|
||||
])
|
||||
pointlike_data_keys = ['point', 'du_point', 'dv_point']
|
||||
pointlike_data_keys = ['point', 'd_normal_point']
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -48,9 +47,11 @@ class Surface(Mobject):
|
|||
# rows/columns of approximating squares
|
||||
resolution: Tuple[int, int] = (101, 101),
|
||||
prefered_creation_axis: int = 1,
|
||||
# For du and dv steps. Much smaller and numerical error
|
||||
# can crop up in the shaders.
|
||||
epsilon: float = 1e-4,
|
||||
# For du and dv steps.
|
||||
epsilon: float = 1e-3,
|
||||
# Step off the surface to a new point which will
|
||||
# be used to determine the normal direction
|
||||
normal_nudge: float = 1e-3,
|
||||
**kwargs
|
||||
):
|
||||
self.u_range = u_range
|
||||
|
@ -58,6 +59,7 @@ class Surface(Mobject):
|
|||
self.resolution = resolution
|
||||
self.prefered_creation_axis = prefered_creation_axis
|
||||
self.epsilon = epsilon
|
||||
self.normal_nudge = normal_nudge
|
||||
|
||||
super().__init__(
|
||||
**kwargs,
|
||||
|
@ -94,9 +96,11 @@ class Surface(Mobject):
|
|||
).reshape((nu * nv, dim))
|
||||
for grid in (uv_grid, uv_plus_du, uv_plus_dv)
|
||||
]
|
||||
crosses = cross(du_points - points, dv_points - points)
|
||||
normals = normalize_along_axis(crosses, 1)
|
||||
|
||||
self.set_points(points)
|
||||
self.data['du_point'][:] = du_points
|
||||
self.data['dv_point'][:] = dv_points
|
||||
self.data['d_normal_point'] = points + self.normal_nudge * normals
|
||||
|
||||
def uv_to_point(self, u, v):
|
||||
nu, nv = self.resolution
|
||||
|
@ -152,12 +156,8 @@ class Surface(Mobject):
|
|||
return self.triangle_indices
|
||||
|
||||
def get_unit_normals(self) -> Vect3Array:
|
||||
points = self.get_points()
|
||||
crosses = cross(
|
||||
self.data['du_point'] - points,
|
||||
self.data['dv_point'] - points,
|
||||
)
|
||||
return normalize_along_axis(crosses, 1)
|
||||
# TOOD, I could try a more resiliant way to compute this using the neighboring grid values
|
||||
return normalize_along_axis(self.data['d_normal_point'] - self.data['point'], 1)
|
||||
|
||||
@Mobject.affects_data
|
||||
def pointwise_become_partial(
|
||||
|
@ -276,8 +276,7 @@ class TexturedSurface(Surface):
|
|||
shader_folder: str = "textured_surface"
|
||||
data_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
|
||||
('point', np.float32, (3,)),
|
||||
('du_point', np.float32, (3,)),
|
||||
('dv_point', np.float32, (3,)),
|
||||
('d_normal_point', np.float32, (3,)),
|
||||
('im_coords', np.float32, (2,)),
|
||||
('opacity', np.float32, (1,)),
|
||||
]
|
||||
|
@ -321,8 +320,7 @@ class TexturedSurface(Surface):
|
|||
self.resize_points(surf.get_num_points())
|
||||
self.resolution = surf.resolution
|
||||
self.data['point'][:] = surf.data['point']
|
||||
self.data['du_point'][:] = surf.data['du_point']
|
||||
self.data['dv_point'][:] = surf.data['dv_point']
|
||||
self.data['d_normal_point'][:] = surf.data['d_normal_point']
|
||||
self.data['opacity'][:, 0] = surf.data["rgba"][:, 3]
|
||||
self.data["im_coords"] = np.array([
|
||||
[u, v]
|
||||
|
|
|
@ -33,7 +33,7 @@ const float COS_THRESHOLD = 0.999;
|
|||
// Used to determine how many lines to break the curve into
|
||||
const float POLYLINE_FACTOR = 100;
|
||||
const int MAX_STEPS = 32;
|
||||
const float MITER_COS_ANGLE_THRESHOLD = -0.9;
|
||||
const float MITER_COS_ANGLE_THRESHOLD = -0.8;
|
||||
|
||||
#INSERT emit_gl_Position.glsl
|
||||
#INSERT finalize_color.glsl
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#version 330
|
||||
|
||||
in vec3 point;
|
||||
in vec3 du_point;
|
||||
in vec3 dv_point;
|
||||
in vec3 d_normal_point;
|
||||
in vec4 rgba;
|
||||
|
||||
out vec4 v_color;
|
||||
|
@ -15,10 +14,6 @@ const float EPSILON = 1e-10;
|
|||
|
||||
void main(){
|
||||
emit_gl_Position(point);
|
||||
vec3 du = (du_point - point);
|
||||
vec3 dv = (dv_point - point);
|
||||
vec3 normal = cross(du, dv);
|
||||
float mag = length(normal);
|
||||
vec3 unit_normal = (mag < EPSILON) ? vec3(0, 0, sign(point.z)) : normal / mag;
|
||||
vec3 unit_normal = normalize(d_normal_point - point);
|
||||
v_color = finalize_color(rgba, point, unit_normal);
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
#version 330
|
||||
|
||||
in vec3 point;
|
||||
in vec3 du_point;
|
||||
in vec3 dv_point;
|
||||
in vec3 d_normal_point;
|
||||
in vec2 im_coords;
|
||||
in float opacity;
|
||||
|
||||
|
@ -11,15 +10,17 @@ out vec3 v_unit_normal;
|
|||
out vec2 v_im_coords;
|
||||
out float v_opacity;
|
||||
|
||||
uniform float is_sphere;
|
||||
uniform vec3 center;
|
||||
|
||||
#INSERT emit_gl_Position.glsl
|
||||
#INSERT get_unit_normal.glsl
|
||||
|
||||
const float EPSILON = 1e-10;
|
||||
|
||||
void main(){
|
||||
v_point = point;
|
||||
v_unit_normal = normalize(cross(
|
||||
normalize(du_point - point),
|
||||
normalize(dv_point - point)
|
||||
));
|
||||
v_unit_normal = normalize(d_normal_point - point);;
|
||||
v_im_coords = im_coords;
|
||||
v_opacity = opacity;
|
||||
emit_gl_Position(point);
|
||||
|
|
Loading…
Add table
Reference in a new issue