mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 11:57:44 +00:00
Merge pull request #1959 from 3b1b/video-work
Simplify quadratic bezier shaders
This commit is contained in:
commit
de38b56d0d
56 changed files with 1125 additions and 1254 deletions
|
|
@ -486,7 +486,7 @@ class GraphExample(Scene):
|
|||
self.wait()
|
||||
|
||||
|
||||
class TexAndNumbersExample(InteractiveScene):
|
||||
class TexAndNumbersExample(Scene):
|
||||
def construct(self):
|
||||
axes = Axes((-3, 3), (-3, 3), unit_size=1)
|
||||
axes.to_edge(DOWN)
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class Animation(object):
|
|||
self.rate_func = squish_rate_func(
|
||||
self.rate_func, start / self.run_time, end / self.run_time,
|
||||
)
|
||||
self.mobject.refresh_shader_data()
|
||||
self.mobject.set_animating_status(True)
|
||||
self.starting_mobject = self.create_starting_mobject()
|
||||
if self.suspend_mobject_updating:
|
||||
|
|
|
|||
|
|
@ -198,32 +198,33 @@ class VShowPassingFlash(Animation):
|
|||
self,
|
||||
vmobject: VMobject,
|
||||
time_width: float = 0.3,
|
||||
taper_width: float = 0.02,
|
||||
taper_width: float = 0.05,
|
||||
remover: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
self.time_width = time_width
|
||||
self.taper_width = taper_width
|
||||
super().__init__(vmobject, remover=remover, **kwargs)
|
||||
self.mobject = vmobject
|
||||
|
||||
def taper_kernel(self, x):
|
||||
if x < self.taper_width:
|
||||
return x
|
||||
elif x > 1 - self.taper_width:
|
||||
return 1.0 - x
|
||||
return 1.0
|
||||
|
||||
def begin(self) -> None:
|
||||
self.mobject.align_stroke_width_data_to_points()
|
||||
# Compute an array of stroke widths for each submobject
|
||||
# which tapers out at either end
|
||||
self.submob_to_anchor_widths = dict()
|
||||
self.submob_to_widths = dict()
|
||||
for sm in self.mobject.get_family():
|
||||
original_widths = sm.get_stroke_widths()
|
||||
anchor_widths = np.array([*original_widths[0::3], original_widths[-1]])
|
||||
|
||||
def taper_kernel(x):
|
||||
if x < self.taper_width:
|
||||
return x
|
||||
elif x > 1 - self.taper_width:
|
||||
return 1.0 - x
|
||||
return 1.0
|
||||
|
||||
taper_array = list(map(taper_kernel, np.linspace(0, 1, len(anchor_widths))))
|
||||
self.submob_to_anchor_widths[hash(sm)] = anchor_widths * taper_array
|
||||
widths = sm.get_stroke_widths()
|
||||
self.submob_to_widths[hash(sm)] = np.array([
|
||||
width * self.taper_kernel(x)
|
||||
for width, x in zip(widths, np.linspace(0, 1, len(widths)))
|
||||
])
|
||||
super().begin()
|
||||
|
||||
def interpolate_submobject(
|
||||
|
|
@ -232,26 +233,20 @@ class VShowPassingFlash(Animation):
|
|||
starting_sumobject: None,
|
||||
alpha: float
|
||||
) -> None:
|
||||
anchor_widths = self.submob_to_anchor_widths[hash(submobject)]
|
||||
widths = self.submob_to_widths[hash(submobject)]
|
||||
|
||||
# Create a gaussian such that 3 sigmas out on either side
|
||||
# will equals time_width
|
||||
tw = self.time_width
|
||||
sigma = tw / 6
|
||||
mu = interpolate(-tw / 2, 1 + tw / 2, alpha)
|
||||
xs = np.linspace(0, 1, len(widths))
|
||||
zs = (xs - mu) / sigma
|
||||
gaussian = np.exp(-0.5 * zs * zs)
|
||||
gaussian[abs(xs - mu) > 3 * sigma] = 0
|
||||
|
||||
def gauss_kernel(x):
|
||||
if abs(x - mu) > 3 * sigma:
|
||||
return 0
|
||||
z = (x - mu) / sigma
|
||||
return math.exp(-0.5 * z * z)
|
||||
submobject.set_stroke(width=widths * gaussian)
|
||||
|
||||
kernel_array = list(map(gauss_kernel, np.linspace(0, 1, len(anchor_widths))))
|
||||
scaled_widths = anchor_widths * kernel_array
|
||||
new_widths = np.zeros(submobject.get_num_points())
|
||||
new_widths[0::3] = scaled_widths[:-1]
|
||||
new_widths[2::3] = scaled_widths[1:]
|
||||
new_widths[1::3] = (new_widths[0::3] + new_widths[2::3]) / 2
|
||||
submobject.set_stroke(width=new_widths)
|
||||
|
||||
def finish(self) -> None:
|
||||
super().finish()
|
||||
|
|
|
|||
|
|
@ -40,8 +40,12 @@ class Rotating(Animation):
|
|||
)
|
||||
|
||||
def interpolate_mobject(self, alpha: float) -> None:
|
||||
for sm1, sm2 in self.get_all_families_zipped():
|
||||
sm1.set_points(sm2.get_points())
|
||||
pairs = zip(
|
||||
self.mobject.family_members_with_points(),
|
||||
self.starting_mobject.family_members_with_points(),
|
||||
)
|
||||
for sm1, sm2 in pairs:
|
||||
sm1.data["points"][:] = sm2.data["points"]
|
||||
self.mobject.rotate(
|
||||
self.rate_func(alpha) * self.angle,
|
||||
axis=self.axis,
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ class TransformMatchingParts(AnimationGroup):
|
|||
self.anim_config = dict(**kwargs)
|
||||
|
||||
# We will progressively build up a list of transforms
|
||||
# from characters in source to those in target. These
|
||||
# two lists keep track of which characters are accounted
|
||||
# from pieces in source to those in target. These
|
||||
# two lists keep track of which pieces are accounted
|
||||
# for so far
|
||||
self.source_pieces = source.family_members_with_points()
|
||||
self.target_pieces = target.family_members_with_points()
|
||||
|
|
@ -57,14 +57,18 @@ class TransformMatchingParts(AnimationGroup):
|
|||
self.add_transform(*pair)
|
||||
|
||||
# Finally, account for mismatches
|
||||
for source_char in self.source_pieces:
|
||||
for source_piece in self.source_pieces:
|
||||
if any([source_piece in anim.mobject.get_family() for anim in self.anims]):
|
||||
continue
|
||||
self.anims.append(FadeOutToPoint(
|
||||
source_char, target.get_center(),
|
||||
source_piece, target.get_center(),
|
||||
**self.anim_config
|
||||
))
|
||||
for target_char in self.target_pieces:
|
||||
for target_piece in self.target_pieces:
|
||||
if any([target_piece in anim.mobject.get_family() for anim in self.anims]):
|
||||
continue
|
||||
self.anims.append(FadeInFromPoint(
|
||||
target_char, source.get_center(),
|
||||
target_piece, source.get_center(),
|
||||
**self.anim_config
|
||||
))
|
||||
|
||||
|
|
|
|||
|
|
@ -250,6 +250,12 @@ class Camera(object):
|
|||
else:
|
||||
self.ctx.disable(moderngl.DEPTH_TEST)
|
||||
|
||||
def set_ctx_clip_distance(self, enable: bool = True) -> None:
|
||||
if enable:
|
||||
gl.glEnable(gl.GL_CLIP_DISTANCE0)
|
||||
else:
|
||||
gl.glDisable(gl.GL_CLIP_DISTANCE0)
|
||||
|
||||
def init_light_source(self) -> None:
|
||||
self.light_source = Point(self.light_source_position)
|
||||
|
||||
|
|
@ -377,6 +383,7 @@ class Camera(object):
|
|||
shader_program = render_group["prog"]
|
||||
self.set_shader_uniforms(shader_program, shader_wrapper)
|
||||
self.set_ctx_depth_test(shader_wrapper.depth_test)
|
||||
self.set_ctx_clip_distance(shader_wrapper.use_clip_plane)
|
||||
render_group["vao"].render(int(shader_wrapper.render_primitive))
|
||||
if render_group["single_use"]:
|
||||
self.release_render_group(render_group)
|
||||
|
|
@ -402,16 +409,23 @@ class Camera(object):
|
|||
shader_wrapper: ShaderWrapper,
|
||||
single_use: bool = True
|
||||
) -> dict[str, Any]:
|
||||
# Data buffers
|
||||
vbo = self.ctx.buffer(shader_wrapper.vert_data.tobytes())
|
||||
if shader_wrapper.vert_indices is None:
|
||||
# Data buffer
|
||||
vert_data = shader_wrapper.vert_data
|
||||
indices = shader_wrapper.vert_indices
|
||||
if indices is None:
|
||||
ibo = None
|
||||
elif single_use:
|
||||
ibo = self.ctx.buffer(indices)
|
||||
else:
|
||||
vert_index_data = shader_wrapper.vert_indices.astype('i4').tobytes()
|
||||
if vert_index_data:
|
||||
ibo = self.ctx.buffer(vert_index_data)
|
||||
else:
|
||||
ibo = None
|
||||
# The vao.render call is strangely longer
|
||||
# when an index buffer is used, so if the
|
||||
# mobject is not changing, meaning only its
|
||||
# uniforms are being updated, just create
|
||||
# a larger data array based on the indices
|
||||
# and don't bother with the ibo
|
||||
vert_data = vert_data[indices]
|
||||
ibo = None
|
||||
vbo = self.ctx.buffer(vert_data)
|
||||
|
||||
# Program and vertex array
|
||||
shader_program, vert_format = self.get_shader_program(shader_wrapper)
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ RADIANS: float = 1
|
|||
FFMPEG_BIN: str = "ffmpeg"
|
||||
|
||||
JOINT_TYPE_MAP: dict = {
|
||||
"auto": 0,
|
||||
"round": 1,
|
||||
"no_joint": 0,
|
||||
"auto": 1,
|
||||
"bevel": 2,
|
||||
"miter": 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,18 +60,17 @@ def get_scene_config(config):
|
|||
for key in set(scene_parameters).intersection(config.keys())
|
||||
}
|
||||
|
||||
|
||||
def compute_total_frames(scene_class, scene_config):
|
||||
"""
|
||||
When a scene is being written to file, a copy of the scene is run with
|
||||
skip_animations set to true so as to count how many frames it will require.
|
||||
This allows for a total progress bar on rendering, and also allows runtime
|
||||
errors to be exposed preemptively for long running scenes. The final frame
|
||||
is saved by default, so that one can more quickly check that the last frame
|
||||
looks as expected.
|
||||
errors to be exposed preemptively for long running scenes.
|
||||
"""
|
||||
pre_config = copy.deepcopy(scene_config)
|
||||
pre_config["file_writer_config"]["write_to_movie"] = False
|
||||
pre_config["file_writer_config"]["save_last_frame"] = True
|
||||
pre_config["file_writer_config"]["save_last_frame"] = False
|
||||
pre_config["file_writer_config"]["quiet"] = True
|
||||
pre_config["skip_animations"] = True
|
||||
pre_scene = scene_class(**pre_config)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ def _convert_vmobject_to_skia_path(vmobject: VMobject) -> pathops.Path:
|
|||
path.moveTo(*start[:2])
|
||||
for p0, p1, p2 in quads:
|
||||
path.quadTo(*p1[:2], *p2[:2])
|
||||
if vmobject.consider_points_equals(subpath[0], subpath[-1]):
|
||||
if vmobject.consider_points_equal(subpath[0], subpath[-1]):
|
||||
path.close()
|
||||
return path
|
||||
|
||||
|
|
|
|||
|
|
@ -92,10 +92,11 @@ class ImplicitFunction(VMobject):
|
|||
y_range: Tuple[float, float] = (-FRAME_Y_RADIUS, FRAME_Y_RADIUS),
|
||||
min_depth: int = 5,
|
||||
max_quads: int = 1500,
|
||||
use_smoothing: bool = True,
|
||||
use_smoothing: bool = False,
|
||||
joint_type: str = 'no_joint',
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
super().__init__(joint_type=joint_type, **kwargs)
|
||||
|
||||
p_min, p_max = (
|
||||
np.array([x_range[0], y_range[0]]),
|
||||
|
|
@ -109,7 +110,9 @@ class ImplicitFunction(VMobject):
|
|||
max_quads=max_quads,
|
||||
) # returns a list of lists of 2D points
|
||||
curves = [
|
||||
np.pad(curve, [(0, 0), (0, 1)]) for curve in curves if curve != []
|
||||
np.pad(curve, [(0, 0), (0, 1)])
|
||||
for curve in curves
|
||||
if curve != []
|
||||
] # add z coord as 0
|
||||
for curve in curves:
|
||||
self.start_new_path(curve[0])
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from manimlib.utils.simple_functions import clip
|
|||
from manimlib.utils.simple_functions import fdiv
|
||||
from manimlib.utils.space_ops import angle_between_vectors
|
||||
from manimlib.utils.space_ops import angle_of_vector
|
||||
from manimlib.utils.space_ops import cross2d
|
||||
from manimlib.utils.space_ops import compass_directions
|
||||
from manimlib.utils.space_ops import find_intersection
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
|
|
@ -225,22 +226,13 @@ class Arc(TipableVMobject):
|
|||
angle: float,
|
||||
start_angle: float = 0,
|
||||
n_components: int = 8
|
||||
) -> Vect3:
|
||||
samples = np.array([
|
||||
[np.cos(a), np.sin(a), 0]
|
||||
for a in np.linspace(
|
||||
start_angle,
|
||||
start_angle + angle,
|
||||
2 * n_components + 1,
|
||||
)
|
||||
])
|
||||
) -> Vect3Array:
|
||||
n_points = 2 * n_components + 1
|
||||
angles = np.linspace(start_angle, start_angle + angle, n_points)
|
||||
points = np.array([np.cos(angles), np.sin(angles), np.zeros(n_points)]).T
|
||||
# Adjust handles
|
||||
theta = angle / n_components
|
||||
samples[1::2] /= np.cos(theta / 2)
|
||||
|
||||
points = np.zeros((3 * n_components, 3))
|
||||
points[0::3] = samples[0:-1:2]
|
||||
points[1::3] = samples[1::2]
|
||||
points[2::3] = samples[2::2]
|
||||
points[1::2] /= np.cos(theta / 2)
|
||||
return points
|
||||
|
||||
def get_arc_center(self) -> Vect3:
|
||||
|
|
@ -416,9 +408,9 @@ class AnnularSector(VMobject):
|
|||
)
|
||||
for radius in (inner_radius, outer_radius)
|
||||
]
|
||||
self.append_points(inner_arc.get_points()[::-1]) # Reverse
|
||||
self.set_points(inner_arc.get_points()[::-1]) # Reverse
|
||||
self.add_line_to(outer_arc.get_points()[0])
|
||||
self.append_points(outer_arc.get_points())
|
||||
self.add_subpath(outer_arc.get_points())
|
||||
self.add_line_to(inner_arc.get_points()[-1])
|
||||
|
||||
|
||||
|
|
@ -456,11 +448,14 @@ class Annulus(VMobject):
|
|||
)
|
||||
|
||||
self.radius = outer_radius
|
||||
outer_circle = Circle(radius=outer_radius)
|
||||
inner_circle = Circle(radius=inner_radius)
|
||||
inner_circle.reverse_points()
|
||||
self.append_points(outer_circle.get_points())
|
||||
self.append_points(inner_circle.get_points())
|
||||
# Make sure to add enough components that triangulation doesn't fail
|
||||
kw = dict(
|
||||
n_components=int(max(8, np.ceil(TAU / math.acos(inner_radius / outer_radius))))
|
||||
)
|
||||
outer_path = outer_radius * Arc.create_quadratic_bezier_points(TAU, 0, **kw)
|
||||
inner_path = inner_radius * Arc.create_quadratic_bezier_points(-TAU, 0, **kw)
|
||||
self.add_subpath(outer_path)
|
||||
self.add_subpath(inner_path)
|
||||
self.shift(center)
|
||||
|
||||
|
||||
|
|
@ -720,6 +715,7 @@ class Arrow(Line):
|
|||
else:
|
||||
alpha = tip_len / arc_len
|
||||
self.pointwise_become_partial(self, 0, 1 - alpha)
|
||||
self.start_new_path(self.get_points()[-1])
|
||||
self.add_line_to(prev_end)
|
||||
return self
|
||||
|
||||
|
|
@ -731,12 +727,9 @@ class Arrow(Line):
|
|||
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)
|
||||
]
|
||||
if len(widths_array) > 3:
|
||||
tip_width = self.tip_width_ratio * width
|
||||
widths_array[-3:] = tip_width * np.linspace(1, 0, 3)
|
||||
self.set_stroke(width=widths_array)
|
||||
return self
|
||||
|
||||
|
|
@ -847,7 +840,7 @@ class FillArrow(Line):
|
|||
self.add_line_to(tip_width * DOWN / 2)
|
||||
self.add_line_to(points2[0])
|
||||
# Close it out
|
||||
self.append_points(points2)
|
||||
self.add_subpath(points2)
|
||||
self.add_line_to(points1[0])
|
||||
|
||||
if length > 0 and self.get_length() > 0:
|
||||
|
|
@ -860,18 +853,18 @@ class FillArrow(Line):
|
|||
axis=rotate_vector(self.get_unit_vector(), -PI / 2),
|
||||
)
|
||||
self.shift(start - self.get_start())
|
||||
self.refresh_triangulation()
|
||||
|
||||
def reset_points_around_ends(self):
|
||||
self.set_points_by_ends(
|
||||
self.get_start().copy(), self.get_end().copy(), path_arc=self.path_arc
|
||||
self.get_start().copy(),
|
||||
self.get_end().copy(),
|
||||
path_arc=self.path_arc
|
||||
)
|
||||
return self
|
||||
|
||||
def get_start(self) -> Vect3:
|
||||
nppc = self.n_points_per_curve
|
||||
points = self.get_points()
|
||||
return (points[0] + points[-nppc]) / 2
|
||||
return 0.5 * (points[0] + points[-3])
|
||||
|
||||
def get_end(self) -> Vect3:
|
||||
return self.get_points()[self.tip_index]
|
||||
|
|
@ -922,8 +915,13 @@ class CubicBezier(VMobject):
|
|||
|
||||
|
||||
class Polygon(VMobject):
|
||||
def __init__(self, *vertices: Vect3, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
def __init__(
|
||||
self,
|
||||
*vertices: Vect3,
|
||||
flat_stroke: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(flat_stroke=flat_stroke, **kwargs)
|
||||
self.set_points_as_corners([*vertices, vertices[0]])
|
||||
|
||||
def get_vertices(self) -> Vect3Array:
|
||||
|
|
@ -941,20 +939,16 @@ class Polygon(VMobject):
|
|||
vertices = self.get_vertices()
|
||||
arcs = []
|
||||
for v1, v2, v3 in adjacent_n_tuples(vertices, 3):
|
||||
vect1 = v2 - v1
|
||||
vect2 = v3 - v2
|
||||
unit_vect1 = normalize(vect1)
|
||||
unit_vect2 = normalize(vect2)
|
||||
vect1 = normalize(v2 - v1)
|
||||
vect2 = normalize(v3 - v2)
|
||||
angle = angle_between_vectors(vect1, vect2)
|
||||
# Negative radius gives concave curves
|
||||
angle *= np.sign(radius)
|
||||
# Distance between vertex and start of the arc
|
||||
cut_off_length = radius * np.tan(angle / 2)
|
||||
# Determines counterclockwise vs. clockwise
|
||||
sign = np.sign(np.cross(vect1, vect2)[2])
|
||||
# Negative radius gives concave curves
|
||||
sign = float(np.sign(radius * cross2d(vect1, vect2)))
|
||||
arc = ArcBetweenPoints(
|
||||
v2 - unit_vect1 * cut_off_length,
|
||||
v2 + unit_vect2 * cut_off_length,
|
||||
v2 - vect1 * cut_off_length,
|
||||
v2 + vect2 * cut_off_length,
|
||||
angle=sign * angle,
|
||||
n_components=2,
|
||||
)
|
||||
|
|
@ -964,20 +958,19 @@ class Polygon(VMobject):
|
|||
# To ensure that we loop through starting with last
|
||||
arcs = [arcs[-1], *arcs[:-1]]
|
||||
for arc1, arc2 in adjacent_pairs(arcs):
|
||||
self.append_points(arc1.get_points())
|
||||
line = Line(arc1.get_end(), arc2.get_start())
|
||||
# Make sure anchors are evenly distributed
|
||||
len_ratio = line.get_length() / arc1.get_arc_length()
|
||||
line.insert_n_curves(
|
||||
int(arc1.get_num_curves() * len_ratio)
|
||||
)
|
||||
self.append_points(line.get_points())
|
||||
self.add_subpath(arc1.get_points())
|
||||
self.add_line_to(arc2.get_start())
|
||||
return self
|
||||
|
||||
|
||||
class Polyline(VMobject):
|
||||
def __init__(self, *vertices: Vect3, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
def __init__(
|
||||
self,
|
||||
*vertices: Vect3,
|
||||
flat_stroke: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(flat_stroke=flat_stroke, **kwargs)
|
||||
self.set_points_as_corners(vertices)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ from manimlib.utils.color import color_gradient
|
|||
from manimlib.utils.color import color_to_rgb
|
||||
from manimlib.utils.color import get_colormap_list
|
||||
from manimlib.utils.color import rgb_to_hex
|
||||
from manimlib.utils.iterables import arrays_match
|
||||
from manimlib.utils.iterables import batch_by_property
|
||||
from manimlib.utils.iterables import list_update
|
||||
from manimlib.utils.iterables import listify
|
||||
|
|
@ -66,6 +67,7 @@ class Mobject(object):
|
|||
shader_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
|
||||
('point', np.float32, (3,)),
|
||||
]
|
||||
aligned_data_keys = ['points']
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -101,6 +103,7 @@ class Mobject(object):
|
|||
self._is_animating: bool = False
|
||||
self.saved_state = None
|
||||
self.target = None
|
||||
self.bounding_box: Vect3Array = np.zeros((3, 3))
|
||||
|
||||
self.init_data()
|
||||
self.init_uniforms()
|
||||
|
|
@ -127,12 +130,11 @@ class Mobject(object):
|
|||
def init_data(self):
|
||||
self.data: dict[str, np.ndarray] = {
|
||||
"points": np.zeros((0, 3)),
|
||||
"bounding_box": np.zeros((3, 3)),
|
||||
"rgbas": np.zeros((1, 4)),
|
||||
}
|
||||
|
||||
def init_uniforms(self):
|
||||
self.uniforms: dict[str, float] = {
|
||||
self.uniforms: dict[str, float | np.ndarray] = {
|
||||
"is_fixed_in_frame": float(self.is_fixed_in_frame),
|
||||
"gloss": self.gloss,
|
||||
"shadow": self.shadow,
|
||||
|
|
@ -170,8 +172,8 @@ class Mobject(object):
|
|||
new_length: int,
|
||||
resize_func: Callable[[np.ndarray, int], np.ndarray] = resize_array
|
||||
):
|
||||
if new_length != len(self.data["points"]):
|
||||
self.data["points"] = resize_func(self.data["points"], new_length)
|
||||
for key in self.aligned_data_keys:
|
||||
self.data[key] = resize_func(self.data[key], new_length)
|
||||
self.refresh_bounding_box()
|
||||
return self
|
||||
|
||||
|
|
@ -249,9 +251,9 @@ class Mobject(object):
|
|||
|
||||
def get_bounding_box(self) -> Vect3Array:
|
||||
if self.needs_new_bounding_box:
|
||||
self.data["bounding_box"] = self.compute_bounding_box()
|
||||
self.bounding_box[:] = self.compute_bounding_box()
|
||||
self.needs_new_bounding_box = False
|
||||
return self.data["bounding_box"]
|
||||
return self.bounding_box
|
||||
|
||||
def compute_bounding_box(self) -> Vect3Array:
|
||||
all_points = np.vstack([
|
||||
|
|
@ -826,7 +828,7 @@ class Mobject(object):
|
|||
return self._is_animating or self.has_updaters
|
||||
|
||||
def set_animating_status(self, is_animating: bool, recurse: bool = True):
|
||||
for mob in (*self.get_family(recurse), *self.get_ancestors(extended=True)):
|
||||
for mob in (*self.get_family(recurse), *self.get_ancestors()):
|
||||
mob._is_animating = is_animating
|
||||
return self
|
||||
|
||||
|
|
@ -1593,14 +1595,16 @@ class Mobject(object):
|
|||
self.align_data(mobject)
|
||||
|
||||
def align_data(self, mobject: Mobject) -> None:
|
||||
# In case any data arrays get resized when aligned to shader data
|
||||
self.refresh_shader_data()
|
||||
for mob1, mob2 in zip(self.get_family(), mobject.get_family()):
|
||||
# Separate out how points are treated so that subclasses
|
||||
# can handle that case differently if they choose
|
||||
# In case any data arrays get resized when aligned to shader data
|
||||
mob1.refresh_shader_data()
|
||||
mob2.refresh_shader_data()
|
||||
|
||||
mob1.align_points(mob2)
|
||||
for key in mob1.data.keys() & mob2.data.keys():
|
||||
if key == "points":
|
||||
# Separate out how points are treated so that subclasses
|
||||
# can handle that case differently if they choose
|
||||
continue
|
||||
arr1 = mob1.data[key]
|
||||
arr2 = mob2.data[key]
|
||||
|
|
@ -1685,10 +1689,7 @@ class Mobject(object):
|
|||
if key not in mobject1.data or key not in mobject2.data:
|
||||
continue
|
||||
|
||||
if key in ("points", "bounding_box"):
|
||||
func = path_func
|
||||
else:
|
||||
func = interpolate
|
||||
func = path_func if key == "points" else interpolate
|
||||
|
||||
self.data[key][:] = func(
|
||||
mobject1.data[key],
|
||||
|
|
@ -1701,6 +1702,9 @@ class Mobject(object):
|
|||
mobject2.uniforms[key],
|
||||
alpha
|
||||
)
|
||||
self.bounding_box[:] = path_func(
|
||||
mobject1.bounding_box, mobject2.bounding_box, alpha
|
||||
)
|
||||
return self
|
||||
|
||||
def pointwise_become_partial(self, mobject, a, b):
|
||||
|
|
@ -1732,7 +1736,7 @@ class Mobject(object):
|
|||
for sm, sm1, sm2 in zip(self.get_family(), mobject1.get_family(), mobject2.get_family()):
|
||||
keys = sm.data.keys() & sm1.data.keys() & sm2.data.keys()
|
||||
sm.lock_data(list(filter(
|
||||
lambda key: (sm1.data[key] == sm2.data[key]).all(),
|
||||
lambda key: arrays_match(sm1.data[key], sm2.data[key]),
|
||||
keys,
|
||||
)))
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -486,17 +486,29 @@ class VectorizedEarth(SVGMobject):
|
|||
|
||||
|
||||
class Piano(VGroup):
|
||||
n_white_keys = 52
|
||||
black_pattern = [0, 2, 3, 5, 6]
|
||||
white_keys_per_octave = 7
|
||||
white_key_dims = (0.15, 1.0)
|
||||
black_key_dims = (0.1, 0.66)
|
||||
key_buff = 0.02
|
||||
white_key_color = WHITE
|
||||
black_key_color = GREY_E
|
||||
total_width = 13
|
||||
def __init__(
|
||||
self,
|
||||
n_white_keys = 52,
|
||||
black_pattern = [0, 2, 3, 5, 6],
|
||||
white_keys_per_octave = 7,
|
||||
white_key_dims = (0.15, 1.0),
|
||||
black_key_dims = (0.1, 0.66),
|
||||
key_buff = 0.02,
|
||||
white_key_color = WHITE,
|
||||
black_key_color = GREY_E,
|
||||
total_width = 13,
|
||||
**kwargs
|
||||
):
|
||||
self.n_white_keys = n_white_keys
|
||||
self.black_pattern = black_pattern
|
||||
self.white_keys_per_octave = white_keys_per_octave
|
||||
self.white_key_dims = white_key_dims
|
||||
self.black_key_dims = black_key_dims
|
||||
self.key_buff = key_buff
|
||||
self.white_key_color = white_key_color
|
||||
self.black_key_color = black_key_color
|
||||
self.total_width = total_width
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.add_white_keys()
|
||||
self.add_black_keys()
|
||||
|
|
@ -563,3 +575,4 @@ class Piano3D(VGroup):
|
|||
for i, key in enumerate(self):
|
||||
if piano_2d[i] in piano_2d.black_keys:
|
||||
key.shift(black_key_shift * OUT)
|
||||
key.set_color(BLACK)
|
||||
|
|
|
|||
|
|
@ -49,10 +49,6 @@ class StringMobject(SVGMobject, ABC):
|
|||
fill_color: ManimColor = WHITE,
|
||||
stroke_color: ManimColor = WHITE,
|
||||
stroke_width: float = 0,
|
||||
path_string_config: dict = dict(
|
||||
should_subdivide_sharp_curves=True,
|
||||
should_remove_null_curves=True,
|
||||
),
|
||||
base_color: ManimColor = WHITE,
|
||||
isolate: Selector = (),
|
||||
protect: Selector = (),
|
||||
|
|
@ -73,7 +69,6 @@ class StringMobject(SVGMobject, ABC):
|
|||
stroke_color=stroke_color,
|
||||
fill_color=fill_color,
|
||||
stroke_width=stroke_width,
|
||||
path_string_config=path_string_config,
|
||||
**kwargs
|
||||
)
|
||||
self.labels = [submob.label for submob in self.submobjects]
|
||||
|
|
|
|||
|
|
@ -23,11 +23,13 @@ from manimlib.utils.simple_functions import hash_string
|
|||
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from manimlib.typing import ManimColor
|
||||
from typing import Tuple
|
||||
from manimlib.typing import ManimColor, Vect3Array
|
||||
|
||||
|
||||
|
||||
SVG_HASH_TO_MOB_MAP: dict[int, list[VMobject]] = {}
|
||||
PATH_TO_POINTS: dict[str, Tuple[Vect3Array, np.ndarray]] = {}
|
||||
|
||||
|
||||
def _convert_point_to_3d(x: float, y: float) -> np.ndarray:
|
||||
|
|
@ -305,15 +307,10 @@ class VMobjectFromSVGPath(VMobject):
|
|||
|
||||
def init_points(self) -> None:
|
||||
# After a given svg_path has been converted into points, the result
|
||||
# will be saved to a file so that future calls for the same path
|
||||
# don't need to retrace the same computation.
|
||||
# will be saved so that future calls for the same pathdon't need to
|
||||
# retrace the same computation.
|
||||
path_string = self.path_obj.d()
|
||||
path_hash = hash_string(path_string)
|
||||
points_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_points.npy")
|
||||
|
||||
if os.path.exists(points_filepath):
|
||||
self.set_points(np.load(points_filepath))
|
||||
else:
|
||||
if path_string not in PATH_TO_POINTS:
|
||||
self.handle_commands()
|
||||
if self.should_subdivide_sharp_curves:
|
||||
# For a healthy triangulation later
|
||||
|
|
@ -321,8 +318,18 @@ class VMobjectFromSVGPath(VMobject):
|
|||
if self.should_remove_null_curves:
|
||||
# Get rid of any null curves
|
||||
self.set_points(self.get_points_without_null_curves())
|
||||
# Save to a file for future use
|
||||
np.save(points_filepath, self.get_points())
|
||||
# So triangulation doesn't get messed up
|
||||
self.subdivide_intersections()
|
||||
# Save for future use
|
||||
PATH_TO_POINTS[path_string] = (
|
||||
self.get_points().copy(),
|
||||
self.get_triangulation().copy()
|
||||
)
|
||||
else:
|
||||
points, triangulation = PATH_TO_POINTS[path_string]
|
||||
self.set_points(points)
|
||||
self.triangulation = triangulation
|
||||
self.needs_new_triangulation = False
|
||||
|
||||
def handle_commands(self) -> None:
|
||||
segment_class_to_func_map = {
|
||||
|
|
@ -343,4 +350,4 @@ class VMobjectFromSVGPath(VMobject):
|
|||
|
||||
# Get rid of the side effect of trailing "Z M" commands.
|
||||
if self.has_new_path_started():
|
||||
self.resize_points(self.get_num_points() - 1)
|
||||
self.resize_points(self.get_num_points() - 2)
|
||||
|
|
|
|||
|
|
@ -417,9 +417,18 @@ class Text(MarkupText):
|
|||
# For backward compatibility
|
||||
isolate: Selector = (re.compile(r"\w+", re.U), re.compile(r"\S+", re.U)),
|
||||
use_labelled_svg: bool = True,
|
||||
path_string_config: dict = dict(
|
||||
use_simple_quadratic_approx=True,
|
||||
),
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(text, isolate=isolate, use_labelled_svg=use_labelled_svg, **kwargs)
|
||||
super().__init__(
|
||||
text,
|
||||
isolate=isolate,
|
||||
use_labelled_svg=use_labelled_svg,
|
||||
path_string_config=path_string_config,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_command_matches(string: str) -> list[re.Match]:
|
||||
|
|
|
|||
|
|
@ -36,19 +36,19 @@ class SurfaceMesh(VGroup):
|
|||
stroke_width: float = 1,
|
||||
stroke_color: ManimColor = GREY_A,
|
||||
normal_nudge: float = 1e-2,
|
||||
flat_stroke: bool = False,
|
||||
depth_test: bool = True,
|
||||
joint_type: str = 'no_joint',
|
||||
**kwargs
|
||||
):
|
||||
self.uv_surface = uv_surface
|
||||
self.resolution = resolution
|
||||
self.normal_nudge = normal_nudge
|
||||
self.flat_stroke = flat_stroke
|
||||
|
||||
super().__init__(
|
||||
stroke_color=stroke_color,
|
||||
stroke_width=stroke_width,
|
||||
depth_test=depth_test,
|
||||
joint_type=joint_type,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
|
@ -291,7 +291,7 @@ class VGroup3D(VGroup):
|
|||
gloss: float = 0.2,
|
||||
shadow: float = 0.2,
|
||||
reflectiveness: float = 0.2,
|
||||
joint_type: str = "round",
|
||||
joint_type: str = "no_joint",
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*vmobjects, **kwargs)
|
||||
|
|
@ -397,11 +397,14 @@ class Prismify(VGroup3D):
|
|||
# At the moment, this assume stright edges
|
||||
vect = depth * direction
|
||||
pieces = [vmobject.copy()]
|
||||
points = vmobject.get_points()[::vmobject.n_points_per_curve]
|
||||
points = vmobject.get_anchors()
|
||||
for p1, p2 in adjacent_pairs(points):
|
||||
wall = VMobject()
|
||||
wall.match_style(vmobject)
|
||||
wall.set_points_as_corners([p1, p2, p2 + vect, p1 + vect])
|
||||
pieces.append(wall)
|
||||
pieces.append(vmobject.copy().shift(vect).reverse_points())
|
||||
top = vmobject.copy()
|
||||
top.shift(vect)
|
||||
top.reverse_points()
|
||||
pieces.append(top)
|
||||
super().__init__(*pieces, **kwargs)
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ class PMobject(Mobject):
|
|||
):
|
||||
# TODO
|
||||
for key in self.data:
|
||||
if key == "bounding_box":
|
||||
continue
|
||||
if len(self.data[key]) != size:
|
||||
self.data[key] = resize_func(self.data[key], size)
|
||||
return self
|
||||
|
|
@ -82,8 +80,6 @@ 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
|
||||
|
||||
|
|
@ -115,8 +111,6 @@ class PMobject(Mobject):
|
|||
lower_index = int(a * pmobject.get_num_points())
|
||||
upper_index = int(b * pmobject.get_num_points())
|
||||
for key in self.data:
|
||||
if key == "bounding_box":
|
||||
continue
|
||||
self.data[key] = pmobject.data[key][lower_index:upper_index].copy()
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@ class Surface(Mobject):
|
|||
)
|
||||
self.compute_triangle_indices()
|
||||
|
||||
def init_uniforms(self):
|
||||
super().init_uniforms()
|
||||
self.uniforms["clip_plane"] = np.zeros(4)
|
||||
|
||||
def uv_func(self, u: float, v: float) -> tuple[float, float, float]:
|
||||
# To be implemented in subclasses
|
||||
return (u, v, 0.0)
|
||||
|
|
@ -190,13 +194,10 @@ class Surface(Mobject):
|
|||
|
||||
def sort_faces_back_to_front(self, vect: Vect3 = OUT):
|
||||
tri_is = self.triangle_indices
|
||||
indices = list(range(len(tri_is) // 3))
|
||||
points = self.get_points()
|
||||
|
||||
def index_dot(index):
|
||||
return np.dot(points[tri_is[3 * index]], vect)
|
||||
|
||||
indices.sort(key=index_dot)
|
||||
dots = (points[tri_is[::3]] * vect).sum(1)
|
||||
indices = np.argsort(dots)
|
||||
for k in range(3):
|
||||
tri_is[k::3] = tri_is[k::3][indices]
|
||||
return self
|
||||
|
|
@ -207,6 +208,23 @@ class Surface(Mobject):
|
|||
surface.sort_faces_back_to_front(vect)
|
||||
self.add_updater(updater)
|
||||
|
||||
def set_clip_plane(
|
||||
self,
|
||||
vect: Vect3 | None = None,
|
||||
threshold: float | None = None
|
||||
):
|
||||
if vect is not None:
|
||||
self.uniforms["clip_plane"][:3] = vect
|
||||
if threshold is not None:
|
||||
self.uniforms["clip_plane"][3] = threshold
|
||||
self.shader_wrapper.use_clip_plane = True
|
||||
return self
|
||||
|
||||
def deactivate_clip_plane(self):
|
||||
self.uniforms["clip_plane"][:] = 0
|
||||
self.shader_wrapper.use_clip_plane = False
|
||||
return self
|
||||
|
||||
# For shaders
|
||||
def get_shader_data(self) -> np.ndarray:
|
||||
s_points, du_points, dv_points = self.get_surface_points_and_nudged_points()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import reduce
|
||||
from functools import wraps
|
||||
import itertools as it
|
||||
import operator as op
|
||||
|
||||
import moderngl
|
||||
import numpy as np
|
||||
|
|
@ -23,7 +20,9 @@ from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points
|
|||
from manimlib.utils.bezier import integer_interpolate
|
||||
from manimlib.utils.bezier import interpolate
|
||||
from manimlib.utils.bezier import inverse_interpolate
|
||||
from manimlib.utils.bezier import find_intersection
|
||||
from manimlib.utils.bezier import partial_quadratic_bezier_points
|
||||
from manimlib.utils.bezier import outer_interpolate
|
||||
from manimlib.utils.color import color_gradient
|
||||
from manimlib.utils.color import rgb_to_hex
|
||||
from manimlib.utils.iterables import listify
|
||||
|
|
@ -35,7 +34,11 @@ from manimlib.utils.space_ops import cross2d
|
|||
from manimlib.utils.space_ops import earclip_triangulation
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
from manimlib.utils.space_ops import get_unit_normal
|
||||
from manimlib.utils.space_ops import line_intersects_path
|
||||
from manimlib.utils.space_ops import midpoint
|
||||
from manimlib.utils.space_ops import normalize_along_axis
|
||||
from manimlib.utils.space_ops import z_to_vector
|
||||
from manimlib.utils.simple_functions import arr_clip
|
||||
from manimlib.shader_wrapper import ShaderWrapper
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
@ -48,9 +51,8 @@ DEFAULT_STROKE_COLOR = GREY_A
|
|||
DEFAULT_FILL_COLOR = GREY_C
|
||||
|
||||
class VMobject(Mobject):
|
||||
n_points_per_curve: int = 3
|
||||
stroke_shader_folder: str = "quadratic_bezier_stroke"
|
||||
fill_shader_folder: str = "quadratic_bezier_fill"
|
||||
stroke_shader_folder: str = "quadratic_bezier_stroke"
|
||||
fill_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
|
||||
('point', np.float32, (3,)),
|
||||
('orientation', np.float32, (1,)),
|
||||
|
|
@ -59,12 +61,13 @@ class VMobject(Mobject):
|
|||
]
|
||||
stroke_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
|
||||
("point", np.float32, (3,)),
|
||||
("prev_point", np.float32, (3,)),
|
||||
("next_point", np.float32, (3,)),
|
||||
("joint_angle", np.float32, (1,)),
|
||||
("stroke_width", np.float32, (1,)),
|
||||
("color", np.float32, (4,)),
|
||||
]
|
||||
render_primitive: int = moderngl.TRIANGLES
|
||||
fill_render_primitive: int = moderngl.TRIANGLES
|
||||
stroke_render_primitive: int = moderngl.TRIANGLE_STRIP
|
||||
aligned_data_keys = ["points", "orientation", "joint_angle"]
|
||||
|
||||
pre_function_handle_to_anchor_scale_factor: float = 0.01
|
||||
make_smooth_after_applying_functions: bool = False
|
||||
|
|
@ -82,9 +85,10 @@ class VMobject(Mobject):
|
|||
draw_stroke_behind_fill: bool = False,
|
||||
background_image_file: str | None = None,
|
||||
long_lines: bool = False,
|
||||
# Could also be "bevel", "miter", "round"
|
||||
# Could also be "no_joint", "bevel", "miter"
|
||||
joint_type: str = "auto",
|
||||
flat_stroke: bool = False,
|
||||
use_simple_quadratic_approx: bool = False,
|
||||
# Measured in pixel widths
|
||||
anti_alias_width: float = 1.0,
|
||||
**kwargs
|
||||
|
|
@ -99,10 +103,13 @@ class VMobject(Mobject):
|
|||
self.long_lines = long_lines
|
||||
self.joint_type = joint_type
|
||||
self.flat_stroke = flat_stroke
|
||||
self.use_simple_quadratic_approx = use_simple_quadratic_approx
|
||||
self.anti_alias_width = anti_alias_width
|
||||
|
||||
self.needs_new_triangulation = True
|
||||
self.triangulation = np.zeros(0, dtype='i4')
|
||||
self.needs_new_joint_angles = True
|
||||
self.outer_vert_indices = np.zeros(0, dtype='i4')
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
|
@ -117,6 +124,7 @@ class VMobject(Mobject):
|
|||
"stroke_rgba": np.zeros((1, 4)),
|
||||
"stroke_width": np.zeros((1, 1)),
|
||||
"orientation": np.ones((1, 1)),
|
||||
"joint_angle": np.zeros((0, 1)),
|
||||
})
|
||||
|
||||
def init_uniforms(self):
|
||||
|
|
@ -415,22 +423,23 @@ class VMobject(Mobject):
|
|||
# Points
|
||||
def set_anchors_and_handles(
|
||||
self,
|
||||
anchors1: Vect3Array,
|
||||
anchors: Vect3Array,
|
||||
handles: Vect3Array,
|
||||
anchors2: Vect3Array
|
||||
):
|
||||
assert(len(anchors1) == len(handles) == len(anchors2))
|
||||
nppc = self.n_points_per_curve
|
||||
new_points = np.zeros((nppc * len(anchors1), self.dim))
|
||||
arrays = [anchors1, handles, anchors2]
|
||||
for index, array in enumerate(arrays):
|
||||
new_points[index::nppc] = array
|
||||
self.set_points(new_points)
|
||||
assert(len(anchors) == len(handles) + 1)
|
||||
points = resize_array(self.get_points(), 2 * len(anchors) - 1)
|
||||
points[0::2] = anchors
|
||||
points[1::2] = handles
|
||||
self.set_points(points)
|
||||
return self
|
||||
|
||||
def start_new_path(self, point: Vect3):
|
||||
assert(self.get_num_points() % self.n_points_per_curve == 0)
|
||||
self.append_points([point])
|
||||
# Path ends are signaled by a handle point sitting directly
|
||||
# on top of the previous anchor
|
||||
if self.has_points():
|
||||
self.append_points([self.get_last_point(), point])
|
||||
else:
|
||||
self.set_points([point])
|
||||
return self
|
||||
|
||||
def add_cubic_bezier_curve(
|
||||
|
|
@ -440,54 +449,54 @@ class VMobject(Mobject):
|
|||
handle2: Vect3,
|
||||
anchor2: Vect3
|
||||
):
|
||||
new_points = get_quadratic_approximation_of_cubic(anchor1, handle1, handle2, anchor2)
|
||||
self.append_points(new_points)
|
||||
self.add_subpath(get_quadratic_approximation_of_cubic(
|
||||
anchor1, handle1, handle2, anchor2
|
||||
))
|
||||
return self
|
||||
|
||||
def add_cubic_bezier_curve_to(
|
||||
self,
|
||||
handle1: Vect3,
|
||||
handle2: Vect3,
|
||||
anchor: Vect3
|
||||
anchor: Vect3,
|
||||
):
|
||||
"""
|
||||
Add cubic bezier curve to the path.
|
||||
"""
|
||||
self.throw_error_if_no_points()
|
||||
quadratic_approx = get_quadratic_approximation_of_cubic(
|
||||
self.get_last_point(), handle1, handle2, anchor
|
||||
)
|
||||
if self.has_new_path_started():
|
||||
self.append_points(quadratic_approx[1:])
|
||||
last = self.get_last_point()
|
||||
# If the two relevant tangents are close in angle to each other,
|
||||
# then just approximate with a single quadratic bezier curve.
|
||||
# Otherwise, approximate with two
|
||||
v1 = handle1 - last
|
||||
v2 = anchor - handle2
|
||||
angle = angle_between_vectors(v1, v2)
|
||||
if self.use_simple_quadratic_approx and angle < 45 * DEGREES:
|
||||
quadratic_approx = [last, find_intersection(last, v1, anchor, -v2), anchor]
|
||||
else:
|
||||
self.append_points(quadratic_approx)
|
||||
quadratic_approx = get_quadratic_approximation_of_cubic(
|
||||
last, handle1, handle2, anchor
|
||||
)
|
||||
if self.consider_points_equal(quadratic_approx[1], last):
|
||||
# This is to prevent subpaths from accidentally being marked closed
|
||||
quadratic_approx[1] = midpoint(*quadratic_approx[1:3])
|
||||
self.append_points(quadratic_approx[1:])
|
||||
return self
|
||||
|
||||
def add_quadratic_bezier_curve_to(self, handle: Vect3, anchor: Vect3):
|
||||
self.throw_error_if_no_points()
|
||||
if self.has_new_path_started():
|
||||
self.append_points([handle, anchor])
|
||||
else:
|
||||
self.append_points([self.get_last_point(), handle, anchor])
|
||||
last_point = self.get_last_point()
|
||||
if self.consider_points_equal(handle, last_point):
|
||||
# This is to prevent subpaths from accidentally being marked closed
|
||||
handle = midpoint(handle, anchor)
|
||||
self.append_points([handle, anchor])
|
||||
return self
|
||||
|
||||
def add_line_to(self, point: Vect3):
|
||||
end = self.get_points()[-1]
|
||||
alphas = np.linspace(0, 1, self.n_points_per_curve)
|
||||
if self.long_lines:
|
||||
halfway = interpolate(end, point, 0.5)
|
||||
points = [
|
||||
interpolate(end, halfway, a)
|
||||
for a in alphas
|
||||
] + [
|
||||
interpolate(halfway, point, a)
|
||||
for a in alphas
|
||||
]
|
||||
else:
|
||||
points = [
|
||||
interpolate(end, point, a)
|
||||
for a in alphas
|
||||
]
|
||||
if self.has_new_path_started():
|
||||
points = points[1:]
|
||||
self.append_points(points)
|
||||
self.throw_error_if_no_points()
|
||||
last_point = self.get_last_point()
|
||||
alphas = np.linspace(0, 1, 5 if self.long_lines else 3)
|
||||
self.append_points(outer_interpolate(last_point, point, alphas[1:]))
|
||||
return self
|
||||
|
||||
def add_smooth_curve_to(self, point: Vect3):
|
||||
|
|
@ -502,13 +511,16 @@ class VMobject(Mobject):
|
|||
def add_smooth_cubic_curve_to(self, handle: Vect3, point: Vect3):
|
||||
self.throw_error_if_no_points()
|
||||
if self.get_num_points() == 1:
|
||||
new_handle = self.get_points()[-1]
|
||||
new_handle = handle
|
||||
else:
|
||||
new_handle = self.get_reflection_of_last_handle()
|
||||
self.add_cubic_bezier_curve_to(new_handle, handle, point)
|
||||
|
||||
def has_new_path_started(self) -> bool:
|
||||
return self.get_num_points() % self.n_points_per_curve == 1
|
||||
points = self.get_points()
|
||||
if len(points) == 1:
|
||||
return True
|
||||
return self.consider_points_equal(points[-3], points[-2])
|
||||
|
||||
def get_last_point(self) -> Vect3:
|
||||
return self.get_points()[-1]
|
||||
|
|
@ -517,35 +529,62 @@ class VMobject(Mobject):
|
|||
points = self.get_points()
|
||||
return 2 * points[-1] - points[-2]
|
||||
|
||||
def close_path(self):
|
||||
if not self.is_closed():
|
||||
self.add_line_to(self.get_subpaths()[-1][0])
|
||||
def close_path(self, smooth: bool = False):
|
||||
if self.is_closed():
|
||||
return self
|
||||
last_path_start = self.get_subpaths()[-1][0]
|
||||
if smooth:
|
||||
self.add_smooth_curve_to(last_path_start)
|
||||
else:
|
||||
self.add_line_to(last_path_start)
|
||||
return self
|
||||
|
||||
def is_closed(self) -> bool:
|
||||
return self.consider_points_equals(
|
||||
self.get_points()[0], self.get_points()[-1]
|
||||
)
|
||||
points = self.get_points()
|
||||
return self.consider_points_equal(points[0], points[-1])
|
||||
|
||||
def subdivide_curves_by_condition(
|
||||
self,
|
||||
tuple_to_subdivisions: Callable,
|
||||
recurse: bool = True
|
||||
):
|
||||
for vmob in self.get_family(recurse):
|
||||
if not vmob.has_points():
|
||||
continue
|
||||
new_points = [vmob.get_points()[0]]
|
||||
for tup in vmob.get_bezier_tuples():
|
||||
n_divisions = tuple_to_subdivisions(*tup)
|
||||
if n_divisions > 0:
|
||||
alphas = np.linspace(0, 1, n_divisions + 2)
|
||||
new_points.extend([
|
||||
partial_quadratic_bezier_points(tup, a1, a2)[1:]
|
||||
for a1, a2 in zip(alphas, alphas[1:])
|
||||
])
|
||||
else:
|
||||
new_points.append(tup[1:])
|
||||
vmob.set_points(np.vstack(new_points))
|
||||
return self
|
||||
|
||||
def subdivide_sharp_curves(
|
||||
self,
|
||||
angle_threshold: float = 30 * DEGREES,
|
||||
recurse: bool = True
|
||||
):
|
||||
vmobs = [vm for vm in self.get_family(recurse) if vm.has_points()]
|
||||
for vmob in vmobs:
|
||||
new_points = []
|
||||
for tup in vmob.get_bezier_tuples():
|
||||
angle = angle_between_vectors(tup[1] - tup[0], tup[2] - tup[1])
|
||||
if angle > angle_threshold:
|
||||
n = int(np.ceil(angle / angle_threshold))
|
||||
alphas = np.linspace(0, 1, n + 1)
|
||||
new_points.extend([
|
||||
partial_quadratic_bezier_points(tup, a1, a2)
|
||||
for a1, a2 in zip(alphas, alphas[1:])
|
||||
])
|
||||
else:
|
||||
new_points.append(tup)
|
||||
vmob.set_points(np.vstack(new_points))
|
||||
def tuple_to_subdivisions(b0, b1, b2):
|
||||
angle = angle_between_vectors(b1 - b0, b2 - b1)
|
||||
return int(angle / angle_threshold)
|
||||
|
||||
self.subdivide_curves_by_condition(tuple_to_subdivisions, recurse)
|
||||
return self
|
||||
|
||||
def subdivide_intersections(self, recurse: bool = True, n_subdivisions: int = 1):
|
||||
path = self.get_anchors()
|
||||
def tuple_to_subdivisions(b0, b1, b2):
|
||||
if line_intersects_path(b0, b1, path):
|
||||
return n_subdivisions
|
||||
return 0
|
||||
|
||||
self.subdivide_curves_by_condition(tuple_to_subdivisions, recurse)
|
||||
return self
|
||||
|
||||
def add_points_as_corners(self, points: Iterable[Vect3]):
|
||||
|
|
@ -554,12 +593,9 @@ class VMobject(Mobject):
|
|||
return points
|
||||
|
||||
def set_points_as_corners(self, points: Iterable[Vect3]):
|
||||
nppc = self.n_points_per_curve
|
||||
points = np.array(points)
|
||||
self.set_anchors_and_handles(*[
|
||||
interpolate(points[:-1], points[1:], a)
|
||||
for a in np.linspace(0, 1, nppc)
|
||||
])
|
||||
anchors = np.array(points)
|
||||
handles = 0.5 * (anchors[:-1] + anchors[1:])
|
||||
self.set_anchors_and_handles(anchors, handles)
|
||||
return self
|
||||
|
||||
def set_points_smoothly(
|
||||
|
|
@ -576,21 +612,29 @@ class VMobject(Mobject):
|
|||
|
||||
def change_anchor_mode(self, mode: str):
|
||||
assert(mode in ("jagged", "approx_smooth", "true_smooth"))
|
||||
nppc = self.n_points_per_curve
|
||||
for submob in self.family_members_with_points():
|
||||
subpaths = submob.get_subpaths()
|
||||
submob.clear_points()
|
||||
new_points = []
|
||||
for subpath in subpaths:
|
||||
anchors = np.vstack([subpath[::nppc], subpath[-1:]])
|
||||
anchors = subpath[::2]
|
||||
new_subpath = np.array(subpath)
|
||||
if mode == "approx_smooth":
|
||||
new_subpath[1::nppc] = get_smooth_quadratic_bezier_handle_points(anchors)
|
||||
new_subpath[1::2] = get_smooth_quadratic_bezier_handle_points(anchors)
|
||||
elif mode == "true_smooth":
|
||||
h1, h2 = get_smooth_cubic_bezier_handle_points(anchors)
|
||||
new_subpath = get_quadratic_approximation_of_cubic(anchors[:-1], h1, h2, anchors[1:])
|
||||
# The format here is that each successive group of 5 points
|
||||
# represents two quadratic bezier curves. We assume the end
|
||||
# of one is the start of the next, so eliminate elements 5, 10, 15, etc.
|
||||
quads = get_quadratic_approximation_of_cubic(anchors[:-1], h1, h2, anchors[1:])
|
||||
is_start = (np.arange(len(quads)) % 5 == 0)
|
||||
new_subpath = np.array([quads[0], *quads[~is_start]])
|
||||
elif mode == "jagged":
|
||||
new_subpath[1::nppc] = 0.5 * (anchors[:-1] + anchors[1:])
|
||||
submob.append_points(new_subpath)
|
||||
new_subpath[1::2] = 0.5 * (anchors[:-1] + anchors[1:])
|
||||
if new_points:
|
||||
# Close previous path
|
||||
new_points.append(new_points[-1][-1])
|
||||
new_points.append(new_subpath)
|
||||
submob.set_points(np.vstack(new_points))
|
||||
submob.refresh_triangulation()
|
||||
return self
|
||||
|
||||
|
|
@ -620,66 +664,63 @@ class VMobject(Mobject):
|
|||
return self
|
||||
|
||||
def add_subpath(self, points: Vect3Array):
|
||||
assert(len(points) % self.n_points_per_curve == 0)
|
||||
self.append_points(points)
|
||||
assert(len(points) % 2 == 1)
|
||||
if not self.has_points():
|
||||
self.set_points(points)
|
||||
return self
|
||||
if not self.consider_points_equal(points[0], self.get_points()[-1]):
|
||||
self.start_new_path(points[0])
|
||||
self.append_points(points[1:])
|
||||
return self
|
||||
|
||||
def append_vectorized_mobject(self, vectorized_mobject: VMobject):
|
||||
new_points = list(vectorized_mobject.get_points())
|
||||
|
||||
if self.has_new_path_started():
|
||||
# Remove last point, which is starting
|
||||
# a new path
|
||||
self.resize_points(len(self.get_points() - 1))
|
||||
self.append_points(new_points)
|
||||
def append_vectorized_mobject(self, vmobject: VMobject):
|
||||
self.add_subpath(vmobject.get_points())
|
||||
return self
|
||||
|
||||
#
|
||||
def consider_points_equals(self, p0: Vect3, p1: Vect3) -> bool:
|
||||
def consider_points_equal(self, p0: Vect3, p1: Vect3) -> bool:
|
||||
return get_norm(p1 - p0) < self.tolerance_for_point_equality
|
||||
|
||||
# Information about the curve
|
||||
def get_bezier_tuples_from_points(self, points: Sequence[Vect3]):
|
||||
nppc = self.n_points_per_curve
|
||||
remainder = len(points) % nppc
|
||||
points = points[:len(points) - remainder]
|
||||
return (
|
||||
points[i:i + nppc]
|
||||
for i in range(0, len(points), nppc)
|
||||
)
|
||||
def get_bezier_tuples_from_points(self, points: Vect3Array) -> Iterable[Vect3Array]:
|
||||
n_curves = (len(points) - 1) // 2
|
||||
return (points[2 * i : 2 * i + 3] for i in range(n_curves))
|
||||
|
||||
def get_bezier_tuples(self):
|
||||
def get_bezier_tuples(self) -> Iterable[Vect3Array]:
|
||||
return self.get_bezier_tuples_from_points(self.get_points())
|
||||
|
||||
def get_subpaths_from_points(
|
||||
self,
|
||||
points: Vect3Array
|
||||
) -> list[Vect3Array]:
|
||||
nppc = self.n_points_per_curve
|
||||
diffs = points[nppc - 1:-1:nppc] - points[nppc::nppc]
|
||||
splits = (diffs * diffs).sum(1) > self.tolerance_for_point_equality
|
||||
split_indices = np.arange(nppc, len(points), nppc, dtype=int)[splits]
|
||||
def get_subpath_end_indices_from_points(self, points: Vect3Array):
|
||||
atol = self.tolerance_for_point_equality
|
||||
a0, h, a1 = points[0:-1:2], points[1::2], points[2::2]
|
||||
# An anchor point is considered the end of a path
|
||||
# if its following handle is sitting on top of it.
|
||||
# To disambiguate this from cases with many null
|
||||
# curves in a row, we also check that the following
|
||||
# anchor is genuinely distinct
|
||||
is_end = (a0 == h).all(1) & (abs(h - a1) > atol).any(1)
|
||||
inner_ends = (2 * n for n, end in enumerate(is_end) if end)
|
||||
return np.array([*inner_ends, len(points) - 1])
|
||||
|
||||
split_indices = [0, *split_indices, len(points)]
|
||||
return [
|
||||
points[i1:i2]
|
||||
for i1, i2 in zip(split_indices, split_indices[1:])
|
||||
if (i2 - i1) >= nppc
|
||||
]
|
||||
def get_subpath_end_indices(self):
|
||||
return self.get_subpath_end_indices_from_points(self.get_points())
|
||||
|
||||
def get_subpaths_from_points(self, points: Vect3Array) -> list[Vect3Array]:
|
||||
end_indices = self.get_subpath_end_indices_from_points(points)
|
||||
start_indices = [0, *(end_indices[:-1] + 2)]
|
||||
return [points[i1:i2 + 1] for i1, i2 in zip(start_indices, end_indices)]
|
||||
|
||||
def get_subpaths(self) -> list[Vect3Array]:
|
||||
return self.get_subpaths_from_points(self.get_points())
|
||||
|
||||
def get_nth_curve_points(self, n: int) -> Vect3:
|
||||
def get_nth_curve_points(self, n: int) -> Vect3Array:
|
||||
assert(n < self.get_num_curves())
|
||||
nppc = self.n_points_per_curve
|
||||
return self.get_points()[nppc * n:nppc * (n + 1)]
|
||||
return self.get_points()[2 * n : 2 * n + 3]
|
||||
|
||||
def get_nth_curve_function(self, n: int) -> Callable[[float], Vect3]:
|
||||
return bezier(self.get_nth_curve_points(n))
|
||||
|
||||
def get_num_curves(self) -> int:
|
||||
return self.get_num_points() // self.n_points_per_curve
|
||||
return len(self.data["points"]) // 2
|
||||
|
||||
def quick_point_from_proportion(self, alpha: float) -> Vect3:
|
||||
# Assumes all curves have the same length, so is inaccurate
|
||||
|
|
@ -694,20 +735,24 @@ class VMobject(Mobject):
|
|||
elif alpha >= 1:
|
||||
return self.get_end()
|
||||
|
||||
partials = [0]
|
||||
partials: list[float] = [0]
|
||||
for tup in self.get_bezier_tuples():
|
||||
# Approximate length with straight line from start to end
|
||||
arclen = get_norm(tup[0] - tup[-1])
|
||||
if self.consider_points_equal(tup[0], tup[1]):
|
||||
# Don't consider null curves
|
||||
arclen = 0
|
||||
else:
|
||||
# Approximate length with straight line from start to end
|
||||
arclen = get_norm(tup[2] - tup[0])
|
||||
partials.append(partials[-1] + arclen)
|
||||
full = partials[-1]
|
||||
if full == 0:
|
||||
return self.get_start()
|
||||
# First index where the partial length is more alpha times the full length
|
||||
# First index where the partial length is more than alpha times the full length
|
||||
i = next(
|
||||
(i for i, x in enumerate(partials) if x >= full * alpha),
|
||||
len(partials) # Default
|
||||
)
|
||||
residue = inverse_interpolate(partials[i - 1] / full, partials[i] / full, alpha)
|
||||
residue = float(inverse_interpolate(partials[i - 1] / full, partials[i] / full, alpha))
|
||||
return self.get_nth_curve_function(i - 1)(residue)
|
||||
|
||||
def get_anchors_and_handles(self) -> list[Vect3]:
|
||||
|
|
@ -717,37 +762,24 @@ class VMobject(Mobject):
|
|||
will be three points defining a quadratic bezier curve
|
||||
for any i in range(0, len(anchors1))
|
||||
"""
|
||||
nppc = self.n_points_per_curve
|
||||
points = self.get_points()
|
||||
return [
|
||||
points[i::nppc]
|
||||
for i in range(nppc)
|
||||
]
|
||||
return [points[0:-1:2], points[1::2], points[2::2]]
|
||||
|
||||
def get_start_anchors(self) -> Vect3Array:
|
||||
return self.get_points()[0::self.n_points_per_curve]
|
||||
return self.get_points()[0:-1:2]
|
||||
|
||||
def get_end_anchors(self) -> Vect3:
|
||||
nppc = self.n_points_per_curve
|
||||
return self.get_points()[nppc - 1::nppc]
|
||||
return self.get_points()[2::2]
|
||||
|
||||
def get_anchors(self) -> Vect3Array:
|
||||
points = self.get_points()
|
||||
if len(points) == 1:
|
||||
return points
|
||||
return np.array(list(it.chain(*zip(
|
||||
self.get_start_anchors(),
|
||||
self.get_end_anchors(),
|
||||
))))
|
||||
return self.get_points()[::2]
|
||||
|
||||
def get_points_without_null_curves(self, atol: float = 1e-9) -> Vect3Array:
|
||||
nppc = self.n_points_per_curve
|
||||
points = self.get_points()
|
||||
distinct_curves = reduce(op.or_, [
|
||||
(abs(points[i::nppc] - points[0::nppc]) > atol).any(1)
|
||||
for i in range(1, nppc)
|
||||
])
|
||||
return points[distinct_curves.repeat(nppc)]
|
||||
new_points = [self.get_points()[0]]
|
||||
for tup in self.get_bezier_tuples():
|
||||
if get_norm(tup[1] - tup[0]) > atol or get_norm(tup[2] - tup[0]) > atol:
|
||||
new_points.append(tup[1:])
|
||||
return np.vstack(new_points)
|
||||
|
||||
def get_arc_length(self, n_sample_points: int | None = None) -> float:
|
||||
if n_sample_points is None:
|
||||
|
|
@ -757,8 +789,7 @@ class VMobject(Mobject):
|
|||
for a in np.linspace(0, 1, n_sample_points)
|
||||
])
|
||||
diffs = points[1:] - points[:-1]
|
||||
norms = np.array([get_norm(d) for d in diffs])
|
||||
return norms.sum()
|
||||
return sum(map(get_norm, diffs))
|
||||
|
||||
def get_area_vector(self) -> Vect3:
|
||||
# Returns a vector whose length is the area bound by
|
||||
|
|
@ -768,21 +799,16 @@ class VMobject(Mobject):
|
|||
if not self.has_points():
|
||||
return np.zeros(3)
|
||||
|
||||
nppc = self.n_points_per_curve
|
||||
points = self.get_points()
|
||||
p0 = points[0::nppc]
|
||||
p1 = points[nppc - 1::nppc]
|
||||
p0 = self.get_anchors()
|
||||
p1 = np.vstack([p0[1:], p0[0]])
|
||||
|
||||
if len(p0) != len(p1):
|
||||
m = min(len(p0), len(p1))
|
||||
p0 = p0[:m]
|
||||
p1 = p1[:m]
|
||||
|
||||
# Each term goes through all edges [(x1, y1, z1), (x2, y2, z2)]
|
||||
# Each term goes through all edges [(x0, y0, z0), (x1, y1, z1)]
|
||||
sums = p0 + p1
|
||||
diffs = p1 - p0
|
||||
return 0.5 * np.array([
|
||||
sum((p0[:, 1] + p1[:, 1]) * (p1[:, 2] - p0[:, 2])), # Add up (y1 + y2)*(z2 - z1)
|
||||
sum((p0[:, 2] + p1[:, 2]) * (p1[:, 0] - p0[:, 0])), # Add up (z1 + z2)*(x2 - x1)
|
||||
sum((p0[:, 0] + p1[:, 0]) * (p1[:, 1] - p0[:, 1])), # Add up (x1 + x2)*(y2 - y1)
|
||||
(sums[:, 1] * diffs[:, 2]).sum(), # Add up (y0 + y1)*(z1 - z0)
|
||||
(sums[:, 2] * diffs[:, 0]).sum(), # Add up (z0 + z1)*(x1 - x0)
|
||||
(sums[:, 0] * diffs[:, 1]).sum(), # Add up (x0 + x1)*(y1 - y0)
|
||||
])
|
||||
|
||||
def get_unit_normal(self) -> Vect3:
|
||||
|
|
@ -809,41 +835,40 @@ class VMobject(Mobject):
|
|||
# needlessly throughout an animation
|
||||
if self.has_fill() and vmobject.has_fill() and self.has_same_shape_as(vmobject):
|
||||
vmobject.triangulation = self.triangulation
|
||||
return
|
||||
return self
|
||||
|
||||
for mob in self, vmobject:
|
||||
# If there are no points, add one to
|
||||
# where the "center" is
|
||||
if not mob.has_points():
|
||||
mob.start_new_path(mob.get_center())
|
||||
# If there's only one point, turn it into
|
||||
# a null curve
|
||||
if mob.has_new_path_started():
|
||||
mob.add_line_to(mob.get_points()[0])
|
||||
|
||||
# Figure out what the subpaths are, and align
|
||||
subpaths1 = self.get_subpaths()
|
||||
subpaths2 = vmobject.get_subpaths()
|
||||
n_subpaths = max(len(subpaths1), len(subpaths2))
|
||||
|
||||
# Start building new ones
|
||||
new_subpaths1 = []
|
||||
new_subpaths2 = []
|
||||
|
||||
nppc = self.n_points_per_curve
|
||||
|
||||
def get_nth_subpath(path_list, n):
|
||||
if n >= len(path_list):
|
||||
# Create a null path at the very end
|
||||
return [path_list[-1][-1]] * nppc
|
||||
return [path_list[-1][-1]] * 3
|
||||
return path_list[n]
|
||||
|
||||
for n in range(n_subpaths):
|
||||
sp1 = get_nth_subpath(subpaths1, n)
|
||||
sp2 = get_nth_subpath(subpaths2, n)
|
||||
diff1 = max(0, (len(sp2) - len(sp1)) // nppc)
|
||||
diff2 = max(0, (len(sp1) - len(sp2)) // nppc)
|
||||
diff1 = max(0, (len(sp2) - len(sp1)) // 2)
|
||||
diff2 = max(0, (len(sp1) - len(sp2)) // 2)
|
||||
sp1 = self.insert_n_curves_to_point_list(diff1, sp1)
|
||||
sp2 = self.insert_n_curves_to_point_list(diff2, sp2)
|
||||
if n > 0:
|
||||
# Add intermediate anchor to mark path end
|
||||
new_subpaths1.append(new_subpaths1[0][-1])
|
||||
new_subpaths2.append(new_subpaths2[0][-1])
|
||||
new_subpaths1.append(sp1)
|
||||
new_subpaths2.append(sp2)
|
||||
self.set_points(np.vstack(new_subpaths1))
|
||||
|
|
@ -854,26 +879,23 @@ class VMobject(Mobject):
|
|||
for mob in self.get_family(recurse):
|
||||
if mob.get_num_curves() > 0:
|
||||
new_points = mob.insert_n_curves_to_point_list(n, mob.get_points())
|
||||
# TODO, this should happen in insert_n_curves_to_point_list
|
||||
if mob.has_new_path_started():
|
||||
new_points = np.vstack([new_points, mob.get_last_point()])
|
||||
mob.set_points(new_points)
|
||||
return self
|
||||
|
||||
def insert_n_curves_to_point_list(self, n: int, points: Vect3Array):
|
||||
nppc = self.n_points_per_curve
|
||||
if len(points) == 1:
|
||||
return np.repeat(points, nppc * n, 0)
|
||||
return np.repeat(points, 2 * n + 1, 0)
|
||||
|
||||
bezier_groups = list(self.get_bezier_tuples_from_points(points))
|
||||
bezier_tuples = list(self.get_bezier_tuples_from_points(points))
|
||||
atol = self.tolerance_for_point_equality
|
||||
norms = np.array([
|
||||
get_norm(bg[nppc - 1] - bg[0])
|
||||
for bg in bezier_groups
|
||||
0 if get_norm(tup[1] - tup[0]) < atol else get_norm(tup[2] - tup[0])
|
||||
for tup in bezier_tuples
|
||||
])
|
||||
total_norm = sum(norms)
|
||||
# Calculate insertions per curve (ipc)
|
||||
if total_norm < 1e-6:
|
||||
ipc = [n] + [0] * (len(bezier_groups) - 1)
|
||||
ipc = [n] + [0] * (len(bezier_tuples) - 1)
|
||||
else:
|
||||
ipc = np.round(n * norms / sum(norms)).astype(int)
|
||||
|
||||
|
|
@ -883,14 +905,14 @@ class VMobject(Mobject):
|
|||
for x in range(-diff):
|
||||
ipc[np.argmax(ipc)] -= 1
|
||||
|
||||
new_points = []
|
||||
for group, n_inserts in zip(bezier_groups, ipc):
|
||||
new_points = [points[0]]
|
||||
for tup, n_inserts in zip(bezier_tuples, ipc):
|
||||
# What was once a single quadratic curve defined
|
||||
# by "group" will now be broken into n_inserts + 1
|
||||
# by "tup" will now be broken into n_inserts + 1
|
||||
# smaller quadratic curves
|
||||
alphas = np.linspace(0, 1, n_inserts + 2)
|
||||
for a1, a2 in zip(alphas, alphas[1:]):
|
||||
new_points += partial_quadratic_bezier_points(group, a1, a2)
|
||||
new_points.extend(partial_quadratic_bezier_points(tup, a1, a2)[1:])
|
||||
return np.vstack(new_points)
|
||||
|
||||
def interpolate(
|
||||
|
|
@ -910,25 +932,24 @@ class VMobject(Mobject):
|
|||
|
||||
def pointwise_become_partial(self, vmobject: VMobject, a: float, b: float):
|
||||
assert(isinstance(vmobject, VMobject))
|
||||
vm_points = vmobject.get_points()
|
||||
if a <= 0 and b >= 1:
|
||||
self.become(vmobject)
|
||||
self.set_points(vm_points, refresh=False)
|
||||
return self
|
||||
num_curves = vmobject.get_num_curves()
|
||||
nppc = self.n_points_per_curve
|
||||
|
||||
# Partial curve includes three portions:
|
||||
# - A middle section, which matches the curve exactly
|
||||
# - A start, which is some ending portion of an inner quadratic
|
||||
# - A middle section, which matches the curve exactly
|
||||
# - An end, which is the starting portion of a later inner quadratic
|
||||
|
||||
lower_index, lower_residue = integer_interpolate(0, num_curves, a)
|
||||
upper_index, upper_residue = integer_interpolate(0, num_curves, b)
|
||||
i1 = nppc * lower_index
|
||||
i2 = nppc * (lower_index + 1)
|
||||
i3 = nppc * upper_index
|
||||
i4 = nppc * (upper_index + 1)
|
||||
i1 = 2 * lower_index
|
||||
i2 = 2 * lower_index + 3
|
||||
i3 = 2 * upper_index
|
||||
i4 = 2 * upper_index + 3
|
||||
|
||||
vm_points = vmobject.get_points()
|
||||
new_points = vm_points.copy()
|
||||
if num_curves == 0:
|
||||
new_points[:] = 0
|
||||
|
|
@ -938,7 +959,6 @@ class VMobject(Mobject):
|
|||
new_points[:i1] = tup[0]
|
||||
new_points[i1:i4] = tup
|
||||
new_points[i4:] = tup[2]
|
||||
new_points[nppc:] = new_points[nppc - 1]
|
||||
else:
|
||||
low_tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, 1)
|
||||
high_tup = partial_quadratic_bezier_points(vm_points[i3:i4], 0, upper_residue)
|
||||
|
|
@ -947,7 +967,9 @@ class VMobject(Mobject):
|
|||
# Keep new_points i2:i3 as they are
|
||||
new_points[i3:i4] = high_tup
|
||||
new_points[i4:] = high_tup[2]
|
||||
self.set_points(new_points)
|
||||
self.set_points(new_points, refresh=False)
|
||||
if self.has_fill():
|
||||
self.refresh_triangulation()
|
||||
return self
|
||||
|
||||
def get_subcurve(self, a: float, b: float) -> VMobject:
|
||||
|
|
@ -955,7 +977,16 @@ class VMobject(Mobject):
|
|||
vmob.pointwise_become_partial(self, a, b)
|
||||
return vmob
|
||||
|
||||
# Related to triangulation
|
||||
def get_outer_vert_indices(self):
|
||||
"""
|
||||
Returns the pattern (0, 1, 2, 2, 3, 4, 4, 5, 6, ...)
|
||||
"""
|
||||
n_curves = self.get_num_curves()
|
||||
if len(self.outer_vert_indices) != 3 * n_curves:
|
||||
self.outer_vert_indices = (np.arange(1, 3 * n_curves + 1) * 2) // 3
|
||||
return self.outer_vert_indices
|
||||
|
||||
# Data for shaders that may need refreshing
|
||||
|
||||
def refresh_triangulation(self):
|
||||
for mob in self.get_family():
|
||||
|
|
@ -981,63 +1012,129 @@ class VMobject(Mobject):
|
|||
return self.triangulation
|
||||
|
||||
normal_vector = self.get_unit_normal()
|
||||
indices = np.arange(len(points), dtype=int)
|
||||
|
||||
# Rotate points such that unit normal vector is OUT
|
||||
if not np.isclose(normal_vector, OUT).all():
|
||||
points = np.dot(points, z_to_vector(normal_vector))
|
||||
|
||||
atol = self.tolerance_for_point_equality
|
||||
end_of_loop = np.zeros(len(points) // 3, dtype=bool)
|
||||
end_of_loop[:-1] = (np.abs(points[2:-3:3] - points[3::3]) > atol).any(1)
|
||||
end_of_loop[-1] = True
|
||||
|
||||
v01s = points[1::3] - points[0::3]
|
||||
v12s = points[2::3] - points[1::3]
|
||||
v01s = points[1::2] - points[0:-1:2]
|
||||
v12s = points[2::2] - points[1::2]
|
||||
curve_orientations = np.sign(cross2d(v01s, v12s))
|
||||
self.data["orientation"] = np.transpose([curve_orientations.repeat(3)])
|
||||
|
||||
# Reset orientation data
|
||||
self.data["orientation"] = resize_array(self.data["orientation"], len(points))
|
||||
self.data["orientation"][1::2, 0] = curve_orientations
|
||||
if "orientation" in self.locked_data_keys:
|
||||
self.locked_data_keys.remove("orientation")
|
||||
|
||||
concave_parts = curve_orientations < 0
|
||||
|
||||
# These are the vertices to which we'll apply a polygon triangulation
|
||||
indices = np.arange(len(points), dtype=int)
|
||||
inner_vert_indices = np.hstack([
|
||||
indices[0::3],
|
||||
indices[1::3][concave_parts],
|
||||
indices[2::3][end_of_loop],
|
||||
indices[0::2],
|
||||
indices[1::2][concave_parts],
|
||||
])
|
||||
inner_vert_indices.sort()
|
||||
rings = np.arange(1, len(inner_vert_indices) + 1)[inner_vert_indices % 3 == 2]
|
||||
# Even indices correspond to anchors, and `end_indices // 2`
|
||||
# shows which anchors are considered end points
|
||||
end_indices = self.get_subpath_end_indices()
|
||||
counts = np.arange(1, len(inner_vert_indices) + 1)
|
||||
rings = counts[inner_vert_indices % 2 == 0][end_indices // 2]
|
||||
|
||||
# Triangulate
|
||||
inner_verts = points[inner_vert_indices]
|
||||
inner_tri_indices = inner_vert_indices[
|
||||
earclip_triangulation(inner_verts, rings)
|
||||
]
|
||||
# Remove null triangles, coming from adjascent points
|
||||
iti = inner_tri_indices
|
||||
null1 = (iti[0::3] + 1 == iti[1::3]) & (iti[0::3] + 2 == iti[2::3])
|
||||
null2 = (iti[0::3] - 1 == iti[1::3]) & (iti[0::3] - 2 == iti[2::3])
|
||||
inner_tri_indices = iti[~(null1 | null2).repeat(3)]
|
||||
|
||||
tri_indices = np.hstack([indices, inner_tri_indices])
|
||||
outer_tri_indices = self.get_outer_vert_indices()
|
||||
tri_indices = np.hstack([outer_tri_indices, inner_tri_indices])
|
||||
self.triangulation = tri_indices
|
||||
self.needs_new_triangulation = False
|
||||
return tri_indices
|
||||
|
||||
def refresh_joint_angles(self):
|
||||
for mob in self.get_family():
|
||||
mob.needs_new_joint_angles = True
|
||||
return self
|
||||
|
||||
def get_joint_angles(self, refresh: bool = False):
|
||||
if not self.needs_new_joint_angles and not refresh:
|
||||
return self.data["joint_angle"]
|
||||
|
||||
self.needs_new_joint_angles = False
|
||||
|
||||
points = self.get_points()
|
||||
self.data["joint_angle"] = resize_array(self.data["joint_angle"], len(points))
|
||||
|
||||
if(len(points) < 3):
|
||||
return self.data["joint_angle"]
|
||||
|
||||
# Unit tangent vectors
|
||||
a0, h, a1 = points[0:-1:2], points[1::2], points[2::2]
|
||||
a0_to_h = normalize_along_axis(h - a0, 1)
|
||||
h_to_a1 = normalize_along_axis(a1 - h, 1)
|
||||
|
||||
vect_to_vert = np.zeros(points.shape)
|
||||
vect_from_vert = np.zeros(points.shape)
|
||||
|
||||
vect_to_vert[1::2] = a0_to_h
|
||||
vect_to_vert[2::2] = h_to_a1
|
||||
vect_from_vert[0:-1:2] = a0_to_h
|
||||
vect_from_vert[1::2] = h_to_a1
|
||||
|
||||
ends = self.get_subpath_end_indices()
|
||||
starts = [0, *(e + 2 for e in ends[:-1])]
|
||||
for start, end in zip(starts, ends):
|
||||
if self.consider_points_equal(points[start], points[end]):
|
||||
vect_to_vert[start] = h_to_a1[end // 2 - 1]
|
||||
vect_from_vert[end] = a0_to_h[start // 2]
|
||||
|
||||
# Compute angles, and read them into
|
||||
# the joint_angles array
|
||||
result = self.data["joint_angle"][:, 0]
|
||||
dots = (vect_to_vert * vect_from_vert).sum(1)
|
||||
np.arccos(dots, out=result, where=((dots <= 1) & (dots >= -1)))
|
||||
# Assumes unit normal in the positive z direction
|
||||
result *= np.sign(cross2d(vect_to_vert, vect_from_vert))
|
||||
return result
|
||||
|
||||
def triggers_refreshed_triangulation(func: Callable):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
def wrapper(self, *args, refresh=True, **kwargs):
|
||||
func(self, *args, **kwargs)
|
||||
self.refresh_triangulation()
|
||||
if refresh:
|
||||
self.refresh_triangulation()
|
||||
self.refresh_joint_angles()
|
||||
return wrapper
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def set_points(self, points: Vect3Array):
|
||||
assert(len(points) == 0 or len(points) % 2 == 1)
|
||||
super().set_points(points)
|
||||
return self
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def append_points(self, points: Vect3Array):
|
||||
assert(len(points) % 2 == 0)
|
||||
super().append_points(points)
|
||||
return self
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def reverse_points(self):
|
||||
# This will reset which anchors are
|
||||
# considered path ends
|
||||
if not self.has_points():
|
||||
return self
|
||||
inner_ends = self.get_subpath_end_indices()[:-1]
|
||||
self.data["points"][inner_ends + 1] = self.data["points"][inner_ends + 2]
|
||||
super().reverse_points()
|
||||
return self
|
||||
|
||||
|
|
@ -1059,6 +1156,10 @@ class VMobject(Mobject):
|
|||
self.make_approximately_smooth()
|
||||
return self
|
||||
|
||||
def apply_points_function(self, *args, **kwargs,):
|
||||
super().apply_points_function(*args, **kwargs)
|
||||
self.refresh_joint_angles()
|
||||
|
||||
# For shaders
|
||||
def init_shader_data(self):
|
||||
self.fill_data = np.zeros(0, dtype=self.fill_dtype)
|
||||
|
|
@ -1068,14 +1169,15 @@ class VMobject(Mobject):
|
|||
vert_indices=np.zeros(0, dtype='i4'),
|
||||
uniforms=self.uniforms,
|
||||
shader_folder=self.fill_shader_folder,
|
||||
render_primitive=self.render_primitive,
|
||||
render_primitive=self.fill_render_primitive,
|
||||
)
|
||||
self.stroke_shader_wrapper = ShaderWrapper(
|
||||
vert_data=self.stroke_data,
|
||||
uniforms=self.uniforms,
|
||||
shader_folder=self.stroke_shader_folder,
|
||||
render_primitive=self.render_primitive,
|
||||
render_primitive=self.stroke_render_primitive,
|
||||
)
|
||||
self.back_stroke_shader_wrapper = self.stroke_shader_wrapper.copy()
|
||||
|
||||
def refresh_shader_wrapper_id(self):
|
||||
for wrapper in [self.fill_shader_wrapper, self.stroke_shader_wrapper]:
|
||||
|
|
@ -1083,7 +1185,7 @@ class VMobject(Mobject):
|
|||
return self
|
||||
|
||||
def get_fill_shader_wrapper(self) -> ShaderWrapper:
|
||||
self.fill_shader_wrapper.vert_indices = self.get_fill_shader_vert_indices()
|
||||
self.fill_shader_wrapper.vert_indices = self.get_triangulation()
|
||||
self.fill_shader_wrapper.vert_data = self.get_fill_shader_data()
|
||||
self.fill_shader_wrapper.uniforms = self.get_shader_uniforms()
|
||||
self.fill_shader_wrapper.depth_test = self.depth_test
|
||||
|
|
@ -1097,18 +1199,26 @@ class VMobject(Mobject):
|
|||
|
||||
def get_shader_wrapper_list(self) -> list[ShaderWrapper]:
|
||||
# Build up data lists
|
||||
fill_shader_wrappers = []
|
||||
stroke_shader_wrappers = []
|
||||
fill_sws = []
|
||||
stroke_sws = []
|
||||
bstroke_sws = []
|
||||
for submob in self.family_members_with_points():
|
||||
if submob.has_fill():
|
||||
fill_shader_wrappers.append(submob.get_fill_shader_wrapper())
|
||||
fill_sws.append(submob.get_fill_shader_wrapper())
|
||||
if submob.has_stroke():
|
||||
stroke_shader_wrappers.append(submob.get_stroke_shader_wrapper())
|
||||
if submob.draw_stroke_behind_fill:
|
||||
self.draw_stroke_behind_fill = True
|
||||
lst = bstroke_sws if submob.draw_stroke_behind_fill else stroke_sws
|
||||
lst.append(submob.get_stroke_shader_wrapper())
|
||||
|
||||
self_sws = [self.fill_shader_wrapper, self.stroke_shader_wrapper]
|
||||
sw_lists = [fill_shader_wrappers, stroke_shader_wrappers]
|
||||
self_sws = [
|
||||
self.back_stroke_shader_wrapper,
|
||||
self.fill_shader_wrapper,
|
||||
self.stroke_shader_wrapper
|
||||
]
|
||||
sw_lists = [
|
||||
bstroke_sws,
|
||||
fill_sws,
|
||||
stroke_sws
|
||||
]
|
||||
for sw, sw_list in zip(self_sws, sw_lists):
|
||||
if not sw_list:
|
||||
sw.vert_data = resize_array(sw.vert_data, 0)
|
||||
|
|
@ -1119,26 +1229,27 @@ class VMobject(Mobject):
|
|||
sw.read_in(*sw_list)
|
||||
sw.depth_test = any(sw.depth_test for sw in sw_list)
|
||||
sw.uniforms.update(sw_list[0].uniforms)
|
||||
if self.draw_stroke_behind_fill:
|
||||
self_sws.reverse()
|
||||
return [sw for sw in self_sws if len(sw.vert_data) > 0]
|
||||
|
||||
def get_stroke_shader_data(self) -> np.ndarray:
|
||||
# Set data array to be one longer than number of points,
|
||||
# with a dummy vertex added at the end. This is to ensure
|
||||
# it can be safely stacked onto other stroke data arrays.
|
||||
points = self.get_points()
|
||||
if len(self.stroke_data) != len(points):
|
||||
self.stroke_data = resize_array(self.stroke_data, len(points))
|
||||
n = len(points)
|
||||
size = n + 1 if n > 0 else 0
|
||||
if len(self.stroke_data) != size:
|
||||
self.stroke_data = resize_array(self.stroke_data, size)
|
||||
if n == 0:
|
||||
return self.stroke_data
|
||||
|
||||
if "points" not in self.locked_data_keys:
|
||||
nppc = self.n_points_per_curve
|
||||
self.stroke_data["point"] = points
|
||||
self.stroke_data["prev_point"][:nppc] = points[-nppc:]
|
||||
self.stroke_data["prev_point"][nppc:] = points[:-nppc]
|
||||
self.stroke_data["next_point"][:-nppc] = points[nppc:]
|
||||
self.stroke_data["next_point"][-nppc:] = points[:nppc]
|
||||
|
||||
self.read_data_to_shader(self.stroke_data, "color", "stroke_rgba")
|
||||
self.read_data_to_shader(self.stroke_data, "stroke_width", "stroke_width")
|
||||
self.read_data_to_shader(self.stroke_data[:n], "point", "points")
|
||||
self.read_data_to_shader(self.stroke_data[:n], "color", "stroke_rgba")
|
||||
self.read_data_to_shader(self.stroke_data[:n], "stroke_width", "stroke_width")
|
||||
self.get_joint_angles() # Recomputes, only if refresh is needed
|
||||
self.read_data_to_shader(self.stroke_data[:n], "joint_angle", "joint_angle")
|
||||
|
||||
self.stroke_data[-1] = self.stroke_data[-2]
|
||||
return self.stroke_data
|
||||
|
||||
def get_fill_shader_data(self) -> np.ndarray:
|
||||
|
|
|
|||
|
|
@ -159,14 +159,14 @@ class InteractiveScene(Scene):
|
|||
pass
|
||||
|
||||
def get_crosshair(self):
|
||||
line = Line(LEFT, RIGHT)
|
||||
line.insert_n_curves(1)
|
||||
lines = line.replicate(2)
|
||||
lines[1].rotate(PI / 2)
|
||||
crosshair = VMobject()
|
||||
crosshair.set_points([*lines[0].get_points(), *lines[1].get_points()])
|
||||
lines = VMobject().replicate(2)
|
||||
lines[0].set_points([LEFT, ORIGIN, RIGHT])
|
||||
lines[1].set_points([UP, ORIGIN, DOWN])
|
||||
crosshair = VGroup(*lines)
|
||||
|
||||
crosshair.set_width(self.crosshair_width)
|
||||
crosshair.set_stroke(self.crosshair_color, width=[2, 0, 2, 2, 0, 2])
|
||||
crosshair.set_stroke(self.crosshair_color, width=[2, 0, 2])
|
||||
crosshair.insert_n_curves(1)
|
||||
crosshair.set_animating_status(True)
|
||||
crosshair.fix_in_frame()
|
||||
return crosshair
|
||||
|
|
@ -303,14 +303,16 @@ class InteractiveScene(Scene):
|
|||
))
|
||||
if len(mobs) == 0:
|
||||
return
|
||||
self.selection.set_animating_status(True)
|
||||
self.selection.add(*mobs)
|
||||
for mob in mobs:
|
||||
mob.set_animating_status(True)
|
||||
|
||||
def toggle_from_selection(self, *mobjects: Mobject):
|
||||
for mob in mobjects:
|
||||
if mob in self.selection:
|
||||
self.selection.remove(mob)
|
||||
mob.set_animating_status(False)
|
||||
mob.refresh_bounding_box()
|
||||
else:
|
||||
self.add_to_selection(mob)
|
||||
self.refresh_static_mobjects()
|
||||
|
|
@ -318,6 +320,7 @@ class InteractiveScene(Scene):
|
|||
def clear_selection(self):
|
||||
for mob in self.selection:
|
||||
mob.set_animating_status(False)
|
||||
mob.refresh_bounding_box()
|
||||
self.selection.set_submobjects([])
|
||||
self.refresh_static_mobjects()
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class ShaderWrapper(object):
|
|||
uniforms: dict[str, float] | None = None, # A dictionary mapping names of uniform variables
|
||||
texture_paths: dict[str, str] | None = None, # A dictionary mapping names to filepaths for textures.
|
||||
depth_test: bool = False,
|
||||
use_clip_plane: bool = False,
|
||||
render_primitive: int = moderngl.TRIANGLE_STRIP,
|
||||
):
|
||||
self.vert_data = vert_data
|
||||
|
|
@ -44,6 +45,7 @@ class ShaderWrapper(object):
|
|||
self.uniforms = uniforms or dict()
|
||||
self.texture_paths = texture_paths or dict()
|
||||
self.depth_test = depth_test
|
||||
self.use_clip_plane = use_clip_plane
|
||||
self.render_primitive = str(render_primitive)
|
||||
self.init_program_code()
|
||||
self.refresh_id()
|
||||
|
|
@ -158,15 +160,10 @@ class ShaderWrapper(object):
|
|||
return self
|
||||
|
||||
|
||||
# For caching
|
||||
filename_to_code_map: dict[str, str] = {}
|
||||
|
||||
@lru_cache(maxsize=12)
|
||||
def get_shader_code_from_file(filename: str) -> str | None:
|
||||
if not filename:
|
||||
return None
|
||||
if filename in filename_to_code_map:
|
||||
return filename_to_code_map[filename]
|
||||
|
||||
try:
|
||||
filepath = find_file(
|
||||
|
|
@ -190,7 +187,6 @@ def get_shader_code_from_file(filename: str) -> str | None:
|
|||
os.path.join("inserts", line.replace("#INSERT ", ""))
|
||||
)
|
||||
result = result.replace(line, inserted_code)
|
||||
filename_to_code_map[filename] = result
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
uniform sampler2D Texture;
|
||||
|
||||
in vec3 point;
|
||||
|
|
@ -13,7 +11,6 @@ out float v_opacity;
|
|||
|
||||
// Analog of import for manim only
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT position_point_into_frame.glsl
|
||||
|
||||
void main(){
|
||||
v_im_coords = im_coords;
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
uniform vec2 frame_shape;
|
||||
uniform vec2 pixel_shape;
|
||||
uniform vec3 camera_offset;
|
||||
uniform mat3 camera_rotation;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform float focal_distance;
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
uniform vec3 light_source_position;
|
||||
uniform vec3 camera_position;
|
||||
uniform float reflectiveness;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
|
||||
vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_data){
|
||||
float alpha = clamp((value - min_val) / (max_val - min_val), 0.0, 1.0);
|
||||
int disc_alpha = min(int(alpha * 8), 7);
|
||||
|
|
@ -9,14 +15,16 @@ vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_
|
|||
}
|
||||
|
||||
|
||||
vec4 add_light(vec4 color,
|
||||
vec3 point,
|
||||
vec3 unit_normal,
|
||||
vec3 light_coords,
|
||||
vec3 cam_coords,
|
||||
float reflectiveness,
|
||||
float gloss,
|
||||
float shadow){
|
||||
vec4 add_light(
|
||||
vec4 color,
|
||||
vec3 point,
|
||||
vec3 unit_normal,
|
||||
vec3 light_coords,
|
||||
vec3 cam_coords,
|
||||
float reflectiveness,
|
||||
float gloss,
|
||||
float shadow
|
||||
){
|
||||
if(reflectiveness == 0.0 && gloss == 0.0 && shadow == 0.0) return color;
|
||||
|
||||
vec4 result = color;
|
||||
|
|
@ -42,22 +50,19 @@ vec4 add_light(vec4 color,
|
|||
// Darken
|
||||
result.rgb = mix(result.rgb, vec3(0.0), -light_to_normal * shadow);
|
||||
}
|
||||
// float darkening = mix(1, max(light_to_normal, 0), shadow);
|
||||
// return vec4(
|
||||
// darkening * mix(color.rgb, vec3(1.0), shine),
|
||||
// color.a
|
||||
// );
|
||||
return result;
|
||||
}
|
||||
|
||||
vec4 finalize_color(vec4 color,
|
||||
vec3 point,
|
||||
vec3 unit_normal,
|
||||
vec3 light_coords,
|
||||
vec3 cam_coords,
|
||||
float reflectiveness,
|
||||
float gloss,
|
||||
float shadow){
|
||||
vec4 finalize_color(
|
||||
vec4 color,
|
||||
vec3 point,
|
||||
vec3 unit_normal,
|
||||
vec3 light_coords,
|
||||
vec3 cam_coords,
|
||||
float reflectiveness,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,31 +1,38 @@
|
|||
// Assumes the following uniforms exist in the surrounding context:
|
||||
// uniform vec2 frame_shape;
|
||||
// uniform float focal_distance;
|
||||
// uniform float is_fixed_in_frame;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform vec3 camera_offset;
|
||||
uniform mat3 camera_rotation;
|
||||
uniform vec2 frame_shape;
|
||||
uniform float focal_distance;
|
||||
|
||||
const vec2 DEFAULT_FRAME_SHAPE = vec2(8.0 * 16.0 / 9.0, 8.0);
|
||||
|
||||
float perspective_scale_factor(float z, float focal_distance){
|
||||
return max(0.0, focal_distance / (focal_distance - z));
|
||||
vec4 get_gl_Position(vec3 point){
|
||||
vec2 shape;
|
||||
if(bool(is_fixed_in_frame)) shape = DEFAULT_FRAME_SHAPE;
|
||||
else shape = frame_shape;
|
||||
|
||||
vec4 result = vec4(point, 1.0);
|
||||
result.x *= 2.0 / shape.x;
|
||||
result.y *= 2.0 / shape.y;
|
||||
result.z /= focal_distance;
|
||||
result.w = 1.0 - result.z;
|
||||
// Flip and scale to prevent premature clipping
|
||||
result.z *= -0.1;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
vec4 get_gl_Position(vec3 point){
|
||||
vec4 result = vec4(point, 1.0);
|
||||
if(!bool(is_fixed_in_frame)){
|
||||
result.x *= 2.0 / frame_shape.x;
|
||||
result.y *= 2.0 / frame_shape.y;
|
||||
float psf = perspective_scale_factor(result.z, focal_distance);
|
||||
if (psf > 0){
|
||||
result.xy *= psf;
|
||||
// TODO, what's the better way to do this?
|
||||
// This is to keep vertices too far out of frame from getting cut.
|
||||
result.z *= 0.01;
|
||||
}
|
||||
} else{
|
||||
result.x *= 2.0 / DEFAULT_FRAME_SHAPE.x;
|
||||
result.y *= 2.0 / DEFAULT_FRAME_SHAPE.y;
|
||||
vec3 rotate_point_into_frame(vec3 point){
|
||||
if(bool(is_fixed_in_frame)){
|
||||
return point;
|
||||
}
|
||||
result.z *= -1;
|
||||
return result;
|
||||
}
|
||||
return camera_rotation * point;
|
||||
}
|
||||
|
||||
|
||||
vec3 position_point_into_frame(vec3 point){
|
||||
if(bool(is_fixed_in_frame)){
|
||||
return point;
|
||||
}
|
||||
return rotate_point_into_frame(point - camera_offset);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
vec3 get_unit_normal(in vec3[3] points){
|
||||
vec3 get_unit_normal(vec3 p0, vec3 p1, vec3 p2){
|
||||
float tol = 1e-6;
|
||||
vec3 v1 = normalize(points[1] - points[0]);
|
||||
vec3 v2 = normalize(points[2] - points[1]);
|
||||
vec3 v1 = normalize(p1 - p0);
|
||||
vec3 v2 = normalize(p2 - p1);
|
||||
vec3 cp = cross(v1, v2);
|
||||
float cp_norm = length(cp);
|
||||
if(cp_norm < tol){
|
||||
// Three points form a line, so find a normal vector
|
||||
// to that line in the plane shared with the z-axis
|
||||
vec3 k_hat = vec3(0.0, 0.0, 1.0);
|
||||
vec3 comb = v1 + v2;
|
||||
vec3 new_cp = cross(cross(comb, k_hat), comb);
|
||||
float new_cp_norm = length(new_cp);
|
||||
if(new_cp_norm < tol){
|
||||
// We only come here if all three points line up
|
||||
// on the z-axis.
|
||||
return vec3(0.0, -1.0, 0.0);
|
||||
}
|
||||
return new_cp / new_cp_norm;
|
||||
}
|
||||
return cp / cp_norm;
|
||||
|
||||
if(cp_norm > tol) return cp / cp_norm;
|
||||
|
||||
// Otherwise, three pionts form a line, so find
|
||||
// a normal vector to that line in the plane shared
|
||||
// with the z-axis
|
||||
vec3 comb = v1 + v2;
|
||||
cp = cross(cross(comb, vec3(0.0, 0.0, 1.0)), comb);
|
||||
cp_norm = length(cp);
|
||||
if(cp_norm > tol) return cp / cp_norm;
|
||||
|
||||
// Otherwise, the points line up with the z-axis.
|
||||
return vec3(0.0, -1.0, 0.0);
|
||||
}
|
||||
88
manimlib/shaders/inserts/get_xy_to_uv.glsl
Normal file
88
manimlib/shaders/inserts/get_xy_to_uv.glsl
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
float cross2d(vec2 v, vec2 w){
|
||||
return v.x * w.y - w.x * v.y;
|
||||
}
|
||||
|
||||
|
||||
vec2 complex_div(vec2 v, vec2 w){
|
||||
return vec2(dot(v, w), cross2d(w, v)) / dot(w, w);
|
||||
}
|
||||
|
||||
|
||||
vec2 xs_on_clean_parabola(vec2 b0, vec2 b1, vec2 b2){
|
||||
/*
|
||||
Given three control points for a quadratic bezier,
|
||||
this returns the two values (x0, x2) such that the
|
||||
section of the parabola y = x^2 between those values
|
||||
is isometric to the given quadratic bezier.
|
||||
|
||||
Adapated from https://github.com/raphlinus/raphlinus.github.io/blob/master/_posts/2019-12-23-flatten-quadbez.md
|
||||
*/
|
||||
vec2 dd = normalize(2 * b1 - b0 - b2);
|
||||
|
||||
float u0 = dot(b1 - b0, dd);
|
||||
float u2 = dot(b2 - b1, dd);
|
||||
float cp = cross2d(b2 - b0, dd);
|
||||
|
||||
return vec2(u0 / cp, u2 / cp);
|
||||
}
|
||||
|
||||
|
||||
mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dest0, vec2 dest1){
|
||||
/*
|
||||
Returns an orthogonal matrix which will map
|
||||
src0 onto dest0 and src1 onto dest1.
|
||||
*/
|
||||
mat3 shift1 = mat3(
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
-src0.x, -src0.y, 1.0
|
||||
);
|
||||
mat3 shift2 = mat3(
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
dest0.x, dest0.y, 1.0
|
||||
);
|
||||
|
||||
// Compute complex division dest_vect / src_vect to determine rotation
|
||||
vec2 complex_rot = complex_div(dest1 - dest0, src1 - src0);
|
||||
mat3 rotate = mat3(
|
||||
complex_rot.x, complex_rot.y, 0.0,
|
||||
-complex_rot.y, complex_rot.x, 0.0,
|
||||
0.0, 0.0, 1.0
|
||||
);
|
||||
|
||||
return shift2 * rotate * shift1;
|
||||
}
|
||||
|
||||
|
||||
mat3 get_xy_to_uv(vec2 b0, vec2 b1, vec2 b2, float temp_is_linear, out float is_linear){
|
||||
/*
|
||||
Returns a matrix for an affine transformation which maps a set of quadratic
|
||||
bezier controls points into a new coordinate system such that the bezier curve
|
||||
coincides with y = x^2, or in the case of a linear curve, it's mapped to the x-axis.
|
||||
*/
|
||||
vec2 dest0;
|
||||
vec2 dest1;
|
||||
is_linear = temp_is_linear;
|
||||
// Portions of the parabola y = x^2 where abs(x) exceeds
|
||||
// this value are treated as straight lines.
|
||||
float thresh = 2.0;
|
||||
if (!bool(is_linear)){
|
||||
vec2 xs = xs_on_clean_parabola(b0, b1, b2);
|
||||
float x0 = xs.x;
|
||||
float x2 = xs.y;
|
||||
if((x0 > thresh && x2 > thresh) || (x0 < -thresh && x2 < -thresh)){
|
||||
is_linear = 1.0;
|
||||
}else{
|
||||
dest0 = vec2(x0, x0 * x0);
|
||||
dest1 = vec2(x2, x2 * x2);
|
||||
}
|
||||
}
|
||||
// Check if is_linear status changed above
|
||||
if (bool(is_linear)){
|
||||
dest0 = vec2(0, 0);
|
||||
dest1 = vec2(1, 0);
|
||||
}
|
||||
|
||||
return map_point_pairs(b0, b2, dest0, dest1);
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// Assumes the following uniforms exist in the surrounding context:
|
||||
// uniform float is_fixed_in_frame;
|
||||
// uniform vec3 camera_offset;
|
||||
// uniform mat3 camera_rotation;
|
||||
|
||||
vec3 rotate_point_into_frame(vec3 point){
|
||||
if(bool(is_fixed_in_frame)){
|
||||
return point;
|
||||
}
|
||||
return camera_rotation * point;
|
||||
}
|
||||
|
||||
|
||||
vec3 position_point_into_frame(vec3 point){
|
||||
if(bool(is_fixed_in_frame)){
|
||||
return point;
|
||||
}
|
||||
return rotate_point_into_frame(point - camera_offset);
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
// Must be inserted in a context with a definition for modify_distance_for_endpoints
|
||||
|
||||
// All of this is with respect to a curve that's been rotated/scaled
|
||||
// so that b0 = (0, 0) and b1 = (1, 0). That is, b2 entirely
|
||||
// determines the shape of the curve
|
||||
|
||||
vec2 bezier(float t, vec2 b2){
|
||||
// Quick returns for the 0 and 1 cases
|
||||
if (t == 0) return vec2(0, 0);
|
||||
else if (t == 1) return b2;
|
||||
// Everything else
|
||||
return vec2(
|
||||
2 * t * (1 - t) + b2.x * t*t,
|
||||
b2.y * t * t
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
float cube_root(float x){
|
||||
return sign(x) * pow(abs(x), 1.0 / 3.0);
|
||||
}
|
||||
|
||||
|
||||
int cubic_solve(float a, float b, float c, float d, out float roots[3]){
|
||||
// Normalize so a = 1
|
||||
b = b / a;
|
||||
c = c / a;
|
||||
d = d / a;
|
||||
|
||||
float p = c - b*b / 3.0;
|
||||
float q = b * (2.0*b*b - 9.0*c) / 27.0 + d;
|
||||
float p3 = p*p*p;
|
||||
float disc = q*q + 4.0*p3 / 27.0;
|
||||
float offset = -b / 3.0;
|
||||
if(disc >= 0.0){
|
||||
float z = sqrt(disc);
|
||||
float u = (-q + z) / 2.0;
|
||||
float v = (-q - z) / 2.0;
|
||||
u = cube_root(u);
|
||||
v = cube_root(v);
|
||||
roots[0] = offset + u + v;
|
||||
return 1;
|
||||
}
|
||||
float u = sqrt(-p / 3.0);
|
||||
float v = acos(-sqrt( -27.0 / p3) * q / 2.0) / 3.0;
|
||||
float m = cos(v);
|
||||
float n = sin(v) * 1.732050808;
|
||||
|
||||
float all_roots[3] = float[3](
|
||||
offset + u * (n - m),
|
||||
offset - u * (n + m),
|
||||
offset + u * (m + m)
|
||||
);
|
||||
|
||||
// Only accept roots with a positive derivative
|
||||
int n_valid_roots = 0;
|
||||
for(int i = 0; i < 3; i++){
|
||||
float r = all_roots[i];
|
||||
if(3*r*r + 2*b*r + c > 0){
|
||||
roots[n_valid_roots] = r;
|
||||
n_valid_roots++;
|
||||
}
|
||||
}
|
||||
return n_valid_roots;
|
||||
}
|
||||
|
||||
float dist_to_line(vec2 p, vec2 b2){
|
||||
float t = clamp(p.x / b2.x, 0, 1);
|
||||
float dist;
|
||||
if(t == 0) dist = length(p);
|
||||
else if(t == 1) dist = distance(p, b2);
|
||||
else dist = abs(p.y);
|
||||
|
||||
return modify_distance_for_endpoints(p, dist, t);
|
||||
}
|
||||
|
||||
|
||||
float dist_to_point_on_curve(vec2 p, float t, vec2 b2){
|
||||
t = clamp(t, 0, 1);
|
||||
return modify_distance_for_endpoints(
|
||||
p, length(p - bezier(t, b2)), t
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
float min_dist_to_curve(vec2 p, vec2 b2, float degree){
|
||||
// Check if curve is really a a line
|
||||
if(degree == 1) return dist_to_line(p, b2);
|
||||
|
||||
// Try finding the exact sdf by solving the equation
|
||||
// (d/dt) dist^2(t) = 0, which amount to the following
|
||||
// cubic.
|
||||
float xm2 = uv_b2.x - 2.0;
|
||||
float y = uv_b2.y;
|
||||
float a = xm2*xm2 + y*y;
|
||||
float b = 3 * xm2;
|
||||
float c = -(p.x*xm2 + p.y*y) + 2;
|
||||
float d = -p.x;
|
||||
|
||||
float roots[3];
|
||||
int n = cubic_solve(a, b, c, d, roots);
|
||||
// At most 2 roots will have been populated.
|
||||
float d0 = dist_to_point_on_curve(p, roots[0], b2);
|
||||
if(n == 1) return d0;
|
||||
float d1 = dist_to_point_on_curve(p, roots[1], b2);
|
||||
return min(d0, d1);
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
float cross2d(vec2 v, vec2 w){
|
||||
return v.x * w.y - w.x * v.y;
|
||||
}
|
||||
|
||||
|
||||
mat3 get_xy_to_uv(vec2 b0, vec2 b1){
|
||||
mat3 shift = mat3(
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
-b0.x, -b0.y, 1.0
|
||||
);
|
||||
|
||||
float sf = length(b1 - b0);
|
||||
vec2 I = (b1 - b0) / sf;
|
||||
vec2 J = vec2(-I.y, I.x);
|
||||
mat3 rotate = mat3(
|
||||
I.x, J.x, 0.0,
|
||||
I.y, J.y, 0.0,
|
||||
0.0, 0.0, 1.0
|
||||
);
|
||||
return (1.0 / sf) * rotate * shift;
|
||||
}
|
||||
|
||||
|
||||
// Orthogonal matrix to convert to a uv space defined so that
|
||||
// b0 goes to [0, 0] and b1 goes to [1, 0]
|
||||
mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){
|
||||
mat4 shift = mat4(
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
-b0.x, -b0.y, -b0.z, 1
|
||||
);
|
||||
|
||||
float scale_factor = length(b1 - b0);
|
||||
vec3 I = (b1 - b0) / scale_factor;
|
||||
vec3 K = unit_normal;
|
||||
vec3 J = cross(K, I);
|
||||
// Transpose (hence inverse) of matrix taking
|
||||
// i-hat to I, k-hat to unit_normal, and j-hat to their cross
|
||||
mat4 rotate = mat4(
|
||||
I.x, J.x, K.x, 0.0,
|
||||
I.y, J.y, K.y, 0.0,
|
||||
I.z, J.z, K.z, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0
|
||||
);
|
||||
return (1.0 / scale_factor) * rotate * shift;
|
||||
}
|
||||
|
||||
|
||||
// Returns 0 for null curve, 1 for linear, 2 for quadratic.
|
||||
// Populates new_points with bezier control points for the curve,
|
||||
// which for quadratics will be the same, but for linear and null
|
||||
// might change. The idea is to inform the caller of the degree,
|
||||
// while also passing tangency information in the linear case.
|
||||
// float get_reduced_control_points(vec3 b0, vec3 b1, vec3 b2, out vec3 new_points[3]){
|
||||
float get_reduced_control_points(in vec3 points[3], out vec3 new_points[3]){
|
||||
float length_threshold = 1e-8;
|
||||
float angle_threshold = 1e-3;
|
||||
|
||||
vec3 p0 = points[0];
|
||||
vec3 p1 = points[1];
|
||||
vec3 p2 = points[2];
|
||||
vec3 v01 = (p1 - p0);
|
||||
vec3 v12 = (p2 - p1);
|
||||
|
||||
float dot_prod = clamp(dot(normalize(v01), normalize(v12)), -1, 1);
|
||||
bool aligned = acos(dot_prod) < angle_threshold;
|
||||
bool distinct_01 = length(v01) > length_threshold; // v01 is considered nonzero
|
||||
bool distinct_12 = length(v12) > length_threshold; // v12 is considered nonzero
|
||||
int n_uniques = int(distinct_01) + int(distinct_12);
|
||||
|
||||
bool quadratic = (n_uniques == 2) && !aligned;
|
||||
bool linear = (n_uniques == 1) || ((n_uniques == 2) && aligned);
|
||||
bool constant = (n_uniques == 0);
|
||||
if(quadratic){
|
||||
new_points[0] = p0;
|
||||
new_points[1] = p1;
|
||||
new_points[2] = p2;
|
||||
return 2.0;
|
||||
}else if(linear){
|
||||
new_points[0] = p0;
|
||||
new_points[1] = 0.5 * (p0 + p2);
|
||||
new_points[2] = p2;
|
||||
return 1.0;
|
||||
}else{
|
||||
new_points[0] = p0;
|
||||
new_points[1] = p0;
|
||||
new_points[2] = p0;
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
19
manimlib/shaders/inserts/rotate.glsl
Normal file
19
manimlib/shaders/inserts/rotate.glsl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
mat3 rotationMatrix(vec3 axis, float angle) {
|
||||
axis = normalize(axis);
|
||||
float s = sin(angle);
|
||||
float c = cos(angle);
|
||||
float oc = 1.0 - c;
|
||||
float ax = axis.x;
|
||||
float ay = axis.y;
|
||||
float az = axis.z;
|
||||
|
||||
return mat3(
|
||||
oc * ax * ax + c, oc * ax * ay - az * s, oc * az * ax + ay * s,
|
||||
oc * ax * ay + az * s, oc * ay * ay + c, oc * ay * az - ax * s,
|
||||
oc * az * ax - ay * s, oc * ay * az + ax * s, oc * az * az + c
|
||||
);
|
||||
}
|
||||
|
||||
vec3 rotate(vec3 vect, float angle, vec3 axis){
|
||||
return rotationMatrix(axis, angle) * vect;
|
||||
}
|
||||
|
|
@ -1,12 +1,5 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 light_source_position;
|
||||
uniform vec3 camera_position;
|
||||
uniform float reflectiveness;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
uniform float focal_distance;
|
||||
|
||||
uniform vec2 parameter;
|
||||
uniform float opacity;
|
||||
uniform float n_steps;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
#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(){
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 light_source_position;
|
||||
uniform vec3 camera_position;
|
||||
uniform float reflectiveness;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
uniform float focal_distance;
|
||||
|
||||
uniform vec4 color0;
|
||||
uniform vec4 color1;
|
||||
uniform vec4 color2;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
#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(){
|
||||
|
|
|
|||
|
|
@ -1,59 +1,29 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec4 color;
|
||||
in float fill_all; // Either 0 or 1
|
||||
in float uv_anti_alias_width;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
in float orientation;
|
||||
in vec2 uv_coords;
|
||||
in vec2 uv_b2;
|
||||
in float bezier_degree;
|
||||
in float is_linear;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
// Needed for quadratic_bezier_distance insertion below
|
||||
float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
||||
return dist;
|
||||
}
|
||||
float sdf(float x0, float y0){
|
||||
if(bool(is_linear)) return abs(y0);
|
||||
|
||||
#INSERT quadratic_bezier_distance.glsl
|
||||
float Fxy = y0 - x0 * x0;
|
||||
if(orientation * Fxy >= 0) return 0.0;
|
||||
|
||||
|
||||
float sdf(){
|
||||
if(bezier_degree < 2){
|
||||
return abs(uv_coords[1]);
|
||||
}
|
||||
float u2 = uv_b2.x;
|
||||
float v2 = uv_b2.y;
|
||||
// For really flat curves, just take the distance to x-axis
|
||||
if(abs(v2 / u2) < 0.1 * uv_anti_alias_width){
|
||||
return abs(uv_coords[1]);
|
||||
}
|
||||
// This converts uv_coords to yet another space where the bezier points sit on
|
||||
// (0, 0), (1/2, 0) and (1, 1), so that the curve can be expressed implicityly
|
||||
// as y = x^2.
|
||||
mat2 to_simple_space = mat2(
|
||||
v2, 0,
|
||||
2 - u2, 4 * v2
|
||||
);
|
||||
vec2 p = to_simple_space * uv_coords;
|
||||
// Sign takes care of whether we should be filling the inside or outside of curve.
|
||||
float sgn = orientation * sign(v2);
|
||||
float Fp = (p.x * p.x - p.y);
|
||||
if(sgn * Fp <= 0){
|
||||
return 0.0;
|
||||
}else{
|
||||
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
|
||||
}
|
||||
return abs(Fxy) / sqrt(1 + 4 * x0 * x0);
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
if (color.a == 0) discard;
|
||||
frag_color = color;
|
||||
if (fill_all == 1.0) return;
|
||||
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
|
||||
if (bool(fill_all)) return;
|
||||
float dist = sdf(uv_coords.x, uv_coords.y);
|
||||
frag_color.a *= smoothstep(1, 0, dist / uv_anti_alias_width);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,9 @@ layout (triangles) in;
|
|||
layout (triangle_strip, max_vertices = 5) out;
|
||||
|
||||
uniform float anti_alias_width;
|
||||
|
||||
// Needed for get_gl_Position
|
||||
uniform vec2 frame_shape;
|
||||
uniform vec2 pixel_shape;
|
||||
uniform float focal_distance;
|
||||
uniform float is_fixed_in_frame;
|
||||
// Needed for finalize_color
|
||||
uniform vec3 light_source_position;
|
||||
uniform vec3 camera_position;
|
||||
uniform float reflectiveness;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
|
||||
in vec3 bp[3];
|
||||
in vec3 verts[3];
|
||||
in float v_orientation[3];
|
||||
in vec4 v_color[3];
|
||||
in float v_vert_index[3];
|
||||
|
|
@ -26,24 +15,21 @@ out vec4 color;
|
|||
out float fill_all;
|
||||
out float uv_anti_alias_width;
|
||||
|
||||
out vec3 xyz_coords;
|
||||
out float orientation;
|
||||
// uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal
|
||||
// uv space is where the curve coincides with y = x^2
|
||||
out vec2 uv_coords;
|
||||
out vec2 uv_b2;
|
||||
out float bezier_degree;
|
||||
out float is_linear;
|
||||
|
||||
vec3 unit_normal;
|
||||
const float ANGLE_THRESHOLD = 1e-3;
|
||||
|
||||
|
||||
// Analog of import for manim only
|
||||
#INSERT quadratic_bezier_geometry_functions.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT get_unit_normal.glsl
|
||||
#INSERT get_xy_to_uv.glsl
|
||||
#INSERT finalize_color.glsl
|
||||
|
||||
|
||||
void emit_vertex_wrapper(vec3 point, int index){
|
||||
void emit_vertex_wrapper(vec3 point, int index, vec3 unit_normal){
|
||||
color = finalize_color(
|
||||
v_color[index],
|
||||
point,
|
||||
|
|
@ -54,71 +40,62 @@ void emit_vertex_wrapper(vec3 point, int index){
|
|||
gloss,
|
||||
shadow
|
||||
);
|
||||
xyz_coords = point;
|
||||
gl_Position = get_gl_Position(xyz_coords);
|
||||
gl_Position = get_gl_Position(point);
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
|
||||
void emit_simple_triangle(){
|
||||
void emit_simple_triangle(vec3 unit_normal){
|
||||
for(int i = 0; i < 3; i++){
|
||||
emit_vertex_wrapper(bp[i], i);
|
||||
emit_vertex_wrapper(verts[i], i, unit_normal);
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
||||
void emit_pentagon(vec3[3] points, vec3 normal){
|
||||
vec3 p0 = points[0];
|
||||
vec3 p1 = points[1];
|
||||
vec3 p2 = points[2];
|
||||
// Tangent vectors
|
||||
vec3 t01 = normalize(p1 - p0);
|
||||
vec3 t12 = normalize(p2 - p1);
|
||||
// Vectors perpendicular to the curve in the plane of the curve pointing outside the curve
|
||||
vec3 p0_perp = cross(t01, normal);
|
||||
vec3 p2_perp = cross(t12, normal);
|
||||
void emit_pentagon(
|
||||
// Triangle vertices
|
||||
vec3 p0,
|
||||
vec3 p1,
|
||||
vec3 p2,
|
||||
// Unit tangent vector
|
||||
vec3 t01,
|
||||
vec3 t12,
|
||||
vec3 unit_normal
|
||||
){
|
||||
// Vectors perpendicular to the curve in the plane of the curve
|
||||
// pointing outside the curve
|
||||
vec3 p0_perp = cross(t01, unit_normal);
|
||||
vec3 p2_perp = cross(t12, unit_normal);
|
||||
|
||||
float angle = acos(clamp(dot(t01, t12), -1, 1));
|
||||
is_linear = float(angle < ANGLE_THRESHOLD);
|
||||
|
||||
bool fill_inside = orientation > 0.0;
|
||||
float aaw = anti_alias_width * frame_shape.y / pixel_shape.y;
|
||||
vec3 corners[5];
|
||||
if(bezier_degree == 1.0){
|
||||
// For straight lines, buff out in both directions
|
||||
corners = vec3[5](
|
||||
p0 + aaw * p0_perp,
|
||||
p0 - aaw * p0_perp,
|
||||
p1 + 0.5 * aaw * (p0_perp + p2_perp),
|
||||
p2 - aaw * p2_perp,
|
||||
p2 + aaw * p2_perp
|
||||
);
|
||||
} else if(fill_inside){
|
||||
// If curved, and filling insight, just buff out away interior
|
||||
corners = vec3[5](
|
||||
p0 + aaw * p0_perp,
|
||||
p0,
|
||||
p1 + 0.5 * aaw * (p0_perp + p2_perp),
|
||||
p2,
|
||||
p2 + aaw * p2_perp
|
||||
);
|
||||
}else{
|
||||
corners = vec3[5](
|
||||
p0,
|
||||
p0 - aaw * p0_perp,
|
||||
p1,
|
||||
p2 - aaw * p2_perp,
|
||||
p2
|
||||
);
|
||||
vec3 corners[5] = vec3[5](p0, p0, p1, p2, p2);
|
||||
|
||||
if(fill_inside || bool(is_linear)){
|
||||
// Add buffer outside the curve
|
||||
corners[0] += aaw * p0_perp;
|
||||
corners[2] += 0.5 * aaw * (p0_perp + p2_perp);
|
||||
corners[4] += aaw * p2_perp;
|
||||
}
|
||||
if(!fill_inside || bool(is_linear)){
|
||||
// Add buffer inside the curve
|
||||
corners[1] -= aaw * p0_perp;
|
||||
corners[3] -= aaw * p2_perp;
|
||||
}
|
||||
|
||||
mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, normal);
|
||||
uv_b2 = (xyz_to_uv * vec4(p2, 1)).xy;
|
||||
uv_anti_alias_width = aaw / length(p1 - p0);
|
||||
// Compute xy_to_uv matrix, and potentially re-evaluate bezier degree
|
||||
mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear);
|
||||
uv_anti_alias_width = aaw * length(xy_to_uv[0].xy);
|
||||
|
||||
for(int i = 0; i < 5; i++){
|
||||
vec3 corner = corners[i];
|
||||
uv_coords = (xyz_to_uv * vec4(corner, 1)).xy;
|
||||
int j = int(sign(i - 1) + 1); // Maps i = [0, 1, 2, 3, 4] onto j = [0, 0, 1, 2, 2]
|
||||
emit_vertex_wrapper(corner, j);
|
||||
vec3 corner = corners[i];
|
||||
uv_coords = (xy_to_uv * vec3(corner.xy, 1.0)).xy;
|
||||
emit_vertex_wrapper(corner, j, unit_normal);
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
|
|
@ -131,19 +108,24 @@ void main(){
|
|||
(v_vert_index[2] - v_vert_index[1]) != 1.0
|
||||
);
|
||||
|
||||
if(fill_all == 1.0){
|
||||
emit_simple_triangle();
|
||||
vec3 p0 = verts[0];
|
||||
vec3 p1 = verts[1];
|
||||
vec3 p2 = verts[2];
|
||||
vec3 t01 = p1 - p0;
|
||||
vec3 t12 = p2 - p1;
|
||||
vec3 unit_normal = normalize(cross(t01, t12));
|
||||
|
||||
if(bool(fill_all)){
|
||||
emit_simple_triangle(unit_normal);
|
||||
return;
|
||||
}
|
||||
orientation = v_orientation[1];
|
||||
|
||||
vec3 new_bp[3];
|
||||
bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), new_bp);
|
||||
unit_normal = get_unit_normal(new_bp);
|
||||
orientation = v_orientation[0];
|
||||
|
||||
if(bezier_degree >= 1){
|
||||
emit_pentagon(new_bp, unit_normal);
|
||||
}
|
||||
// Don't emit any vertices for bezier_degree 0
|
||||
emit_pentagon(
|
||||
p0, p1, p2,
|
||||
normalize(t01),
|
||||
normalize(t12),
|
||||
unit_normal
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in float orientation;
|
||||
in vec4 color;
|
||||
in float vert_index;
|
||||
|
||||
out vec3 bp; // Bezier control point
|
||||
out vec3 verts; // Bezier control point
|
||||
out float v_orientation;
|
||||
out vec4 v_color;
|
||||
out float v_vert_index;
|
||||
|
||||
// Analog of import for manim only
|
||||
#INSERT position_point_into_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
|
||||
void main(){
|
||||
bp = position_point_into_frame(point);
|
||||
verts = position_point_into_frame(point);
|
||||
v_orientation = orientation;
|
||||
v_color = color;
|
||||
v_vert_index = vert_index;
|
||||
|
|
|
|||
|
|
@ -1,92 +1,58 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec2 uv_coords;
|
||||
in vec2 uv_b2;
|
||||
|
||||
in float uv_stroke_width;
|
||||
in vec4 color;
|
||||
in float uv_anti_alias_width;
|
||||
in vec4 color;
|
||||
|
||||
in float has_prev;
|
||||
in float has_next;
|
||||
in float bevel_start;
|
||||
in float bevel_end;
|
||||
in float angle_from_prev;
|
||||
in float angle_to_next;
|
||||
|
||||
in float bezier_degree;
|
||||
in float is_linear;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
const float QUICK_DIST_WIDTH = 0.2;
|
||||
|
||||
float cross2d(vec2 v, vec2 w){
|
||||
return v.x * w.y - w.x * v.y;
|
||||
}
|
||||
// Distance from (x0, y0) to the curve y = x^2
|
||||
float dist_to_curve(float x0, float y0){
|
||||
// In the linear case, the curve will have
|
||||
// been set to equal the x axis
|
||||
if(bool(is_linear)) return y0;
|
||||
|
||||
|
||||
float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
||||
float buff = 0.5 * uv_stroke_width - uv_anti_alias_width;
|
||||
// Check the beginning of the curve
|
||||
if(t == 0){
|
||||
// Clip the start
|
||||
if(has_prev == 0) return max(dist, -p.x + buff);
|
||||
// Bevel start
|
||||
if(bevel_start == 1){
|
||||
float a = angle_from_prev;
|
||||
mat2 rot = mat2(
|
||||
cos(a), sin(a),
|
||||
-sin(a), cos(a)
|
||||
);
|
||||
// Dist for intersection of two lines
|
||||
float bevel_d = max(abs(p.y), abs((rot * p).y));
|
||||
// Dist for union of this intersection with the real curve
|
||||
// intersected with radius 2 away from curve to smooth out
|
||||
// really sharp corners
|
||||
return max(min(dist, bevel_d), dist / 2);
|
||||
}
|
||||
// Otherwise, start will be rounded off
|
||||
}else if(t == 1){
|
||||
// Check the end of the curve
|
||||
// TODO, too much code repetition
|
||||
vec2 v21 = (bezier_degree == 2) ? vec2(1, 0) - uv_b2 : vec2(-1, 0);
|
||||
float len_v21 = length(v21);
|
||||
if(len_v21 == 0){
|
||||
v21 = -uv_b2;
|
||||
len_v21 = length(v21);
|
||||
}
|
||||
|
||||
float perp_dist = dot(p - uv_b2, v21) / len_v21;
|
||||
if(has_next == 0) return max(dist, -perp_dist + buff);
|
||||
// Bevel end
|
||||
if(bevel_end == 1){
|
||||
float a = -angle_to_next;
|
||||
mat2 rot = mat2(
|
||||
cos(a), sin(a),
|
||||
-sin(a), cos(a)
|
||||
);
|
||||
vec2 v21_unit = v21 / length(v21);
|
||||
float bevel_d = max(
|
||||
abs(cross2d(p - uv_b2, v21_unit)),
|
||||
abs(cross2d((rot * (p - uv_b2)), v21_unit))
|
||||
);
|
||||
return max(min(dist, bevel_d), dist / 2);
|
||||
}
|
||||
// Otherwise, end will be rounded off
|
||||
if(uv_stroke_width < QUICK_DIST_WIDTH){
|
||||
// This is a quick approximation for computing
|
||||
// the distance to the curve.
|
||||
// Evaluate F(x, y) = y - x^2
|
||||
// divide by its gradient's magnitude
|
||||
return (y0 - x0 * x0) / sqrt(1 + 4 * x0 * x0);
|
||||
}
|
||||
return dist;
|
||||
// Otherwise, solve for the minimal distance.
|
||||
// The distance squared between (x0, y0) and a point (x, x^2) looks like
|
||||
//
|
||||
// (x0 - x)^2 + (y0 - x^2)^2 = x^4 + (1 - 2y0)x^2 - 2x0 * x + (x0^2 + y0^2)
|
||||
//
|
||||
// Setting the derivative equal to zero (and rescaling) looks like
|
||||
//
|
||||
// x^3 + (0.5 - y0) * x - 0.5 * x0 = 0
|
||||
//
|
||||
// Use two rounds of Newton's method
|
||||
float x = x0;
|
||||
float p = (0.5 - y0);
|
||||
float q = -0.5 * x0;
|
||||
for(int i = 0; i < 2; i++){
|
||||
float fx = x * x * x + p * x + q;
|
||||
float dfx = 3 * x * x + p;
|
||||
x = x - fx / dfx;
|
||||
}
|
||||
return distance(uv_coords, vec2(x, x * x));
|
||||
}
|
||||
|
||||
|
||||
#INSERT quadratic_bezier_distance.glsl
|
||||
|
||||
|
||||
void main() {
|
||||
if (uv_stroke_width == 0) discard;
|
||||
float dist_to_curve = min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
|
||||
// An sdf for the region around the curve we wish to color.
|
||||
float signed_dist = abs(dist_to_curve) - 0.5 * uv_stroke_width;
|
||||
|
||||
// Compute sdf for the region around the curve we wish to color.
|
||||
float dist = dist_to_curve(uv_coords.x, uv_coords.y);
|
||||
float signed_dist = abs(dist) - 0.5 * uv_stroke_width;
|
||||
|
||||
frag_color = color;
|
||||
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width);
|
||||
|
|
|
|||
|
|
@ -1,104 +1,58 @@
|
|||
#version 330
|
||||
|
||||
layout (triangles) in;
|
||||
layout (triangle_strip, max_vertices = 5) out;
|
||||
|
||||
// Needed for get_gl_Position
|
||||
uniform vec2 frame_shape;
|
||||
uniform vec2 pixel_shape;
|
||||
uniform float focal_distance;
|
||||
uniform float is_fixed_in_frame;
|
||||
layout (triangle_strip, max_vertices = 6) out;
|
||||
|
||||
uniform float anti_alias_width;
|
||||
uniform float flat_stroke;
|
||||
|
||||
//Needed for lighting
|
||||
uniform vec3 light_source_position;
|
||||
uniform vec3 camera_position;
|
||||
uniform vec2 pixel_shape;
|
||||
uniform float joint_type;
|
||||
uniform float reflectiveness;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
|
||||
in vec3 bp[3];
|
||||
in vec3 prev_bp[3];
|
||||
in vec3 next_bp[3];
|
||||
in vec3 verts[3];
|
||||
|
||||
in vec4 v_color[3];
|
||||
in float v_joint_angle[3];
|
||||
in float v_stroke_width[3];
|
||||
in vec4 v_color[3];
|
||||
in float v_vert_index[3];
|
||||
|
||||
out vec4 color;
|
||||
out float uv_stroke_width;
|
||||
out float uv_anti_alias_width;
|
||||
|
||||
out float has_prev;
|
||||
out float has_next;
|
||||
out float bevel_start;
|
||||
out float bevel_end;
|
||||
out float angle_from_prev;
|
||||
out float angle_to_next;
|
||||
|
||||
out float bezier_degree;
|
||||
out float is_linear;
|
||||
|
||||
out vec2 uv_coords;
|
||||
out vec2 uv_b2;
|
||||
|
||||
vec3 unit_normal;
|
||||
|
||||
// Codes for joint types
|
||||
const float AUTO_JOINT = 0;
|
||||
const float ROUND_JOINT = 1;
|
||||
const float BEVEL_JOINT = 2;
|
||||
const float MITER_JOINT = 3;
|
||||
const int NO_JOINT = 0;
|
||||
const int AUTO_JOINT = 1;
|
||||
const int BEVEL_JOINT = 2;
|
||||
const int MITER_JOINT = 3;
|
||||
|
||||
const float PI = 3.141592653;
|
||||
const float ANGLE_THRESHOLD = 1e-3;
|
||||
|
||||
|
||||
#INSERT quadratic_bezier_geometry_functions.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT get_unit_normal.glsl
|
||||
#INSERT get_xy_to_uv.glsl
|
||||
#INSERT finalize_color.glsl
|
||||
|
||||
|
||||
void flatten_points(in vec3[3] points, out vec2[3] flat_points){
|
||||
for(int i = 0; i < 3; i++){
|
||||
float sf = perspective_scale_factor(points[i].z, focal_distance);
|
||||
flat_points[i] = sf * points[i].xy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float angle_between_vectors(vec2 v1, vec2 v2){
|
||||
float v1_norm = length(v1);
|
||||
float v2_norm = length(v2);
|
||||
if(v1_norm == 0 || v2_norm == 0) return 0.0;
|
||||
float dp = dot(v1, v2) / (v1_norm * v2_norm);
|
||||
float angle = acos(clamp(dp, -1.0, 1.0));
|
||||
float sn = sign(cross2d(v1, v2));
|
||||
return sn * angle;
|
||||
}
|
||||
|
||||
|
||||
bool find_intersection(vec2 p0, vec2 v0, vec2 p1, vec2 v1, out vec2 intersection){
|
||||
// Find the intersection of a line passing through
|
||||
// p0 in the direction v0 and one passing through p1 in
|
||||
// the direction p1.
|
||||
// That is, find a solutoin to p0 + v0 * t = p1 + v1 * s
|
||||
float det = -v0.x * v1.y + v1.x * v0.y;
|
||||
if(det == 0) return false;
|
||||
float t = cross2d(p0 - p1, v1) / det;
|
||||
intersection = p0 + v0 * t;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void create_joint(float angle, vec2 unit_tan, float buff,
|
||||
vec2 static_c0, out vec2 changing_c0,
|
||||
vec2 static_c1, out vec2 changing_c1){
|
||||
void create_joint(
|
||||
float angle,
|
||||
vec3 unit_tan,
|
||||
float buff,
|
||||
vec3 static_c0,
|
||||
out vec3 changing_c0,
|
||||
vec3 static_c1,
|
||||
out vec3 changing_c1
|
||||
){
|
||||
float shift;
|
||||
if(abs(angle) < 1e-3){
|
||||
// if(abs(angle) < ANGLE_THRESHOLD || abs(angle) > 0.99 * PI || int(joint_type) == NO_JOINT){
|
||||
if(abs(angle) < ANGLE_THRESHOLD || int(joint_type) == NO_JOINT){
|
||||
// No joint
|
||||
shift = 0;
|
||||
}else if(joint_type == MITER_JOINT){
|
||||
}else if(int(joint_type) == MITER_JOINT){
|
||||
shift = buff * (-1.0 - cos(angle)) / sin(angle);
|
||||
}else{
|
||||
// For a Bevel joint
|
||||
|
|
@ -108,160 +62,121 @@ void create_joint(float angle, vec2 unit_tan, float buff,
|
|||
changing_c1 = static_c1 + shift * unit_tan;
|
||||
}
|
||||
|
||||
|
||||
// This function is responsible for finding the corners of
|
||||
// a bounding region around the bezier curve, which can be
|
||||
// emitted as a triangle fan
|
||||
int get_corners(vec2 controls[3], int degree, float stroke_widths[3], out vec2 corners[5]){
|
||||
vec2 p0 = controls[0];
|
||||
vec2 p1 = controls[1];
|
||||
vec2 p2 = controls[2];
|
||||
// emitted as a triangle fan, with vertices vaguely close
|
||||
// to control points so that the passage of vert data to
|
||||
// frag shaders is most natural.
|
||||
void get_corners(
|
||||
// Control points for a bezier curve
|
||||
vec3 p0,
|
||||
vec3 p1,
|
||||
vec3 p2,
|
||||
// Unit tangent vectors at p0 and p2
|
||||
vec3 v01,
|
||||
vec3 v12,
|
||||
float stroke_width0,
|
||||
float stroke_width2,
|
||||
// Unit normal to the whole curve
|
||||
vec3 normal,
|
||||
// Anti-alias width
|
||||
float aaw,
|
||||
float angle_from_prev,
|
||||
float angle_to_next,
|
||||
out vec3 corners[6]
|
||||
){
|
||||
|
||||
// Unit vectors for directions between control points
|
||||
vec2 v10 = normalize(p0 - p1);
|
||||
vec2 v12 = normalize(p2 - p1);
|
||||
vec2 v01 = -v10;
|
||||
vec2 v21 = -v12;
|
||||
float buff0 = 0.5 * stroke_width0 + aaw;
|
||||
float buff2 = 0.5 * stroke_width2 + aaw;
|
||||
|
||||
vec2 p0_perp = vec2(-v01.y, v01.x); // Pointing to the left of the curve from p0
|
||||
vec2 p2_perp = vec2(-v12.y, v12.x); // Pointing to the left of the curve from p2
|
||||
// Add correction for sharp angles to prevent weird bevel effects (Needed?)
|
||||
float thresh = 5 * PI / 6;
|
||||
if(angle_from_prev > thresh) buff0 *= 2 * sin(angle_from_prev);
|
||||
if(angle_to_next > thresh) buff2 *= 2 * sin(angle_to_next);
|
||||
|
||||
// aaw is the added width given around the polygon for antialiasing.
|
||||
// In case the normal is faced away from (0, 0, 1), the vector to the
|
||||
// camera, this is scaled up.
|
||||
float aaw = anti_alias_width * frame_shape.y / pixel_shape.y;
|
||||
float buff0 = 0.5 * stroke_widths[0] + aaw;
|
||||
float buff2 = 0.5 * stroke_widths[2] + aaw;
|
||||
float aaw0 = (1 - has_prev) * aaw;
|
||||
float aaw2 = (1 - has_next) * aaw;
|
||||
// Perpendicular vectors to the left of the curve
|
||||
vec3 p0_perp = buff0 * normalize(cross(normal, v01));
|
||||
vec3 p2_perp = buff2 * normalize(cross(normal, v12));
|
||||
vec3 p1_perp = 0.5 * (p0_perp + p2_perp);
|
||||
|
||||
vec2 c0 = p0 - buff0 * p0_perp + aaw0 * v10;
|
||||
vec2 c1 = p0 + buff0 * p0_perp + aaw0 * v10;
|
||||
vec2 c2 = p2 + buff2 * p2_perp + aaw2 * v12;
|
||||
vec2 c3 = p2 - buff2 * p2_perp + aaw2 * v12;
|
||||
// The order of corners should be for a triangle_strip.
|
||||
vec3 c0 = p0 + p0_perp;
|
||||
vec3 c1 = p0 - p0_perp;
|
||||
vec3 c2 = p1 + p1_perp;
|
||||
vec3 c3 = p1 - p1_perp;
|
||||
vec3 c4 = p2 + p2_perp;
|
||||
vec3 c5 = p2 - p2_perp;
|
||||
float orientation = dot(normal, cross(v01, v12));
|
||||
// Move the inner middle control point to make
|
||||
// room for the curve
|
||||
if(orientation > 0.0) c2 = 0.5 * (c0 + c4);
|
||||
else if(orientation < 0.0) c3 = 0.5 * (c1 + c5);
|
||||
|
||||
// Account for previous and next control points
|
||||
if(has_prev > 0) create_joint(angle_from_prev, v01, buff0, c0, c0, c1, c1);
|
||||
if(has_next > 0) create_joint(angle_to_next, v21, buff2, c3, c3, c2, c2);
|
||||
create_joint(angle_from_prev, v01, buff0, c1, c1, c0, c0);
|
||||
create_joint(angle_to_next, -v12, buff2, c5, c5, c4, c4);
|
||||
|
||||
// Linear case is the simplest
|
||||
if(degree == 1){
|
||||
// The order of corners should be for a triangle_strip. Last entry is a dummy
|
||||
corners = vec2[5](c0, c1, c3, c2, vec2(0.0));
|
||||
return 4;
|
||||
}
|
||||
// Otherwise, form a pentagon around the curve
|
||||
float orientation = sign(cross2d(v01, v12)); // Positive for ccw curves
|
||||
if(orientation > 0) corners = vec2[5](c0, c1, p1, c2, c3);
|
||||
else corners = vec2[5](c1, c0, p1, c3, c2);
|
||||
// Replace corner[2] with convex hull point accounting for stroke width
|
||||
find_intersection(corners[0], v01, corners[4], v21, corners[2]);
|
||||
return 5;
|
||||
}
|
||||
|
||||
|
||||
void set_adjascent_info(vec2 c0, vec2 tangent,
|
||||
int degree,
|
||||
vec2 adj[3],
|
||||
out float bevel,
|
||||
out float angle
|
||||
){
|
||||
bool linear_adj = (angle_between_vectors(adj[1] - adj[0], adj[2] - adj[1]) < 1e-3);
|
||||
angle = angle_between_vectors(c0 - adj[1], tangent);
|
||||
// Decide on joint type
|
||||
bool one_linear = (degree == 1 || linear_adj);
|
||||
bool should_bevel = (
|
||||
(joint_type == AUTO_JOINT && one_linear) ||
|
||||
joint_type == BEVEL_JOINT
|
||||
);
|
||||
bevel = should_bevel ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
|
||||
void find_joint_info(vec2 controls[3], vec2 prev[3], vec2 next[3], int degree){
|
||||
float tol = 1e-6;
|
||||
|
||||
// Made as floats not bools so they can be passed to the frag shader
|
||||
has_prev = float(distance(prev[2], controls[0]) < tol);
|
||||
has_next = float(distance(next[0], controls[2]) < tol);
|
||||
|
||||
if(bool(has_prev)){
|
||||
vec2 tangent = controls[1] - controls[0];
|
||||
set_adjascent_info(
|
||||
controls[0], tangent, degree, prev,
|
||||
bevel_start, angle_from_prev
|
||||
);
|
||||
}
|
||||
if(bool(has_next)){
|
||||
vec2 tangent = controls[1] - controls[2];
|
||||
set_adjascent_info(
|
||||
controls[2], tangent, degree, next,
|
||||
bevel_end, angle_to_next
|
||||
);
|
||||
angle_to_next *= -1;
|
||||
}
|
||||
corners = vec3[6](c0, c1, c2, c3, c4, c5);
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
// Convert control points to a standard form if they are linear or null
|
||||
vec3 controls[3];
|
||||
vec3 prev[3];
|
||||
vec3 next[3];
|
||||
unit_normal = get_unit_normal(controls);
|
||||
bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), controls);
|
||||
if(bezier_degree == 0.0) return; // Null curve
|
||||
int degree = int(bezier_degree);
|
||||
get_reduced_control_points(vec3[3](prev_bp[0], prev_bp[1], prev_bp[2]), prev);
|
||||
get_reduced_control_points(vec3[3](next_bp[0], next_bp[1], next_bp[2]), next);
|
||||
// We use the triangle strip primative, but
|
||||
// actually only need every other strip element
|
||||
if (int(v_vert_index[0]) % 2 == 1) return;
|
||||
|
||||
// Curves are marked as eneded when the handle after
|
||||
// the first anchor is set equal to that anchor
|
||||
if (verts[0] == verts[1]) return;
|
||||
|
||||
// Adjust stroke width based on distance from the camera
|
||||
float scaled_strokes[3];
|
||||
for(int i = 0; i < 3; i++){
|
||||
float sf = perspective_scale_factor(controls[i].z, focal_distance);
|
||||
if(bool(flat_stroke)){
|
||||
vec3 to_cam = normalize(vec3(0.0, 0.0, focal_distance) - controls[i]);
|
||||
sf *= abs(dot(unit_normal, to_cam));
|
||||
}
|
||||
scaled_strokes[i] = v_stroke_width[i] * sf;
|
||||
// TODO, track true unit normal globally (probably as a uniform)
|
||||
vec3 unit_normal = vec3(0.0, 0.0, 1.0);
|
||||
if(bool(flat_stroke)){
|
||||
unit_normal = camera_rotation * vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// Control points are projected to the xy plane before drawing, which in turn
|
||||
// gets tranlated to a uv plane. The z-coordinate information will be remembered
|
||||
// by what's sent out to gl_Position, and by how it affects the lighting and stroke width
|
||||
vec2 flat_controls[3];
|
||||
vec2 flat_prev[3];
|
||||
vec2 flat_next[3];
|
||||
flatten_points(controls, flat_controls);
|
||||
flatten_points(prev, flat_prev);
|
||||
flatten_points(next, flat_next);
|
||||
vec3 p0 = verts[0];
|
||||
vec3 p1 = verts[1];
|
||||
vec3 p2 = verts[2];
|
||||
vec3 v01 = normalize(p1 - p0);
|
||||
vec3 v12 = normalize(p2 - p1);
|
||||
|
||||
find_joint_info(flat_controls, flat_prev, flat_next, degree);
|
||||
float angle = acos(clamp(dot(v01, v12), -1, 1));
|
||||
is_linear = float(abs(angle) < ANGLE_THRESHOLD);
|
||||
|
||||
// Corners of a bounding region around curve
|
||||
vec2 corners[5];
|
||||
int n_corners = get_corners(flat_controls, degree, scaled_strokes, corners);
|
||||
// If the curve is flat, put the middle control in the midpoint
|
||||
if (bool(is_linear)) p1 = 0.5 * (p0 + p2);
|
||||
|
||||
int index_map[5] = int[5](0, 0, 1, 2, 2);
|
||||
if(n_corners == 4) index_map[2] = 2;
|
||||
// We want to change the coordinates to a space where the curve
|
||||
// coincides with y = x^2, between some values x0 and x2. Or, in
|
||||
// the case of a linear curve (bezier degree 1), just put it on
|
||||
// the segment from (0, 0) to (1, 0)
|
||||
mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear);
|
||||
|
||||
// Find uv conversion matrix
|
||||
mat3 xy_to_uv = get_xy_to_uv(flat_controls[0], flat_controls[1]);
|
||||
float scale_factor = length(flat_controls[1] - flat_controls[0]);
|
||||
uv_anti_alias_width = anti_alias_width * frame_shape.y / pixel_shape.y / scale_factor;
|
||||
uv_b2 = (xy_to_uv * vec3(flat_controls[2], 1.0)).xy;
|
||||
float uv_scale_factor = length(xy_to_uv[0].xy);
|
||||
float scaled_aaw = anti_alias_width * (frame_shape.y / pixel_shape.y);
|
||||
uv_anti_alias_width = uv_scale_factor * scaled_aaw;
|
||||
|
||||
vec3 corners[6];
|
||||
get_corners(
|
||||
p0, p1, p2, v01, v12,
|
||||
v_stroke_width[0],
|
||||
v_stroke_width[2],
|
||||
unit_normal,
|
||||
scaled_aaw,
|
||||
v_joint_angle[0],
|
||||
v_joint_angle[2],
|
||||
corners
|
||||
);
|
||||
|
||||
// Emit each corner
|
||||
for(int i = 0; i < n_corners; i++){
|
||||
uv_coords = (xy_to_uv * vec3(corners[i], 1.0)).xy;
|
||||
uv_stroke_width = scaled_strokes[index_map[i]] / scale_factor;
|
||||
// Apply some lighting to the color before sending out.
|
||||
// vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z);
|
||||
vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z);
|
||||
for(int i = 0; i < 6; i++){
|
||||
int vert_index = i / 2;
|
||||
uv_coords = (xy_to_uv * vec3(corners[i].xy, 1)).xy;
|
||||
uv_stroke_width = uv_scale_factor * v_stroke_width[vert_index];
|
||||
color = finalize_color(
|
||||
v_color[index_map[i]],
|
||||
xyz_coords,
|
||||
v_color[vert_index],
|
||||
corners[i],
|
||||
unit_normal,
|
||||
light_source_position,
|
||||
camera_position,
|
||||
|
|
@ -269,10 +184,7 @@ void main() {
|
|||
gloss,
|
||||
shadow
|
||||
);
|
||||
gl_Position = vec4(
|
||||
get_gl_Position(vec3(corners[i], 0.0)).xy,
|
||||
get_gl_Position(controls[index_map[i]]).zw
|
||||
);
|
||||
gl_Position = get_gl_Position(corners[i]);
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
|
|
|
|||
|
|
@ -1,31 +1,28 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in vec3 prev_point;
|
||||
in vec3 next_point;
|
||||
|
||||
in float joint_angle;
|
||||
in float stroke_width;
|
||||
in vec4 color;
|
||||
|
||||
// Bezier control point
|
||||
out vec3 bp;
|
||||
out vec3 prev_bp;
|
||||
out vec3 next_bp;
|
||||
out vec3 verts;
|
||||
|
||||
out float v_joint_angle;
|
||||
out float v_stroke_width;
|
||||
out vec4 v_color;
|
||||
out float v_vert_index;
|
||||
|
||||
const float STROKE_WIDTH_CONVERSION = 0.01;
|
||||
|
||||
#INSERT position_point_into_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
|
||||
void main(){
|
||||
bp = position_point_into_frame(point);
|
||||
prev_bp = position_point_into_frame(prev_point);
|
||||
next_bp = position_point_into_frame(next_point);
|
||||
verts = position_point_into_frame(point);
|
||||
|
||||
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * frame_shape[1] / 8.0;
|
||||
v_joint_angle = joint_angle;
|
||||
v_color = color;
|
||||
v_vert_index = gl_VertexID;
|
||||
}
|
||||
|
|
@ -1,12 +1,8 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
|
||||
// Analog of import for manim only
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT position_point_into_frame.glsl
|
||||
|
||||
void main(){
|
||||
gl_Position = get_gl_Position(position_point_into_frame(point));
|
||||
|
|
|
|||
|
|
@ -1,29 +1,8 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 light_source_position;
|
||||
uniform vec3 camera_position;
|
||||
uniform float reflectiveness;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
uniform float focal_distance;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
in vec3 v_normal;
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
#INSERT finalize_color.glsl
|
||||
|
||||
void main() {
|
||||
frag_color = finalize_color(
|
||||
v_color,
|
||||
xyz_coords,
|
||||
normalize(v_normal),
|
||||
light_source_position,
|
||||
camera_position,
|
||||
reflectiveness,
|
||||
gloss,
|
||||
shadow
|
||||
);
|
||||
frag_color = v_color;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
uniform vec4 clip_plane;
|
||||
|
||||
in vec3 point;
|
||||
in vec3 du_point;
|
||||
|
|
@ -11,13 +11,28 @@ out vec3 xyz_coords;
|
|||
out vec3 v_normal;
|
||||
out vec4 v_color;
|
||||
|
||||
#INSERT position_point_into_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT get_rotated_surface_unit_normal_vector.glsl
|
||||
#INSERT finalize_color.glsl
|
||||
|
||||
void main(){
|
||||
xyz_coords = position_point_into_frame(point);
|
||||
v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point);
|
||||
v_color = color;
|
||||
gl_Position = get_gl_Position(xyz_coords);
|
||||
|
||||
if(clip_plane.xyz != vec3(0.0, 0.0, 0.0)){
|
||||
gl_ClipDistance[0] = dot(vec4(point, 1.0), clip_plane);
|
||||
}
|
||||
|
||||
v_color = finalize_color(
|
||||
color,
|
||||
xyz_coords,
|
||||
v_normal,
|
||||
light_source_position,
|
||||
camera_position,
|
||||
reflectiveness,
|
||||
gloss,
|
||||
shadow
|
||||
);
|
||||
}
|
||||
|
|
@ -3,12 +3,6 @@
|
|||
uniform sampler2D LightTexture;
|
||||
uniform sampler2D DarkTexture;
|
||||
uniform float num_textures;
|
||||
uniform vec3 light_source_position;
|
||||
uniform vec3 camera_position;
|
||||
uniform float reflectiveness;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
uniform float focal_distance;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
in vec3 v_normal;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in vec3 du_point;
|
||||
in vec3 dv_point;
|
||||
|
|
@ -13,7 +11,6 @@ out vec3 v_normal;
|
|||
out vec2 v_im_coords;
|
||||
out float v_opacity;
|
||||
|
||||
#INSERT position_point_into_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT get_rotated_surface_unit_normal_vector.glsl
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 light_source_position;
|
||||
uniform vec3 camera_position;
|
||||
uniform float reflectiveness;
|
||||
uniform float gloss;
|
||||
uniform float shadow;
|
||||
uniform float anti_alias_width;
|
||||
uniform float focal_distance;
|
||||
uniform float glow_factor;
|
||||
|
||||
in vec4 color;
|
||||
in float radius;
|
||||
in vec2 center;
|
||||
in vec2 point;
|
||||
in float scaled_aaw;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
|
|
@ -22,7 +17,7 @@ void main() {
|
|||
vec2 diff = point - center;
|
||||
float dist = length(diff);
|
||||
float signed_dist = dist - radius;
|
||||
if (signed_dist > 0.5 * anti_alias_width){
|
||||
if (signed_dist > 0.5 * scaled_aaw){
|
||||
discard;
|
||||
}
|
||||
frag_color = color;
|
||||
|
|
@ -43,5 +38,5 @@ void main() {
|
|||
frag_color.a *= pow(1 - dist / radius, glow_factor);
|
||||
}
|
||||
|
||||
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / anti_alias_width);
|
||||
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / scaled_aaw);
|
||||
}
|
||||
|
|
@ -3,11 +3,8 @@
|
|||
layout (points) in;
|
||||
layout (triangle_strip, max_vertices = 4) out;
|
||||
|
||||
// Needed for get_gl_Position
|
||||
uniform vec2 frame_shape;
|
||||
uniform float focal_distance;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform float anti_alias_width;
|
||||
uniform vec2 pixel_shape;
|
||||
|
||||
in vec3 v_point[1];
|
||||
in float v_radius[1];
|
||||
|
|
@ -17,6 +14,7 @@ out vec4 color;
|
|||
out float radius;
|
||||
out vec2 center;
|
||||
out vec2 point;
|
||||
out float scaled_aaw;
|
||||
|
||||
#INSERT get_gl_Position.glsl
|
||||
|
||||
|
|
@ -25,8 +23,9 @@ void main() {
|
|||
radius = v_radius[0];
|
||||
center = v_point[0].xy;
|
||||
|
||||
scaled_aaw = (frame_shape.y / pixel_shape.y);
|
||||
radius = v_radius[0] / max(1.0 - v_point[0].z / focal_distance / frame_shape.y, 0.0);
|
||||
float rpa = radius + anti_alias_width;
|
||||
float rpa = radius + scaled_aaw;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
// To account for perspective
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
#version 330
|
||||
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in float radius;
|
||||
in vec4 color;
|
||||
|
|
@ -10,7 +8,7 @@ out vec3 v_point;
|
|||
out float v_radius;
|
||||
out vec4 v_color;
|
||||
|
||||
#INSERT position_point_into_frame.glsl
|
||||
#INSERT get_gl_Position.glsl
|
||||
|
||||
void main(){
|
||||
v_point = position_point_into_frame(point);
|
||||
|
|
|
|||
|
|
@ -36,3 +36,4 @@ if TYPE_CHECKING:
|
|||
Vect2Array = Annotated[FloatArray, Literal["N", 2]]
|
||||
Vect3Array = Annotated[FloatArray, Literal["N", 3]]
|
||||
Vect4Array = Annotated[FloatArray, Literal["N", 4]]
|
||||
VectNArray = Annotated[FloatArray, Literal["N", "M"]]
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ from manimlib.utils.space_ops import midpoint
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Sequence, TypeVar
|
||||
from manimlib.typing import VectN, FloatArray
|
||||
from typing import Callable, Sequence, TypeVar, Tuple
|
||||
from manimlib.typing import VectN, FloatArray, VectNArray
|
||||
|
||||
Scalable = TypeVar("Scalable", float, FloatArray)
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ CLOSED_THRESHOLD = 0.001
|
|||
|
||||
|
||||
def bezier(
|
||||
points: Sequence[Scalable]
|
||||
points: Sequence[Scalable] | VectNArray
|
||||
) -> Callable[[float], Scalable]:
|
||||
if len(points) == 0:
|
||||
raise Exception("bezier cannot be calld on an empty list")
|
||||
|
|
@ -69,10 +69,10 @@ def partial_bezier_points(
|
|||
# Shortened version of partial_bezier_points just for quadratics,
|
||||
# since this is called a fair amount
|
||||
def partial_quadratic_bezier_points(
|
||||
points: Sequence[Scalable],
|
||||
points: Sequence[VectN] | VectNArray,
|
||||
a: float,
|
||||
b: float
|
||||
) -> list[Scalable]:
|
||||
) -> list[VectN]:
|
||||
if a == 1:
|
||||
return 3 * [points[-1]]
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ def get_smooth_quadratic_bezier_handle_points(
|
|||
|
||||
|
||||
def get_smooth_cubic_bezier_handle_points(
|
||||
points: Sequence[VectN]
|
||||
points: Sequence[VectN] | VectNArray
|
||||
) -> tuple[FloatArray, FloatArray]:
|
||||
points = np.array(points)
|
||||
num_handles = len(points) - 1
|
||||
|
|
@ -292,7 +292,7 @@ def get_quadratic_approximation_of_cubic(
|
|||
h0: FloatArray,
|
||||
h1: FloatArray,
|
||||
a1: FloatArray
|
||||
) -> np.ndarray:
|
||||
) -> FloatArray:
|
||||
a0 = np.array(a0, ndmin=2)
|
||||
h0 = np.array(h0, ndmin=2)
|
||||
h1 = np.array(h1, ndmin=2)
|
||||
|
|
@ -350,13 +350,12 @@ def get_quadratic_approximation_of_cubic(
|
|||
i1 = find_intersection(a1, T1, mid, Tm)
|
||||
|
||||
m, n = np.shape(a0)
|
||||
result = np.zeros((6 * m, n))
|
||||
result[0::6] = a0
|
||||
result[1::6] = i0
|
||||
result[2::6] = mid
|
||||
result[3::6] = mid
|
||||
result[4::6] = i1
|
||||
result[5::6] = a1
|
||||
result = np.zeros((5 * m, n))
|
||||
result[0::5] = a0
|
||||
result[1::5] = i0
|
||||
result[2::5] = mid
|
||||
result[3::5] = i1
|
||||
result[4::5] = a1
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ def make_even(
|
|||
)
|
||||
|
||||
|
||||
def arrays_match(arr1: np.ndarray, arr2: np.ndarray) -> bool:
|
||||
return arr1.shape == arr2.shape and (arr1 == arr2).all()
|
||||
|
||||
|
||||
def hash_obj(obj: object) -> int:
|
||||
if isinstance(obj, dict):
|
||||
return hash(tuple(sorted([
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ def clip(a: float, min_a: float, max_a: float) -> float:
|
|||
return a
|
||||
|
||||
|
||||
def arr_clip(arr: np.ndarray, min_a: float, max_a: float) -> np.ndarray:
|
||||
arr[arr < min_a] = min_a
|
||||
arr[arr > max_a] = max_a
|
||||
return arr
|
||||
|
||||
|
||||
def fdiv(a: Scalable, b: Scalable, zero_over_zero_value: Scalable | None = None) -> Scalable:
|
||||
if zero_over_zero_value is not None:
|
||||
out = np.full_like(a, zero_over_zero_value)
|
||||
|
|
|
|||
|
|
@ -22,12 +22,20 @@ if TYPE_CHECKING:
|
|||
from manimlib.typing import Vect2, Vect3, Vect4, VectN, Matrix3x3, Vect3Array, Vect2Array
|
||||
|
||||
|
||||
def cross(v1: Vect3 | List[float], v2: Vect3 | List[float]) -> Vect3:
|
||||
return np.array([
|
||||
v1[1] * v2[2] - v1[2] * v2[1],
|
||||
v1[2] * v2[0] - v1[0] * v2[2],
|
||||
v1[0] * v2[1] - v1[1] * v2[0]
|
||||
def cross(v1: Vect3 | List[float], v2: Vect3 | List[float]) -> Vect3 | Vect3Array:
|
||||
is2d = isinstance(v1, np.ndarray) and len(v1.shape) == 2
|
||||
if is2d:
|
||||
x1, y1, z1 = v1[:, 0], v1[:, 1], v1[:, 2]
|
||||
x2, y2, z2 = v2[:, 0], v2[:, 1], v2[:, 2]
|
||||
else:
|
||||
x1, y1, z1 = v1
|
||||
x2, y2, z2 = v2
|
||||
result = np.array([
|
||||
y1 * z2 - z1 * y2,
|
||||
z1 * x2 - x1 * z2,
|
||||
x1 * y2 - y1 * x2,
|
||||
])
|
||||
return result.T if is2d else result
|
||||
|
||||
|
||||
def get_norm(vect: VectN | List[float]) -> float:
|
||||
|
|
@ -134,15 +142,16 @@ def rotation_about_z(angle: float) -> Matrix3x3:
|
|||
|
||||
|
||||
def rotation_between_vectors(v1: Vect3, v2: Vect3) -> Matrix3x3:
|
||||
if np.isclose(v1, v2).all():
|
||||
atol = 1e-8
|
||||
if get_norm(v1 - v2) < atol:
|
||||
return np.identity(3)
|
||||
axis = np.cross(v1, v2)
|
||||
if np.isclose(axis, [0, 0, 0]).all():
|
||||
axis = cross(v1, v2)
|
||||
if get_norm(axis) < atol:
|
||||
# v1 and v2 align
|
||||
axis = np.cross(v1, RIGHT)
|
||||
if np.isclose(axis, [0, 0, 0]).all():
|
||||
axis = cross(v1, RIGHT)
|
||||
if get_norm(axis) < atol:
|
||||
# v1 and v2 _and_ RIGHT all align
|
||||
axis = np.cross(v1, UP)
|
||||
axis = cross(v1, UP)
|
||||
return rotation_matrix(
|
||||
angle=angle_between_vectors(v1, v2),
|
||||
axis=axis,
|
||||
|
|
@ -157,7 +166,7 @@ def angle_of_vector(vector: Vect2 | Vect3) -> float:
|
|||
"""
|
||||
Returns polar coordinate theta when vector is project on xy plane
|
||||
"""
|
||||
return np.angle(complex(*vector[:2]))
|
||||
return math.atan2(vector[1], vector[0])
|
||||
|
||||
|
||||
def angle_between_vectors(v1: VectN, v2: VectN) -> float:
|
||||
|
|
@ -184,8 +193,7 @@ def normalize_along_axis(
|
|||
) -> np.ndarray:
|
||||
norms = np.sqrt((array * array).sum(axis))
|
||||
norms[norms == 0] = 1
|
||||
buffed_norms = np.repeat(norms, array.shape[axis]).reshape(array.shape)
|
||||
return array / buffed_norms
|
||||
return (array.T / norms).T
|
||||
|
||||
|
||||
def get_unit_normal(
|
||||
|
|
@ -271,41 +279,61 @@ def line_intersection(
|
|||
|
||||
|
||||
def find_intersection(
|
||||
p0: Vect3,
|
||||
v0: Vect3,
|
||||
p1: Vect3,
|
||||
v1: Vect3,
|
||||
threshold: float = 1e-5
|
||||
p0: Vect3 | Vect3Array,
|
||||
v0: Vect3 | Vect3Array,
|
||||
p1: Vect3 | Vect3Array,
|
||||
v1: Vect3 | Vect3Array,
|
||||
threshold: float = 1e-5,
|
||||
) -> Vect3:
|
||||
"""
|
||||
Return the intersection of a line passing through p0 in direction v0
|
||||
with one passing through p1 in direction v1. (Or array of intersections
|
||||
from arrays of such points/directions).
|
||||
|
||||
For 3d values, it returns the point on the ray p0 + v0 * t closest to the
|
||||
ray p1 + v1 * t
|
||||
"""
|
||||
p0 = np.array(p0, ndmin=2)
|
||||
v0 = np.array(v0, ndmin=2)
|
||||
p1 = np.array(p1, ndmin=2)
|
||||
v1 = np.array(v1, ndmin=2)
|
||||
m, n = np.shape(p0)
|
||||
assert(n in [2, 3])
|
||||
|
||||
numer = np.cross(v1, p1 - p0)
|
||||
denom = np.cross(v1, v0)
|
||||
if n == 3:
|
||||
d = len(np.shape(numer))
|
||||
new_numer = np.multiply(numer, numer).sum(d - 1)
|
||||
new_denom = np.multiply(denom, numer).sum(d - 1)
|
||||
numer, denom = new_numer, new_denom
|
||||
|
||||
denom[abs(denom) < threshold] = np.inf # So that ratio goes to 0 there
|
||||
d = len(p0.shape)
|
||||
if d == 1:
|
||||
is_3d = any(arr[2] for arr in (p0, v0, p1, v1))
|
||||
else:
|
||||
is_3d = any(z for arr in (p0, v0, p1, v1) for z in arr.T[2])
|
||||
if not is_3d:
|
||||
numer = np.array(cross2d(v1, p1 - p0))
|
||||
denom = np.array(cross2d(v1, v0))
|
||||
else:
|
||||
cp1 = cross(v1, p1 - p0)
|
||||
cp2 = cross(v1, v0)
|
||||
numer = np.array((cp1 * cp1).sum(d - 1))
|
||||
denom = np.array((cp1 * cp2).sum(d - 1))
|
||||
denom[abs(denom) < threshold] = np.inf
|
||||
ratio = numer / denom
|
||||
ratio = np.repeat(ratio, n).reshape((m, n))
|
||||
result = p0 + ratio * v0
|
||||
if m == 1:
|
||||
return result[0]
|
||||
return result
|
||||
return p0 + (ratio * v0.T).T
|
||||
|
||||
|
||||
def line_intersects_path(
|
||||
start: Vect2 | Vect3,
|
||||
end: Vect2 | Vect3,
|
||||
path: Vect2Array | Vect3Array,
|
||||
) -> bool:
|
||||
"""
|
||||
Tests whether the line (start, end) intersects
|
||||
a polygonal path defined by its vertices
|
||||
"""
|
||||
n = len(path) - 1
|
||||
p1 = np.empty((n, 2))
|
||||
q1 = np.empty((n, 2))
|
||||
p1[:] = start[:2]
|
||||
q1[:] = end[:2]
|
||||
p2 = path[:-1, :2]
|
||||
q2 = path[1:, :2]
|
||||
|
||||
v1 = q1 - p1
|
||||
v2 = q2 - p2
|
||||
|
||||
mis1 = cross2d(v1, p2 - p1) * cross2d(v1, q2 - p1) < 0
|
||||
mis2 = cross2d(v2, p1 - p2) * cross2d(v2, q1 - p2) < 0
|
||||
return bool((mis1 * mis2).any())
|
||||
|
||||
|
||||
def get_closest_point_on_line(a: VectN, b: VectN, p: VectN) -> VectN:
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ def num_tex_symbols(tex: str) -> int:
|
|||
rf"(\\{s})" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?"
|
||||
for s in ["begin", "end", "phantom"]
|
||||
)
|
||||
for tup in re.findall(pattern, tex):
|
||||
tex = tex.replace("".join(tup), " ")
|
||||
tex = re.sub(pattern, "", tex)
|
||||
|
||||
# Progressively count the symbols associated with certain tex commands,
|
||||
# and remove those commands from the string, adding the number of symbols
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue