mirror of
https://github.com/3b1b/manim.git
synced 2025-08-19 13:01:00 +00:00
Merge pull request #2155 from 3b1b/video-work
Update how stroke is rendered
This commit is contained in:
commit
aad2bded14
10 changed files with 169 additions and 149 deletions
|
@ -488,10 +488,7 @@ class GraphExample(Scene):
|
|||
# with the intent of having other mobjects update based
|
||||
# on the parameter
|
||||
x_tracker = ValueTracker(2)
|
||||
f_always(
|
||||
dot.move_to,
|
||||
lambda: axes.i2gp(x_tracker.get_value(), parabola)
|
||||
)
|
||||
dot.add_updater(lambda d: d.move_to(axes.i2gp(x_tracker.get_value(), parabola)))
|
||||
|
||||
self.play(x_tracker.animate.set_value(4), run_time=3)
|
||||
self.play(x_tracker.animate.set_value(-2), run_time=3)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from scipy.spatial.transform import Rotation
|
||||
|
@ -9,8 +10,10 @@ from pyrr import Matrix44
|
|||
from manimlib.constants import DEGREES, RADIANS
|
||||
from manimlib.constants import FRAME_SHAPE
|
||||
from manimlib.constants import DOWN, LEFT, ORIGIN, OUT, RIGHT, UP
|
||||
from manimlib.constants import PI
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.utils.space_ops import normalize
|
||||
from manimlib.utils.simple_functions import clip
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
@ -62,9 +65,19 @@ class CameraFrame(Mobject):
|
|||
|
||||
def get_euler_angles(self) -> np.ndarray:
|
||||
orientation = self.get_orientation()
|
||||
if all(orientation.as_quat() == [0, 0, 0, 1]):
|
||||
if np.isclose(orientation.as_quat(), [0, 0, 0, 1]).all():
|
||||
return np.zeros(3)
|
||||
return orientation.as_euler(self.euler_axes)[::-1]
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', UserWarning) # Ignore UserWarnings
|
||||
angles = orientation.as_euler(self.euler_axes)[::-1]
|
||||
# Handle Gimble lock case
|
||||
if np.isclose(angles[1], 0, atol=1e-2):
|
||||
angles[0] = angles[0] + angles[2]
|
||||
angles[2] = 0
|
||||
if np.isclose(angles[1], PI, atol=1e-2):
|
||||
angles[0] = angles[0] - angles[2]
|
||||
angles[2] = 0
|
||||
return angles
|
||||
|
||||
def get_theta(self):
|
||||
return self.get_euler_angles()[0]
|
||||
|
@ -134,16 +147,16 @@ class CameraFrame(Mobject):
|
|||
|
||||
def increment_euler_angles(
|
||||
self,
|
||||
dtheta: float | None = None,
|
||||
dphi: float | None = None,
|
||||
dgamma: float | None = None,
|
||||
dtheta: float = 0,
|
||||
dphi: float = 0,
|
||||
dgamma: float = 0,
|
||||
units: float = RADIANS
|
||||
):
|
||||
angles = self.get_euler_angles()
|
||||
for i, value in enumerate([dtheta, dphi, dgamma]):
|
||||
if value is not None:
|
||||
angles[i] += value * units
|
||||
self.set_euler_angles(*angles)
|
||||
new_angles = angles + np.array([dtheta, dphi, dgamma]) * units
|
||||
new_angles[1] = clip(new_angles[1], 0, PI) # Limit range for phi
|
||||
new_rot = Rotation.from_euler(self.euler_axes, new_angles[::-1])
|
||||
self.set_orientation(new_rot)
|
||||
return self
|
||||
|
||||
def set_euler_axes(self, seq: str):
|
||||
|
|
|
@ -103,21 +103,16 @@ class TracedPath(VMobject):
|
|||
time_per_anchor: float = 1.0 / 15,
|
||||
stroke_width: float | Iterable[float] = 2.0,
|
||||
stroke_color: ManimColor = WHITE,
|
||||
fill_opacity: float = 0.0,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(
|
||||
stroke_width=stroke_width,
|
||||
stroke_color=stroke_color,
|
||||
fill_opacity=fill_opacity,
|
||||
**kwargs
|
||||
)
|
||||
super().__init__(**kwargs)
|
||||
self.traced_point_func = traced_point_func
|
||||
self.time_traced = time_traced
|
||||
self.time_per_anchor = time_per_anchor
|
||||
self.time: float = 0
|
||||
self.traced_points: list[np.ndarray] = []
|
||||
self.add_updater(lambda m, dt: m.update_path(dt))
|
||||
self.set_stroke(stroke_color, stroke_width)
|
||||
|
||||
def update_path(self, dt: float) -> Self:
|
||||
if dt == 0:
|
||||
|
|
|
@ -530,7 +530,6 @@ class ThreeDAxes(Axes):
|
|||
z_axis_config: dict = dict(),
|
||||
z_normal: Vect3 = DOWN,
|
||||
depth: float | None = None,
|
||||
flat_stroke: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
Axes.__init__(self, x_range, y_range, **kwargs)
|
||||
|
@ -555,8 +554,6 @@ class ThreeDAxes(Axes):
|
|||
self.axes.add(self.z_axis)
|
||||
self.add(self.z_axis)
|
||||
|
||||
self.set_flat_stroke(flat_stroke)
|
||||
|
||||
def get_all_ranges(self) -> list[Sequence[float]]:
|
||||
return [self.x_range, self.y_range, self.z_range]
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ class SurfaceMesh(VGroup):
|
|||
normal_nudge: float = 1e-2,
|
||||
depth_test: bool = True,
|
||||
joint_type: str = 'no_joint',
|
||||
flat_stroke: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
self.uv_surface = uv_surface
|
||||
|
@ -52,7 +51,6 @@ class SurfaceMesh(VGroup):
|
|||
joint_type=joint_type,
|
||||
**kwargs
|
||||
)
|
||||
self.set_flat_stroke(flat_stroke)
|
||||
|
||||
def init_points(self) -> None:
|
||||
uv_surface = self.uv_surface
|
||||
|
|
|
@ -97,7 +97,7 @@ class VMobject(Mobject):
|
|||
long_lines: bool = False,
|
||||
# Could also be "no_joint", "bevel", "miter"
|
||||
joint_type: str = "auto",
|
||||
flat_stroke: bool = True,
|
||||
flat_stroke: bool = False,
|
||||
use_simple_quadratic_approx: bool = False,
|
||||
# Measured in pixel widths
|
||||
anti_alias_width: float = 1.5,
|
||||
|
@ -203,6 +203,7 @@ class VMobject(Mobject):
|
|||
width: float | Iterable[float] | None = None,
|
||||
opacity: float | Iterable[float] | None = None,
|
||||
background: bool | None = None,
|
||||
flat: bool | None = None,
|
||||
recurse: bool = True
|
||||
) -> Self:
|
||||
self.set_rgba_array_by_color(color, opacity, 'stroke_rgba', recurse)
|
||||
|
@ -221,6 +222,9 @@ class VMobject(Mobject):
|
|||
for mob in self.get_family(recurse):
|
||||
mob.stroke_behind = background
|
||||
|
||||
if flat is not None:
|
||||
self.set_flat_stroke(flat)
|
||||
|
||||
self.note_changed_stroke()
|
||||
return self
|
||||
|
||||
|
@ -673,7 +677,7 @@ class VMobject(Mobject):
|
|||
return bool((dots > 1 - 1e-3).all())
|
||||
|
||||
def change_anchor_mode(self, mode: str) -> Self:
|
||||
assert(mode in ("jagged", "approx_smooth", "true_smooth"))
|
||||
assert mode in ("jagged", "approx_smooth", "true_smooth")
|
||||
if self.get_num_points() == 0:
|
||||
return self
|
||||
subpaths = self.get_subpaths()
|
||||
|
@ -697,7 +701,7 @@ class VMobject(Mobject):
|
|||
self.add_subpath(new_subpath)
|
||||
return self
|
||||
|
||||
def make_smooth(self, approx=False, recurse=True) -> Self:
|
||||
def make_smooth(self, approx=True, recurse=True) -> Self:
|
||||
"""
|
||||
Edits the path so as to pass smoothly through all
|
||||
the current anchor points.
|
||||
|
@ -722,7 +726,7 @@ class VMobject(Mobject):
|
|||
return self
|
||||
|
||||
def add_subpath(self, points: Vect3Array) -> Self:
|
||||
assert(len(points) % 2 == 1 or len(points) == 0)
|
||||
assert len(points) % 2 == 1 or len(points) == 0
|
||||
if not self.has_points():
|
||||
self.set_points(points)
|
||||
return self
|
||||
|
@ -1201,7 +1205,7 @@ class VMobject(Mobject):
|
|||
|
||||
points = self.get_points()
|
||||
|
||||
if(len(points) < 3):
|
||||
if len(points) < 3:
|
||||
return self.data["joint_product"]
|
||||
|
||||
# Find all the unit tangent vectors at each joint
|
||||
|
|
|
@ -13,8 +13,5 @@ void main() {
|
|||
|
||||
// sdf for the region around the curve we wish to color.
|
||||
float signed_dist_to_region = abs(scaled_signed_dist_to_curve) - 1.0;
|
||||
frag_color.a *= smoothstep(
|
||||
0, -scaled_anti_alias_width,
|
||||
signed_dist_to_region
|
||||
);
|
||||
frag_color.a *= smoothstep(0, -scaled_anti_alias_width, signed_dist_to_region);
|
||||
}
|
|
@ -28,102 +28,101 @@ const int MITER_JOINT = 3;
|
|||
// When the cosine of the angle between
|
||||
// two vectors is larger than this, we
|
||||
// consider them aligned
|
||||
const float COS_THRESHOLD = 0.99;
|
||||
const float COS_THRESHOLD = 0.999;
|
||||
// Used to determine how many lines to break the curve into
|
||||
const float POLYLINE_FACTOR = 30;
|
||||
const float POLYLINE_FACTOR = 100;
|
||||
const int MAX_STEPS = 32;
|
||||
|
||||
vec3 unit_normal = vec3(0.0, 0.0, 1.0);
|
||||
|
||||
#INSERT emit_gl_Position.glsl
|
||||
#INSERT finalize_color.glsl
|
||||
|
||||
|
||||
vec3 get_joint_unit_normal(vec4 joint_product){
|
||||
vec3 result = (joint_product.w < COS_THRESHOLD) ?
|
||||
joint_product.xyz : v_joint_product[1].xyz;
|
||||
float norm = length(result);
|
||||
return (norm > 1e-5) ? result / norm : vec3(0.0, 0.0, 1.0);
|
||||
float tol = 1e-8;
|
||||
if (length(joint_product.xyz) > tol){
|
||||
return normalize(joint_product.xyz);
|
||||
}
|
||||
if (length(v_joint_product[1].xyz) > tol){
|
||||
return normalize(v_joint_product[1].xyz);
|
||||
}
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
|
||||
vec4 normalized_joint_product(vec4 joint_product){
|
||||
vec4 unit_joint_product(vec4 joint_product){
|
||||
float tol = 1e-8;
|
||||
float norm = length(joint_product);
|
||||
return (norm > 1e-10) ? joint_product / norm : vec4(0.0, 0.0, 0.0, 1.0);
|
||||
return (norm < tol) ? vec4(0.0, 0.0, 0.0, 1.0) : joint_product / norm;
|
||||
}
|
||||
|
||||
|
||||
vec3 point_on_curve(float t){
|
||||
return verts[0] + 2 * (verts[1] - verts[0]) * t + (verts[0] - 2 * verts[1] + verts[2]) * t * t;
|
||||
vec3 point_on_quadratic(float t, vec3 c0, vec3 c1, vec3 c2){
|
||||
return c0 + c1 * t + c2 * t * t;
|
||||
}
|
||||
|
||||
|
||||
vec3 tangent_on_curve(float t){
|
||||
return 2 * (verts[1] - verts[0]) + 2 * (verts[0] - 2 * verts[1] + verts[2]) * t;
|
||||
vec3 tangent_on_quadratic(float t, vec3 c1, vec3 c2){
|
||||
return c1 + 2 * c2 * t;
|
||||
}
|
||||
|
||||
|
||||
void compute_subdivision(out int n_steps, out float subdivision[MAX_STEPS]){
|
||||
// Crude estimate for the number of polyline segments to use, based
|
||||
// on the area spanned by the control points
|
||||
float area = 0.5 * length(v_joint_product[1].xzy);
|
||||
int count = 2 + int(round(POLYLINE_FACTOR * sqrt(area) / frame_scale));
|
||||
|
||||
n_steps = min(count, MAX_STEPS);
|
||||
for(int i = 0; i < MAX_STEPS; i++){
|
||||
if (i >= n_steps) break;
|
||||
subdivision[i] = float(i) / (n_steps - 1);
|
||||
}
|
||||
vec4 get_joint_product(vec3 v1, vec3 v2){
|
||||
return vec4(cross(v1, v2), dot(v1, v2));
|
||||
}
|
||||
|
||||
|
||||
void create_joint(
|
||||
vec4 joint_product,
|
||||
vec3 unit_tan,
|
||||
float buff,
|
||||
vec3 static_c0,
|
||||
out vec3 changing_c0,
|
||||
vec3 static_c1,
|
||||
out vec3 changing_c1
|
||||
){
|
||||
float cos_angle = joint_product.w;
|
||||
if(abs(cos_angle) > COS_THRESHOLD || int(joint_type) == NO_JOINT){
|
||||
// No joint
|
||||
changing_c0 = static_c0;
|
||||
changing_c1 = static_c1;
|
||||
return;
|
||||
}
|
||||
|
||||
float shift;
|
||||
float sin_angle = length(joint_product.xyz) * sign(joint_product.z);
|
||||
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;
|
||||
}
|
||||
changing_c0 = static_c0 - shift * unit_tan;
|
||||
changing_c1 = static_c1 + shift * unit_tan;
|
||||
vec3 project(vec3 vect, vec3 unit_normal){
|
||||
/* Project the vector onto the plane perpendicular to a given unit normal */
|
||||
return vect - dot(vect, unit_normal) * unit_normal;
|
||||
}
|
||||
|
||||
|
||||
vec3 left_step(vec3 point, vec3 tangent, vec4 joint_product){
|
||||
/*
|
||||
Perpendicular vectors to the left of the curve
|
||||
vec3 inverse_vector_product(vec3 vect, vec3 cross_product, float dot_product){
|
||||
/*
|
||||
Suppose cross(v1, v2) = cross_product and dot(v1, v2) = dot_product.
|
||||
Given v1, this function return v2.
|
||||
*/
|
||||
// Add correction for sharp angles to prevent weird bevel effects
|
||||
float mult = 1.0;
|
||||
if(joint_product.w < -0.75) mult *= 4 * (joint_product.w + 1.0);
|
||||
vec3 normal = get_joint_unit_normal(joint_product);
|
||||
// Set global unit normal
|
||||
unit_normal = normal;
|
||||
// Choose the "outward" normal direction
|
||||
if(normal.z < 0) normal *= -1;
|
||||
if(bool(flat_stroke)){
|
||||
return mult * normalize(cross(normal, tangent));
|
||||
}else{
|
||||
return mult * normalize(cross(camera_position - point, tangent));
|
||||
return (vect * dot_product - cross(vect, cross_product)) / dot(vect, vect);
|
||||
}
|
||||
|
||||
|
||||
vec3 step_to_corner(vec3 point, vec3 tangent, vec3 unit_normal, vec4 joint_product, bool inner_joint){
|
||||
/*
|
||||
Step the the left of a curve.
|
||||
First a perpendicular direction is calculated, then it is adjusted
|
||||
so as to make a joint.
|
||||
*/
|
||||
vec3 unit_tan = normalize(flat_stroke == 0.0 ? project(tangent, unit_normal) : tangent);
|
||||
vec4 unit_jp = unit_joint_product(joint_product);
|
||||
float cos_angle = unit_jp.w;
|
||||
|
||||
// Step to stroke width bound should be perpendicular
|
||||
// both to the tangent and the normal direction
|
||||
vec3 step = normalize(cross(unit_normal, unit_tan));
|
||||
|
||||
// Conditions where no joint needs to be created
|
||||
if (inner_joint || int(joint_type) == NO_JOINT || cos_angle > COS_THRESHOLD) return step;
|
||||
|
||||
if (flat_stroke == 0){
|
||||
// Figure out what joint product would be for everything projected onto
|
||||
// the plane perpendicular to the normal direction (which here would be to_camera)
|
||||
vec3 adj_tan = inverse_vector_product(tangent, unit_jp.xyz, unit_jp.w);
|
||||
adj_tan = project(adj_tan, unit_normal);
|
||||
vec4 flat_jp = get_joint_product(unit_tan, adj_tan);
|
||||
cos_angle = unit_joint_product(flat_jp).w;
|
||||
}
|
||||
|
||||
// Adjust based on the joint.
|
||||
// If joint type is auto, it will bevel for cos(angle) > -0.7,
|
||||
// and smoothly transition to miter for those with sharper angles
|
||||
float miter_factor;
|
||||
if (joint_type == AUTO_JOINT) miter_factor = smoothstep(-0.7, -0.9, cos_angle);
|
||||
else if (joint_type == BEVEL_JOINT) miter_factor = 0.0;
|
||||
else miter_factor = 1.0;
|
||||
|
||||
float sin_angle = sqrt(1 - cos_angle * cos_angle) * sign(dot(joint_product.xyz, unit_normal));
|
||||
float shift = (cos_angle + mix(-1, 1, miter_factor)) / sin_angle;
|
||||
|
||||
return step + shift * unit_tan;
|
||||
}
|
||||
|
||||
|
||||
|
@ -132,72 +131,91 @@ void emit_point_with_width(
|
|||
vec3 tangent,
|
||||
vec4 joint_product,
|
||||
float width,
|
||||
vec4 joint_color
|
||||
vec4 joint_color,
|
||||
bool inner_joint
|
||||
){
|
||||
vec3 unit_tan = normalize(tangent);
|
||||
vec4 normed_join_product = normalized_joint_product(joint_product);
|
||||
vec3 perp = 0.5 * width * left_step(point, unit_tan, normed_join_product);
|
||||
// Find unit normal
|
||||
vec3 to_camera = camera_position - point;
|
||||
vec3 unit_normal;
|
||||
if (flat_stroke == 0.0){
|
||||
unit_normal = normalize(to_camera);
|
||||
}else{
|
||||
unit_normal = get_joint_unit_normal(joint_product);
|
||||
unit_normal *= sign(dot(unit_normal, to_camera)); // Choose the "outward" normal direction
|
||||
}
|
||||
|
||||
vec3 corners[2] = vec3[2](point + perp, point - perp);
|
||||
create_joint(
|
||||
normed_join_product, unit_tan, length(perp),
|
||||
corners[0], corners[0],
|
||||
corners[1], corners[1]
|
||||
);
|
||||
// Figure out the step from the point to the corners of the
|
||||
// triangle strip around the polyline
|
||||
vec3 step = step_to_corner(point, tangent, unit_normal, joint_product, inner_joint);
|
||||
|
||||
// TODO, this gives a potentially nice effect that's like a ribbon mostly with its
|
||||
// broad side to the camera. Currently hard to access via VMobject
|
||||
if(flat_stroke == 2.0){
|
||||
// Rotate the step towards the unit normal by an amount depending
|
||||
// on the camera position
|
||||
float cos_angle = dot(unit_normal, normalize(camera_position));
|
||||
float sin_angle = sqrt(max(1 - cos_angle * cos_angle, 0));
|
||||
step = cos_angle * step + sin_angle * unit_normal;
|
||||
}
|
||||
|
||||
// Set styling
|
||||
color = finalize_color(joint_color, point, unit_normal);
|
||||
if (width == 0) scaled_anti_alias_width = -1.0; // Signal to discard in frag
|
||||
if (width == 0) scaled_anti_alias_width = -1.0; // Signal to discard in the frag shader
|
||||
else scaled_anti_alias_width = 2.0 * anti_alias_width * pixel_size / width;
|
||||
|
||||
// Emit two corners
|
||||
// The frag shader will receive a value from -1 to 1,
|
||||
// reflecting where in the stroke that point is
|
||||
scaled_signed_dist_to_curve = -1.0;
|
||||
emit_gl_Position(corners[0]);
|
||||
EmitVertex();
|
||||
scaled_signed_dist_to_curve = +1.0;
|
||||
emit_gl_Position(corners[1]);
|
||||
EmitVertex();
|
||||
for (int sign = -1; sign <= 1; sign += 2){
|
||||
scaled_signed_dist_to_curve = sign;
|
||||
emit_gl_Position(point + 0.5 * width * sign * step);
|
||||
EmitVertex();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
// Curves are marked as ended when the handle after
|
||||
// the first anchor is set equal to that anchor
|
||||
if (verts[0] == verts[1]) return;
|
||||
|
||||
// Compute subdivision
|
||||
int n_steps;
|
||||
float subdivision[MAX_STEPS];
|
||||
compute_subdivision(n_steps, subdivision);
|
||||
vec3 points[MAX_STEPS];
|
||||
for (int i = 0; i < MAX_STEPS; i++){
|
||||
if (i >= n_steps) break;
|
||||
points[i] = point_on_curve(subdivision[i]);
|
||||
}
|
||||
// Coefficients such that the quadratic bezier is c0 + c1 * t + c2 * t^2
|
||||
vec3 c0 = verts[0];
|
||||
vec3 c1 = 2 * (verts[1] - verts[0]);
|
||||
vec3 c2 = verts[0] - 2 * verts[1] + verts[2];
|
||||
|
||||
// Compute joint products
|
||||
vec4 joint_products[MAX_STEPS];
|
||||
joint_products[0] = v_joint_product[0];
|
||||
joint_products[0].xyz *= -1;
|
||||
joint_products[n_steps - 1] = v_joint_product[2];
|
||||
for (int i = 1; i < MAX_STEPS; i++){
|
||||
if (i >= n_steps - 1) break;
|
||||
vec3 v1 = points[i] - points[i - 1];
|
||||
vec3 v2 = points[i + 1] - points[i];
|
||||
joint_products[i].xyz = cross(v1, v2);
|
||||
joint_products[i].w = dot(v1, v2);
|
||||
}
|
||||
// Estimate how many line segment the curve should be divided into
|
||||
// based on the area of the triangle defined by these control points
|
||||
float area = 0.5 * length(v_joint_product[1].xzy);
|
||||
int count = int(round(POLYLINE_FACTOR * sqrt(area) / frame_scale));
|
||||
int n_steps = min(2 + count, MAX_STEPS);
|
||||
|
||||
// Emit vertex pairs aroudn subdivided points
|
||||
for (int i = 0; i < MAX_STEPS; i++){
|
||||
if (i >= n_steps) break;
|
||||
float t = subdivision[i];
|
||||
float t = float(i) / (n_steps - 1);
|
||||
|
||||
// Point and tangent
|
||||
vec3 point = point_on_quadratic(t, c0, c1, c2);
|
||||
vec3 tangent = tangent_on_quadratic(t, c1, c2);
|
||||
|
||||
// Style
|
||||
float stroke_width = mix(v_stroke_width[0], v_stroke_width[2], t);
|
||||
vec4 color = mix(v_color[0], v_color[2], t);
|
||||
|
||||
// This is sent along to prevent needless joint creation
|
||||
bool inside_curve = (i > 0 && i < n_steps - 1);
|
||||
|
||||
// Use middle joint product for inner points, flip sign for first one's cross product component
|
||||
vec4 joint_product;
|
||||
if (i == 0) joint_product = v_joint_product[0] * vec4(-1, -1, -1, 1);
|
||||
else if (inside_curve) joint_product = v_joint_product[1];
|
||||
else joint_product = v_joint_product[2];
|
||||
|
||||
emit_point_with_width(
|
||||
points[i],
|
||||
tangent_on_curve(t),
|
||||
joint_products[i],
|
||||
mix(v_stroke_width[0], v_stroke_width[2], t),
|
||||
mix(v_color[0], v_color[2], t)
|
||||
point, tangent, joint_product,
|
||||
stroke_width, color,
|
||||
inside_curve
|
||||
);
|
||||
}
|
||||
EndPrimitive();
|
||||
|
|
|
@ -6,7 +6,6 @@ uniform float is_fixed_in_frame;
|
|||
in vec3 point;
|
||||
in vec4 stroke_rgba;
|
||||
in float stroke_width;
|
||||
in vec3 joint_normal;
|
||||
in vec4 joint_product;
|
||||
|
||||
// Bezier control point
|
||||
|
@ -16,7 +15,7 @@ out vec4 v_joint_product;
|
|||
out float v_stroke_width;
|
||||
out vec4 v_color;
|
||||
|
||||
const float STROKE_WIDTH_CONVERSION = 0.01;
|
||||
const float STROKE_WIDTH_CONVERSION = 0.015;
|
||||
|
||||
void main(){
|
||||
verts = point;
|
||||
|
|
|
@ -198,7 +198,9 @@ def approx_smooth_quadratic_bezier_handles(
|
|||
another that would produce a parabola passing through P0, call it smooth_to_left,
|
||||
and use the midpoint between the two.
|
||||
"""
|
||||
if len(points) == 2:
|
||||
if len(points) == 1:
|
||||
return points[0]
|
||||
elif len(points) == 2:
|
||||
return midpoint(*points)
|
||||
smooth_to_right, smooth_to_left = [
|
||||
0.25 * ps[0:-2] + ps[1:-1] - 0.25 * ps[2:]
|
||||
|
|
Loading…
Add table
Reference in a new issue