mirror of
https://github.com/3b1b/manim.git
synced 2025-07-29 04:53:34 +08:00
Track full cross product and dot product of tangent vectors at joints
And alter the convention of what flat_stroke means to be more sensible in 3d
This commit is contained in:
@ -31,6 +31,7 @@ from manimlib.utils.iterables import resize_with_interpolation
|
|||||||
from manimlib.utils.iterables import resize_preserving_order
|
from manimlib.utils.iterables import resize_preserving_order
|
||||||
from manimlib.utils.iterables import arrays_match
|
from manimlib.utils.iterables import arrays_match
|
||||||
from manimlib.utils.space_ops import angle_between_vectors
|
from manimlib.utils.space_ops import angle_between_vectors
|
||||||
|
from manimlib.utils.space_ops import cross
|
||||||
from manimlib.utils.space_ops import cross2d
|
from manimlib.utils.space_ops import cross2d
|
||||||
from manimlib.utils.space_ops import earclip_triangulation
|
from manimlib.utils.space_ops import earclip_triangulation
|
||||||
from manimlib.utils.space_ops import get_norm
|
from manimlib.utils.space_ops import get_norm
|
||||||
@ -57,13 +58,13 @@ class VMobject(Mobject):
|
|||||||
('point', np.float32, (3,)),
|
('point', np.float32, (3,)),
|
||||||
('stroke_rgba', np.float32, (4,)),
|
('stroke_rgba', np.float32, (4,)),
|
||||||
('stroke_width', np.float32, (1,)),
|
('stroke_width', np.float32, (1,)),
|
||||||
('joint_angle', np.float32, (1,)),
|
('joint_product', np.float32, (4,)),
|
||||||
('fill_rgba', np.float32, (4,)),
|
('fill_rgba', np.float32, (4,)),
|
||||||
('orientation', np.float32, (1,)),
|
('orientation', np.float32, (1,)),
|
||||||
('vert_index', np.float32, (1,)),
|
('vert_index', np.float32, (1,)),
|
||||||
])
|
])
|
||||||
fill_data_names = ['point', 'fill_rgba', 'orientation', 'vert_index']
|
fill_data_names = ['point', 'fill_rgba', 'orientation', 'vert_index']
|
||||||
stroke_data_names = ['point', 'stroke_rgba', 'stroke_width', 'joint_angle']
|
stroke_data_names = ['point', 'stroke_rgba', 'stroke_width', 'joint_product']
|
||||||
|
|
||||||
fill_render_primitive: int = moderngl.TRIANGLES
|
fill_render_primitive: int = moderngl.TRIANGLES
|
||||||
stroke_render_primitive: int = moderngl.TRIANGLE_STRIP
|
stroke_render_primitive: int = moderngl.TRIANGLE_STRIP
|
||||||
@ -86,7 +87,7 @@ class VMobject(Mobject):
|
|||||||
long_lines: bool = False,
|
long_lines: bool = False,
|
||||||
# Could also be "no_joint", "bevel", "miter"
|
# Could also be "no_joint", "bevel", "miter"
|
||||||
joint_type: str = "auto",
|
joint_type: str = "auto",
|
||||||
flat_stroke: bool = False,
|
flat_stroke: bool = True,
|
||||||
use_simple_quadratic_approx: bool = False,
|
use_simple_quadratic_approx: bool = False,
|
||||||
# Measured in pixel widths
|
# Measured in pixel widths
|
||||||
anti_alias_width: float = 1.0,
|
anti_alias_width: float = 1.0,
|
||||||
@ -107,7 +108,7 @@ class VMobject(Mobject):
|
|||||||
|
|
||||||
self.needs_new_triangulation = True
|
self.needs_new_triangulation = True
|
||||||
self.triangulation = np.zeros(0, dtype='i4')
|
self.triangulation = np.zeros(0, dtype='i4')
|
||||||
self.needs_new_joint_angles = True
|
self.needs_new_joint_products = True
|
||||||
self.outer_vert_indices = np.zeros(0, dtype='i4')
|
self.outer_vert_indices = np.zeros(0, dtype='i4')
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@ -1042,25 +1043,30 @@ class VMobject(Mobject):
|
|||||||
self.needs_new_triangulation = False
|
self.needs_new_triangulation = False
|
||||||
return tri_indices
|
return tri_indices
|
||||||
|
|
||||||
def refresh_joint_angles(self):
|
def refresh_joint_products(self):
|
||||||
for mob in self.get_family():
|
for mob in self.get_family():
|
||||||
mob.needs_new_joint_angles = True
|
mob.needs_new_joint_products = True
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_joint_angles(self, refresh: bool = False):
|
def recompute_joint_products(self, refresh: bool = False):
|
||||||
if not self.needs_new_joint_angles and not refresh:
|
"""
|
||||||
return self.data["joint_angle"]
|
The 'joint product' is a 4-vector holding the cross and dot
|
||||||
if "joint_angle" in self.locked_data_keys:
|
product between tangent vectors at a joint
|
||||||
return self.data["joint_angle"]
|
"""
|
||||||
|
if not self.needs_new_joint_products and not refresh:
|
||||||
|
return self.data["joint_product"]
|
||||||
|
|
||||||
self.needs_new_joint_angles = False
|
if "joint_product" in self.locked_data_keys:
|
||||||
|
return self.data["joint_product"]
|
||||||
|
|
||||||
|
self.needs_new_joint_products = False
|
||||||
|
|
||||||
points = self.get_points()
|
points = self.get_points()
|
||||||
|
|
||||||
if(len(points) < 3):
|
if(len(points) < 3):
|
||||||
return self.data["joint_angle"]
|
return self.data["joint_product"]
|
||||||
|
|
||||||
# Unit tangent vectors
|
# Find all the unit tangent vectors at each joint
|
||||||
a0, h, a1 = points[0:-1:2], points[1::2], points[2::2]
|
a0, h, a1 = points[0:-1:2], points[1::2], points[2::2]
|
||||||
a0_to_h = normalize_along_axis(h - a0, 1)
|
a0_to_h = normalize_along_axis(h - a0, 1)
|
||||||
h_to_a1 = normalize_along_axis(a1 - h, 1)
|
h_to_a1 = normalize_along_axis(a1 - h, 1)
|
||||||
@ -1073,24 +1079,24 @@ class VMobject(Mobject):
|
|||||||
vect_from_vert[0:-1:2] = a0_to_h
|
vect_from_vert[0:-1:2] = a0_to_h
|
||||||
vect_from_vert[1::2] = h_to_a1
|
vect_from_vert[1::2] = h_to_a1
|
||||||
|
|
||||||
|
# Joint up closed loops, or mark unclosed paths as such
|
||||||
ends = self.get_subpath_end_indices()
|
ends = self.get_subpath_end_indices()
|
||||||
starts = [0, *(e + 2 for e in ends[:-1])]
|
starts = [0, *(e + 2 for e in ends[:-1])]
|
||||||
for start, end in zip(starts, ends):
|
for start, end in zip(starts, ends):
|
||||||
if start > len(a0_to_h):
|
|
||||||
continue
|
|
||||||
if self.consider_points_equal(points[start], points[end]):
|
if self.consider_points_equal(points[start], points[end]):
|
||||||
vect_to_vert[start] = h_to_a1[end // 2 - 1]
|
vect_to_vert[start] = vect_from_vert[end - 1]
|
||||||
vect_from_vert[end] = a0_to_h[start // 2]
|
vect_from_vert[end] = vect_to_vert[start + 1]
|
||||||
|
else:
|
||||||
|
vect_to_vert[start] = vect_from_vert[start]
|
||||||
|
vect_from_vert[end] = vect_to_vert[end]
|
||||||
|
|
||||||
# Compute angles, and read them into
|
# Compute dot and cross products
|
||||||
# the joint_angles array
|
cross(
|
||||||
result = self.data["joint_angle"][:, 0]
|
vect_to_vert, vect_from_vert,
|
||||||
result[:] = 0
|
out=self.data["joint_product"][:, :3]
|
||||||
dots = (vect_to_vert * vect_from_vert).sum(1)
|
)
|
||||||
np.arccos(dots, out=result, where=((dots <= 1) & (dots >= -1)))
|
self.data["joint_product"][:, 3] = (vect_to_vert * vect_from_vert).sum(1)
|
||||||
# Assumes unit normal in the positive z direction
|
return self.data["joint_product"]
|
||||||
result *= np.sign(cross2d(vect_to_vert, vect_from_vert))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def triggers_refreshed_triangulation(func: Callable):
|
def triggers_refreshed_triangulation(func: Callable):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
@ -1098,7 +1104,7 @@ class VMobject(Mobject):
|
|||||||
func(self, *args, **kwargs)
|
func(self, *args, **kwargs)
|
||||||
if refresh:
|
if refresh:
|
||||||
self.refresh_triangulation()
|
self.refresh_triangulation()
|
||||||
self.refresh_joint_angles()
|
self.refresh_joint_products()
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@triggers_refreshed_triangulation
|
@triggers_refreshed_triangulation
|
||||||
@ -1152,7 +1158,7 @@ class VMobject(Mobject):
|
|||||||
|
|
||||||
def apply_points_function(self, *args, **kwargs):
|
def apply_points_function(self, *args, **kwargs):
|
||||||
super().apply_points_function(*args, **kwargs)
|
super().apply_points_function(*args, **kwargs)
|
||||||
self.refresh_joint_angles()
|
self.refresh_joint_products()
|
||||||
|
|
||||||
# For shaders
|
# For shaders
|
||||||
def init_shader_data(self):
|
def init_shader_data(self):
|
||||||
@ -1203,7 +1209,7 @@ class VMobject(Mobject):
|
|||||||
fill_datas.append(submob.data[fill_names])
|
fill_datas.append(submob.data[fill_names])
|
||||||
fill_indices.append(submob.get_triangulation())
|
fill_indices.append(submob.get_triangulation())
|
||||||
if submob.has_stroke():
|
if submob.has_stroke():
|
||||||
submob.get_joint_angles()
|
submob.recompute_joint_products()
|
||||||
if submob.stroke_behind:
|
if submob.stroke_behind:
|
||||||
lst = back_stroke_data
|
lst = back_stroke_data
|
||||||
else:
|
else:
|
||||||
|
@ -10,7 +10,7 @@ uniform float joint_type;
|
|||||||
|
|
||||||
in vec3 verts[3];
|
in vec3 verts[3];
|
||||||
|
|
||||||
in float v_joint_angle[3];
|
in vec4 v_joint_product[3];
|
||||||
in float v_stroke_width[3];
|
in float v_stroke_width[3];
|
||||||
in vec4 v_color[3];
|
in vec4 v_color[3];
|
||||||
in float v_vert_index[3];
|
in float v_vert_index[3];
|
||||||
@ -38,8 +38,24 @@ const float ANGLE_THRESHOLD = 1e-3;
|
|||||||
#INSERT finalize_color.glsl
|
#INSERT finalize_color.glsl
|
||||||
|
|
||||||
|
|
||||||
|
vec3 get_joint_normal(vec4 joint_product){
|
||||||
|
vec3 result = joint_product.xyz;
|
||||||
|
float norm = length(result);
|
||||||
|
if(norm < 1e-8){
|
||||||
|
// If it's too short, use the middle joint angle
|
||||||
|
result = v_joint_product[1].xyz;
|
||||||
|
norm = length(result);
|
||||||
|
}
|
||||||
|
if(norm < 1e-8){
|
||||||
|
// If that's also to short, just return unit z vector
|
||||||
|
return vec3(0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
return result / norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void create_joint(
|
void create_joint(
|
||||||
float angle,
|
float cos_angle,
|
||||||
vec3 unit_tan,
|
vec3 unit_tan,
|
||||||
float buff,
|
float buff,
|
||||||
vec3 static_c0,
|
vec3 static_c0,
|
||||||
@ -47,16 +63,20 @@ void create_joint(
|
|||||||
vec3 static_c1,
|
vec3 static_c1,
|
||||||
out vec3 changing_c1
|
out vec3 changing_c1
|
||||||
){
|
){
|
||||||
float shift;
|
if(cos_angle > (1.0 - ANGLE_THRESHOLD) || int(joint_type) == NO_JOINT){
|
||||||
// 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
|
// No joint
|
||||||
shift = 0;
|
changing_c0 = static_c0;
|
||||||
}else if(int(joint_type) == MITER_JOINT){
|
changing_c1 = static_c1;
|
||||||
shift = buff * (-1.0 - cos(angle)) / sin(angle);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float shift;
|
||||||
|
float sin_angle = sqrt(1.0 - cos_angle * cos_angle);
|
||||||
|
if(int(joint_type) == MITER_JOINT){
|
||||||
|
shift = buff * (-1.0 - cos_angle) / sin_angle;
|
||||||
}else{
|
}else{
|
||||||
// For a Bevel joint
|
// For a Bevel joint
|
||||||
shift = buff * (1.0 - cos(angle)) / sin(angle);
|
shift = buff * (1.0 - cos_angle) / sin_angle;
|
||||||
}
|
}
|
||||||
changing_c0 = static_c0 - shift * unit_tan;
|
changing_c0 = static_c0 - shift * unit_tan;
|
||||||
changing_c1 = static_c1 + shift * unit_tan;
|
changing_c1 = static_c1 + shift * unit_tan;
|
||||||
@ -75,28 +95,35 @@ void get_corners(
|
|||||||
// Unit tangent vectors at p0 and p2
|
// Unit tangent vectors at p0 and p2
|
||||||
vec3 v01,
|
vec3 v01,
|
||||||
vec3 v12,
|
vec3 v12,
|
||||||
float stroke_width0,
|
|
||||||
float stroke_width2,
|
|
||||||
// Unit normal to the whole curve
|
|
||||||
vec3 normal,
|
|
||||||
// Anti-alias width
|
// Anti-alias width
|
||||||
float aaw,
|
float aaw,
|
||||||
float angle_from_prev,
|
|
||||||
float angle_to_next,
|
|
||||||
out vec3 corners[6]
|
out vec3 corners[6]
|
||||||
){
|
){
|
||||||
|
|
||||||
float buff0 = 0.5 * stroke_width0 + aaw;
|
float buff0 = 0.5 * v_stroke_width[0] + aaw;
|
||||||
float buff2 = 0.5 * stroke_width2 + aaw;
|
float buff2 = 0.5 * v_stroke_width[2] + aaw;
|
||||||
|
|
||||||
// Add correction for sharp angles to prevent weird bevel effects (Needed?)
|
// Add correction for sharp angles to prevent weird bevel effects
|
||||||
float thresh = 5 * PI / 6;
|
if(v_joint_product[0].w < -0.5) buff0 *= -2 * v_joint_product[0].w;
|
||||||
if(angle_from_prev > thresh) buff0 *= 2 * sin(angle_from_prev);
|
if(v_joint_product[2].w < -0.5) buff2 *= -2 * v_joint_product[0].w;
|
||||||
if(angle_to_next > thresh) buff2 *= 2 * sin(angle_to_next);
|
|
||||||
|
// Unit normal and joint angles
|
||||||
|
vec3 normal0 = get_joint_normal(v_joint_product[0]);
|
||||||
|
vec3 normal2 = get_joint_normal(v_joint_product[2]);
|
||||||
|
// Chose the normal in the positive z direction
|
||||||
|
normal0 *= sign(normal0.z);
|
||||||
|
normal2 *= sign(normal2.z);
|
||||||
|
|
||||||
// Perpendicular vectors to the left of the curve
|
// Perpendicular vectors to the left of the curve
|
||||||
vec3 p0_perp = buff0 * normalize(cross(normal, v01));
|
vec3 p0_perp;
|
||||||
vec3 p2_perp = buff2 * normalize(cross(normal, v12));
|
vec3 p2_perp;
|
||||||
|
if(bool(flat_stroke)){
|
||||||
|
p0_perp = buff0 * normalize(cross(normal0, v01));
|
||||||
|
p2_perp = buff2 * normalize(cross(normal2, v12));
|
||||||
|
}else{
|
||||||
|
p0_perp = buff0 * normal0;
|
||||||
|
p2_perp = buff2 * normal2;
|
||||||
|
}
|
||||||
vec3 p1_perp = 0.5 * (p0_perp + p2_perp);
|
vec3 p1_perp = 0.5 * (p0_perp + p2_perp);
|
||||||
|
|
||||||
// The order of corners should be for a triangle_strip.
|
// The order of corners should be for a triangle_strip.
|
||||||
@ -106,20 +133,19 @@ void get_corners(
|
|||||||
vec3 c3 = p1 - p1_perp;
|
vec3 c3 = p1 - p1_perp;
|
||||||
vec3 c4 = p2 + p2_perp;
|
vec3 c4 = p2 + p2_perp;
|
||||||
vec3 c5 = p2 - p2_perp;
|
vec3 c5 = p2 - p2_perp;
|
||||||
float orientation = dot(normal, cross(v01, v12));
|
|
||||||
// Move the inner middle control point to make
|
// Move the inner middle control point to make
|
||||||
// room for the curve
|
// room for the curve
|
||||||
if(orientation > 0.0) c2 = 0.5 * (c0 + c4);
|
float orientation = dot(normal0, cross(v01, v12));
|
||||||
|
if(orientation >= 0.0) c2 = 0.5 * (c0 + c4);
|
||||||
else if(orientation < 0.0) c3 = 0.5 * (c1 + c5);
|
else if(orientation < 0.0) c3 = 0.5 * (c1 + c5);
|
||||||
|
|
||||||
// Account for previous and next control points
|
// Account for previous and next control points
|
||||||
create_joint(angle_from_prev, v01, buff0, c1, c1, c0, c0);
|
create_joint(v_joint_product[0].w, v01, buff0, c1, c1, c0, c0);
|
||||||
create_joint(angle_to_next, -v12, buff2, c5, c5, c4, c4);
|
create_joint(v_joint_product[2].w, -v12, buff2, c5, c5, c4, c4);
|
||||||
|
|
||||||
corners = vec3[6](c0, c1, c2, c3, c4, c5);
|
corners = vec3[6](c0, c1, c2, c3, c4, c5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// We use the triangle strip primative, but
|
// We use the triangle strip primative, but
|
||||||
// actually only need every other strip element
|
// actually only need every other strip element
|
||||||
@ -129,12 +155,6 @@ void main() {
|
|||||||
// the first anchor is set equal to that anchor
|
// the first anchor is set equal to that anchor
|
||||||
if (verts[0] == verts[1]) return;
|
if (verts[0] == verts[1]) return;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 p0 = verts[0];
|
vec3 p0 = verts[0];
|
||||||
vec3 p1 = verts[1];
|
vec3 p1 = verts[1];
|
||||||
vec3 p2 = verts[2];
|
vec3 p2 = verts[2];
|
||||||
@ -144,6 +164,7 @@ void main() {
|
|||||||
float angle = acos(clamp(dot(v01, v12), -1, 1));
|
float angle = acos(clamp(dot(v01, v12), -1, 1));
|
||||||
is_linear = float(abs(angle) < ANGLE_THRESHOLD);
|
is_linear = float(abs(angle) < ANGLE_THRESHOLD);
|
||||||
|
|
||||||
|
|
||||||
// If the curve is flat, put the middle control in the midpoint
|
// If the curve is flat, put the middle control in the midpoint
|
||||||
if (bool(is_linear)) p1 = 0.5 * (p0 + p2);
|
if (bool(is_linear)) p1 = 0.5 * (p0 + p2);
|
||||||
|
|
||||||
@ -158,16 +179,7 @@ void main() {
|
|||||||
uv_anti_alias_width = uv_scale_factor * scaled_aaw;
|
uv_anti_alias_width = uv_scale_factor * scaled_aaw;
|
||||||
|
|
||||||
vec3 corners[6];
|
vec3 corners[6];
|
||||||
get_corners(
|
get_corners(p0, p1, p2, v01, v12, scaled_aaw, 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
|
// Emit each corner
|
||||||
for(int i = 0; i < 6; i++){
|
for(int i = 0; i < 6; i++){
|
||||||
@ -177,9 +189,9 @@ void main() {
|
|||||||
color = finalize_color(
|
color = finalize_color(
|
||||||
v_color[vert_index],
|
v_color[vert_index],
|
||||||
corners[i],
|
corners[i],
|
||||||
unit_normal
|
vec3(0.0, 0.0, 1.0) // TODO
|
||||||
);
|
);
|
||||||
gl_Position = get_gl_Position(corners[i]);
|
gl_Position = get_gl_Position(position_point_into_frame(corners[i]));
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
}
|
}
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
#version 330
|
#version 330
|
||||||
|
|
||||||
|
uniform vec2 frame_shape;
|
||||||
|
|
||||||
in vec3 point;
|
in vec3 point;
|
||||||
in vec4 stroke_rgba;
|
in vec4 stroke_rgba;
|
||||||
in float stroke_width;
|
in float stroke_width;
|
||||||
in vec3 joint_normal;
|
in vec3 joint_normal;
|
||||||
in float joint_angle;
|
in vec4 joint_product;
|
||||||
|
|
||||||
// Bezier control point
|
// Bezier control point
|
||||||
out vec3 verts;
|
out vec3 verts;
|
||||||
|
|
||||||
out float v_joint_angle;
|
out vec4 v_joint_product;
|
||||||
out float v_stroke_width;
|
out float v_stroke_width;
|
||||||
out vec4 v_color;
|
out vec4 v_color;
|
||||||
out float v_vert_index;
|
out float v_vert_index;
|
||||||
|
|
||||||
const float STROKE_WIDTH_CONVERSION = 0.01;
|
const float STROKE_WIDTH_CONVERSION = 0.01;
|
||||||
|
|
||||||
#INSERT get_gl_Position.glsl
|
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
verts = position_point_into_frame(point);
|
verts = point;
|
||||||
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * frame_shape[1] / 8.0;
|
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * frame_shape[1] / 8.0;
|
||||||
v_joint_angle = joint_angle;
|
v_joint_product = joint_product;
|
||||||
v_color = stroke_rgba;
|
v_color = stroke_rgba;
|
||||||
v_vert_index = gl_VertexID;
|
v_vert_index = gl_VertexID;
|
||||||
}
|
}
|
Reference in New Issue
Block a user