mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 12:32:36 +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 arrays_match
|
||||
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 earclip_triangulation
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
@ -57,13 +58,13 @@ class VMobject(Mobject):
|
||||
('point', np.float32, (3,)),
|
||||
('stroke_rgba', np.float32, (4,)),
|
||||
('stroke_width', np.float32, (1,)),
|
||||
('joint_angle', np.float32, (1,)),
|
||||
('joint_product', np.float32, (4,)),
|
||||
('fill_rgba', np.float32, (4,)),
|
||||
('orientation', np.float32, (1,)),
|
||||
('vert_index', np.float32, (1,)),
|
||||
])
|
||||
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
|
||||
stroke_render_primitive: int = moderngl.TRIANGLE_STRIP
|
||||
@ -86,7 +87,7 @@ class VMobject(Mobject):
|
||||
long_lines: bool = False,
|
||||
# Could also be "no_joint", "bevel", "miter"
|
||||
joint_type: str = "auto",
|
||||
flat_stroke: bool = False,
|
||||
flat_stroke: bool = True,
|
||||
use_simple_quadratic_approx: bool = False,
|
||||
# Measured in pixel widths
|
||||
anti_alias_width: float = 1.0,
|
||||
@ -107,7 +108,7 @@ class VMobject(Mobject):
|
||||
|
||||
self.needs_new_triangulation = True
|
||||
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')
|
||||
|
||||
super().__init__(**kwargs)
|
||||
@ -1042,25 +1043,30 @@ class VMobject(Mobject):
|
||||
self.needs_new_triangulation = False
|
||||
return tri_indices
|
||||
|
||||
def refresh_joint_angles(self):
|
||||
def refresh_joint_products(self):
|
||||
for mob in self.get_family():
|
||||
mob.needs_new_joint_angles = True
|
||||
mob.needs_new_joint_products = 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"]
|
||||
if "joint_angle" in self.locked_data_keys:
|
||||
return self.data["joint_angle"]
|
||||
def recompute_joint_products(self, refresh: bool = False):
|
||||
"""
|
||||
The 'joint product' is a 4-vector holding the cross and dot
|
||||
product between tangent vectors at a joint
|
||||
"""
|
||||
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()
|
||||
|
||||
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_to_h = normalize_along_axis(h - a0, 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[1::2] = h_to_a1
|
||||
|
||||
# Joint up closed loops, or mark unclosed paths as such
|
||||
ends = self.get_subpath_end_indices()
|
||||
starts = [0, *(e + 2 for e in ends[:-1])]
|
||||
for start, end in zip(starts, ends):
|
||||
if start > len(a0_to_h):
|
||||
continue
|
||||
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]
|
||||
vect_to_vert[start] = vect_from_vert[end - 1]
|
||||
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
|
||||
# the joint_angles array
|
||||
result = self.data["joint_angle"][:, 0]
|
||||
result[:] = 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
|
||||
# Compute dot and cross products
|
||||
cross(
|
||||
vect_to_vert, vect_from_vert,
|
||||
out=self.data["joint_product"][:, :3]
|
||||
)
|
||||
self.data["joint_product"][:, 3] = (vect_to_vert * vect_from_vert).sum(1)
|
||||
return self.data["joint_product"]
|
||||
|
||||
def triggers_refreshed_triangulation(func: Callable):
|
||||
@wraps(func)
|
||||
@ -1098,7 +1104,7 @@ class VMobject(Mobject):
|
||||
func(self, *args, **kwargs)
|
||||
if refresh:
|
||||
self.refresh_triangulation()
|
||||
self.refresh_joint_angles()
|
||||
self.refresh_joint_products()
|
||||
return wrapper
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
@ -1152,7 +1158,7 @@ class VMobject(Mobject):
|
||||
|
||||
def apply_points_function(self, *args, **kwargs):
|
||||
super().apply_points_function(*args, **kwargs)
|
||||
self.refresh_joint_angles()
|
||||
self.refresh_joint_products()
|
||||
|
||||
# For shaders
|
||||
def init_shader_data(self):
|
||||
@ -1203,7 +1209,7 @@ class VMobject(Mobject):
|
||||
fill_datas.append(submob.data[fill_names])
|
||||
fill_indices.append(submob.get_triangulation())
|
||||
if submob.has_stroke():
|
||||
submob.get_joint_angles()
|
||||
submob.recompute_joint_products()
|
||||
if submob.stroke_behind:
|
||||
lst = back_stroke_data
|
||||
else:
|
||||
|
@ -10,7 +10,7 @@ uniform float joint_type;
|
||||
|
||||
in vec3 verts[3];
|
||||
|
||||
in float v_joint_angle[3];
|
||||
in vec4 v_joint_product[3];
|
||||
in float v_stroke_width[3];
|
||||
in vec4 v_color[3];
|
||||
in float v_vert_index[3];
|
||||
@ -38,8 +38,24 @@ const float ANGLE_THRESHOLD = 1e-3;
|
||||
#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(
|
||||
float angle,
|
||||
float cos_angle,
|
||||
vec3 unit_tan,
|
||||
float buff,
|
||||
vec3 static_c0,
|
||||
@ -47,16 +63,20 @@ void create_joint(
|
||||
vec3 static_c1,
|
||||
out vec3 changing_c1
|
||||
){
|
||||
float shift;
|
||||
// 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){
|
||||
if(cos_angle > (1.0 - ANGLE_THRESHOLD) || int(joint_type) == NO_JOINT){
|
||||
// No joint
|
||||
shift = 0;
|
||||
}else if(int(joint_type) == MITER_JOINT){
|
||||
shift = buff * (-1.0 - cos(angle)) / sin(angle);
|
||||
changing_c0 = static_c0;
|
||||
changing_c1 = static_c1;
|
||||
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{
|
||||
// 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_c1 = static_c1 + shift * unit_tan;
|
||||
@ -75,28 +95,35 @@ void get_corners(
|
||||
// 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]
|
||||
){
|
||||
|
||||
float buff0 = 0.5 * stroke_width0 + aaw;
|
||||
float buff2 = 0.5 * stroke_width2 + aaw;
|
||||
float buff0 = 0.5 * v_stroke_width[0] + aaw;
|
||||
float buff2 = 0.5 * v_stroke_width[2] + aaw;
|
||||
|
||||
// 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);
|
||||
// Add correction for sharp angles to prevent weird bevel effects
|
||||
if(v_joint_product[0].w < -0.5) buff0 *= -2 * v_joint_product[0].w;
|
||||
if(v_joint_product[2].w < -0.5) buff2 *= -2 * v_joint_product[0].w;
|
||||
|
||||
// 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
|
||||
vec3 p0_perp = buff0 * normalize(cross(normal, v01));
|
||||
vec3 p2_perp = buff2 * normalize(cross(normal, v12));
|
||||
vec3 p0_perp;
|
||||
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);
|
||||
|
||||
// The order of corners should be for a triangle_strip.
|
||||
@ -106,20 +133,19 @@ void get_corners(
|
||||
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);
|
||||
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);
|
||||
|
||||
// Account for previous and next control points
|
||||
create_joint(angle_from_prev, v01, buff0, c1, c1, c0, c0);
|
||||
create_joint(angle_to_next, -v12, buff2, c5, c5, c4, c4);
|
||||
create_joint(v_joint_product[0].w, v01, buff0, c1, c1, c0, c0);
|
||||
create_joint(v_joint_product[2].w, -v12, buff2, c5, c5, c4, c4);
|
||||
|
||||
corners = vec3[6](c0, c1, c2, c3, c4, c5);
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
// We use the triangle strip primative, but
|
||||
// actually only need every other strip element
|
||||
@ -129,12 +155,6 @@ void main() {
|
||||
// the first anchor is set equal to that anchor
|
||||
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 p1 = verts[1];
|
||||
vec3 p2 = verts[2];
|
||||
@ -144,6 +164,7 @@ void main() {
|
||||
float angle = acos(clamp(dot(v01, v12), -1, 1));
|
||||
is_linear = float(abs(angle) < ANGLE_THRESHOLD);
|
||||
|
||||
|
||||
// If the curve is flat, put the middle control in the midpoint
|
||||
if (bool(is_linear)) p1 = 0.5 * (p0 + p2);
|
||||
|
||||
@ -158,16 +179,7 @@ void main() {
|
||||
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
|
||||
);
|
||||
get_corners(p0, p1, p2, v01, v12, scaled_aaw, corners);
|
||||
|
||||
// Emit each corner
|
||||
for(int i = 0; i < 6; i++){
|
||||
@ -177,9 +189,9 @@ void main() {
|
||||
color = finalize_color(
|
||||
v_color[vert_index],
|
||||
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();
|
||||
}
|
||||
EndPrimitive();
|
||||
|
@ -1,27 +1,27 @@
|
||||
#version 330
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
|
||||
in vec3 point;
|
||||
in vec4 stroke_rgba;
|
||||
in float stroke_width;
|
||||
in vec3 joint_normal;
|
||||
in float joint_angle;
|
||||
in vec4 joint_product;
|
||||
|
||||
// Bezier control point
|
||||
out vec3 verts;
|
||||
|
||||
out float v_joint_angle;
|
||||
out vec4 v_joint_product;
|
||||
out float v_stroke_width;
|
||||
out vec4 v_color;
|
||||
out float v_vert_index;
|
||||
|
||||
const float STROKE_WIDTH_CONVERSION = 0.01;
|
||||
|
||||
#INSERT get_gl_Position.glsl
|
||||
|
||||
void main(){
|
||||
verts = position_point_into_frame(point);
|
||||
verts = point;
|
||||
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_vert_index = gl_VertexID;
|
||||
}
|
Reference in New Issue
Block a user