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:
Grant Sanderson
2023-01-17 11:36:47 -08:00
parent 4de0d098ea
commit abbe131e8d
3 changed files with 100 additions and 82 deletions

View File

@ -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:

View File

@ -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();

View File

@ -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;
}