Grease Pencil: Add new Corner Types
This adds three corner types: `Round` `Sharp` and `Flat`. These control how the corner of `Grease Pencil` line strokes are rendered. - The `Round` type draws circular arcs, and is what `Grease Pencil` currently supports and is default. - The `Flat` type cuts off the tip of the corner. - The `Sharp` type allows for sharp corners to be created. If the angle is sharper than `Miter Limit` then the tip will be cut like `Flat` These three types match the main types of `line joins` present in `SVG` files. This data is stored in one `Point` attribute called `miter_angle`. This stores both the `Corner Type`, the `Miter Limit` and defaults to the `Round` type. This PR adds: - Rendering of the corner types. - An operator for setting the `Corner Type` and `Miter Angle` attribute. - Corner types to the `Outline` operator and modifier. Part of #145380. Pull Request: https://projects.blender.org/blender/blender/pulls/143688
This commit is contained in:
committed by
Falk David
parent
e53f0066f9
commit
d4f84619ea
@@ -5853,6 +5853,7 @@ class VIEW3D_MT_edit_greasepencil_point(Menu):
|
||||
layout.separator()
|
||||
|
||||
layout.operator_menu_enum("grease_pencil.set_handle_type", property="type")
|
||||
layout.operator_menu_enum("grease_pencil.set_corner_type", property="corner_type")
|
||||
|
||||
layout.template_node_operator_asset_menu_items(catalog_path=self.bl_label)
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ enum gpLightType : uint32_t {
|
||||
|
||||
#define GP_IS_STROKE_VERTEX_BIT (1 << 30)
|
||||
#define GP_VERTEX_ID_SHIFT 2
|
||||
#define GP_CORNER_TYPE_ROUND_BITS 0u
|
||||
#define GP_CORNER_TYPE_BEVEL_BITS 63u
|
||||
#define GP_CORNER_TYPE_MITER_NUMBER 62u
|
||||
|
||||
/* Avoid compiler funkiness with enum types not being strongly typed in C. */
|
||||
#ifndef GPU_SHADER
|
||||
|
||||
@@ -81,11 +81,15 @@ void main()
|
||||
|
||||
frag_color.rgb *= gpencil_lighting();
|
||||
|
||||
frag_color *= gpencil_stroke_round_cap_mask(gp_interp_flat.sspos.xy,
|
||||
gp_interp_flat.sspos.zw,
|
||||
gp_interp_flat.aspect,
|
||||
gp_interp_noperspective.thickness.x,
|
||||
gp_interp_noperspective.hardness);
|
||||
frag_color *= gpencil_stroke_mask(gp_interp_flat.sspos.xy,
|
||||
gp_interp_flat.sspos.zw,
|
||||
gp_interp_flat.sspos_adj.xy,
|
||||
gp_interp_flat.sspos_adj.zw,
|
||||
gp_interp.uv,
|
||||
gp_interp_flat.mat_flag,
|
||||
gp_interp_noperspective.thickness.x,
|
||||
gp_interp_noperspective.hardness,
|
||||
gp_interp_noperspective.thickness.zw);
|
||||
|
||||
/* To avoid aliasing artifacts, we reduce the opacity of small strokes. */
|
||||
frag_color *= smoothstep(0.0f, 1.0f, gp_interp_noperspective.thickness.y);
|
||||
|
||||
@@ -52,6 +52,7 @@ void main()
|
||||
vert_strength,
|
||||
gp_interp.uv,
|
||||
gp_interp_flat.sspos,
|
||||
gp_interp_flat.sspos_adj,
|
||||
gp_interp_flat.aspect,
|
||||
gp_interp_noperspective.thickness,
|
||||
gp_interp_noperspective.hardness);
|
||||
|
||||
@@ -40,11 +40,12 @@ GPU_SHADER_NAMED_INTERFACE_END(gp_interp)
|
||||
GPU_SHADER_NAMED_INTERFACE_INFO(gpencil_geometry_flat_iface, gp_interp_flat)
|
||||
FLAT(float2, aspect)
|
||||
FLAT(float4, sspos)
|
||||
FLAT(float4, sspos_adj)
|
||||
FLAT(uint, mat_flag)
|
||||
FLAT(float, depth)
|
||||
GPU_SHADER_NAMED_INTERFACE_END(gp_interp_flat)
|
||||
GPU_SHADER_NAMED_INTERFACE_INFO(gpencil_geometry_noperspective_iface, gp_interp_noperspective)
|
||||
NO_PERSPECTIVE(float2, thickness)
|
||||
NO_PERSPECTIVE(float4, thickness)
|
||||
NO_PERSPECTIVE(float, hardness)
|
||||
GPU_SHADER_NAMED_INTERFACE_END(gp_interp_noperspective)
|
||||
|
||||
|
||||
@@ -773,10 +773,11 @@ OVERLAY_INFO_VARIATIONS_MODELMAT(overlay_depth_mesh_conservative,
|
||||
GPU_SHADER_NAMED_INTERFACE_INFO(overlay_depth_only_gpencil_flat_iface, gp_interp_flat)
|
||||
FLAT(float2, aspect)
|
||||
FLAT(float4, sspos)
|
||||
FLAT(float4, sspos_adj)
|
||||
GPU_SHADER_NAMED_INTERFACE_END(gp_interp_flat)
|
||||
GPU_SHADER_NAMED_INTERFACE_INFO(overlay_depth_only_gpencil_noperspective_iface,
|
||||
gp_interp_noperspective)
|
||||
NO_PERSPECTIVE(float2, thickness)
|
||||
NO_PERSPECTIVE(float4, thickness)
|
||||
NO_PERSPECTIVE(float, hardness)
|
||||
GPU_SHADER_NAMED_INTERFACE_END(gp_interp_noperspective)
|
||||
|
||||
|
||||
@@ -90,10 +90,11 @@ OVERLAY_INFO_CLIP_VARIATION(overlay_outline_prepass_wire)
|
||||
GPU_SHADER_NAMED_INTERFACE_INFO(overlay_outline_prepass_gpencil_flat_iface, gp_interp_flat)
|
||||
FLAT(float2, aspect)
|
||||
FLAT(float4, sspos)
|
||||
FLAT(float4, sspos_adj)
|
||||
GPU_SHADER_NAMED_INTERFACE_END(gp_interp_flat)
|
||||
GPU_SHADER_NAMED_INTERFACE_INFO(overlay_outline_prepass_gpencil_noperspective_iface,
|
||||
gp_interp_noperspective)
|
||||
NO_PERSPECTIVE(float2, thickness)
|
||||
NO_PERSPECTIVE(float4, thickness)
|
||||
NO_PERSPECTIVE(float, hardness)
|
||||
GPU_SHADER_NAMED_INTERFACE_END(gp_interp_noperspective)
|
||||
|
||||
|
||||
@@ -20,11 +20,13 @@ float3 ray_plane_intersection(float3 ray_ori, float3 ray_dir, float4 plane)
|
||||
|
||||
void main()
|
||||
{
|
||||
if (gpencil_stroke_round_cap_mask(gp_interp_flat.sspos.xy,
|
||||
gp_interp_flat.sspos.zw,
|
||||
gp_interp_flat.aspect,
|
||||
gp_interp_noperspective.thickness.x,
|
||||
gp_interp_noperspective.hardness) < 0.001f)
|
||||
if (gpencil_stroke_segment_mask(gp_interp_flat.sspos.xy,
|
||||
gp_interp_flat.sspos.zw,
|
||||
gp_interp_flat.sspos_adj.xy,
|
||||
gp_interp_flat.sspos_adj.zw,
|
||||
gp_interp_noperspective.thickness.x,
|
||||
gp_interp_noperspective.hardness,
|
||||
gp_interp_noperspective.thickness.zw) < 0.001f)
|
||||
{
|
||||
#ifndef SELECT_ENABLE
|
||||
/* We cannot discard the fragment in selection mode. Otherwise we would break pipeline
|
||||
|
||||
@@ -27,6 +27,7 @@ void main()
|
||||
unused_strength,
|
||||
unused_uv,
|
||||
gp_interp_flat.sspos,
|
||||
gp_interp_flat.sspos_adj,
|
||||
gp_interp_flat.aspect,
|
||||
gp_interp_noperspective.thickness,
|
||||
gp_interp_noperspective.hardness);
|
||||
|
||||
@@ -19,11 +19,13 @@ float3 ray_plane_intersection(float3 ray_ori, float3 ray_dir, float4 plane)
|
||||
|
||||
void main()
|
||||
{
|
||||
if (gpencil_stroke_round_cap_mask(gp_interp_flat.sspos.xy,
|
||||
gp_interp_flat.sspos.zw,
|
||||
gp_interp_flat.aspect,
|
||||
gp_interp_noperspective.thickness.x,
|
||||
gp_interp_noperspective.hardness) < 0.001f)
|
||||
if (gpencil_stroke_segment_mask(gp_interp_flat.sspos.xy,
|
||||
gp_interp_flat.sspos.zw,
|
||||
gp_interp_flat.sspos_adj.xy,
|
||||
gp_interp_flat.sspos_adj.zw,
|
||||
gp_interp_noperspective.thickness.x,
|
||||
gp_interp_noperspective.hardness,
|
||||
gp_interp_noperspective.thickness.zw) < 0.001f)
|
||||
{
|
||||
gpu_discard_fragment();
|
||||
return;
|
||||
|
||||
@@ -45,6 +45,7 @@ void main()
|
||||
unused_strength,
|
||||
unused_uv,
|
||||
gp_interp_flat.sspos,
|
||||
gp_interp_flat.sspos_adj,
|
||||
gp_interp_flat.aspect,
|
||||
gp_interp_noperspective.thickness,
|
||||
gp_interp_noperspective.hardness);
|
||||
|
||||
@@ -223,7 +223,10 @@ static GreasePencilBatchCache *grease_pencil_batch_cache_get(GreasePencil &greas
|
||||
/** \name Vertex Buffers
|
||||
* \{ */
|
||||
|
||||
BLI_INLINE int32_t pack_rotation_aspect_hardness(float rot, float asp, float softness)
|
||||
BLI_INLINE int32_t pack_rotation_aspect_hardness_miter(const float rot,
|
||||
const float asp,
|
||||
const float softness,
|
||||
const float miter_angle)
|
||||
{
|
||||
int32_t packed = 0;
|
||||
/* Aspect uses 9 bits */
|
||||
@@ -247,6 +250,21 @@ BLI_INLINE int32_t pack_rotation_aspect_hardness(float rot, float asp, float sof
|
||||
}
|
||||
/* Hardness uses 8 bits */
|
||||
packed |= int32_t(unit_float_to_uchar_clamp(1.0f - softness)) << 18;
|
||||
|
||||
/* Miter Angle uses the last 6 bits */
|
||||
if (miter_angle <= GP_STROKE_MITER_ANGLE_ROUND) {
|
||||
packed |= GP_CORNER_TYPE_ROUND_BITS << 26;
|
||||
}
|
||||
else if (miter_angle >= GP_STROKE_MITER_ANGLE_BEVEL) {
|
||||
packed |= GP_CORNER_TYPE_BEVEL_BITS << 26;
|
||||
}
|
||||
else {
|
||||
const float miter_norm = (miter_angle / M_PI);
|
||||
packed |= int32_t(clamp_i(
|
||||
int(miter_norm * GP_CORNER_TYPE_MITER_NUMBER), 1, GP_CORNER_TYPE_MITER_NUMBER))
|
||||
<< 26;
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
@@ -1058,6 +1076,59 @@ static VArray<T> attribute_interpolate(const VArray<T> &input, const bke::Curves
|
||||
return VArray<T>::from_container(std::move(out));
|
||||
};
|
||||
|
||||
static VArray<float> interpolate_corners(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
const VArray<float> miter_angles = *curves.attributes().lookup_or_default<float>(
|
||||
"miter_angle", bke::AttrDomain::Point, GP_STROKE_MITER_ANGLE_ROUND);
|
||||
|
||||
if (curves.is_single_type(CURVE_TYPE_POLY)) {
|
||||
return miter_angles;
|
||||
}
|
||||
|
||||
if (miter_angles.is_single() &&
|
||||
miter_angles.get_internal_single() == GP_STROKE_MITER_ANGLE_ROUND)
|
||||
{
|
||||
return VArray<float>::from_single(GP_STROKE_MITER_ANGLE_ROUND, curves.evaluated_points_num());
|
||||
}
|
||||
|
||||
/* Default all the evaluated points to be round.
|
||||
* This is done so that the added points look as smooth as possible. */
|
||||
Array<float> eval_corners(curves.evaluated_points_num(), GP_STROKE_MITER_ANGLE_ROUND);
|
||||
|
||||
const VArray<int8_t> types = curves.curve_types();
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const OffsetIndices<int> evaluated_points_by_curve = curves.evaluated_points_by_curve();
|
||||
|
||||
threading::parallel_for(curves.curves_range(), 128, [&](IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
const IndexRange eval_points = evaluated_points_by_curve[curve_i];
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
MutableSpan<float> eval_corners_range = eval_corners.as_mutable_span().slice(eval_points);
|
||||
|
||||
switch (types[curve_i]) {
|
||||
case CURVE_TYPE_POLY:
|
||||
for (const int i : points.index_range()) {
|
||||
eval_corners_range[i] = miter_angles[points[i]];
|
||||
}
|
||||
break;
|
||||
case CURVE_TYPE_BEZIER: {
|
||||
const Span<int> offsets = curves.bezier_evaluated_offsets_for_curve(curve_i);
|
||||
for (const int i : points.index_range()) {
|
||||
eval_corners_range[offsets[i]] = miter_angles[points[i]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CURVE_TYPE_NURBS:
|
||||
case CURVE_TYPE_CATMULL_ROM: {
|
||||
/* NUBRS and Catmull-Rom are continuous and don't have corners. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return VArray<float>::from_container(std::move(eval_corners));
|
||||
}
|
||||
|
||||
static void grease_pencil_geom_batch_ensure(Object &object,
|
||||
const GreasePencil &grease_pencil,
|
||||
const Scene &scene)
|
||||
@@ -1183,6 +1254,7 @@ static void grease_pencil_geom_batch_ensure(Object &object,
|
||||
*attributes.lookup_or_default<ColorGeometry4f>(
|
||||
"vertex_color", bke::AttrDomain::Point, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)),
|
||||
curves);
|
||||
const VArray<float> miter_angles = interpolate_corners(curves);
|
||||
|
||||
/* Assumes that if the ".selection" attribute does not exist, all points are selected. */
|
||||
const VArray<float> selection_float = *attributes.lookup_or_default<float>(
|
||||
@@ -1243,8 +1315,11 @@ static void grease_pencil_geom_batch_ensure(Object &object,
|
||||
* ensure the material used by the shader is valid this needs to be clamped to zero. */
|
||||
s_vert.mat = std::max(materials[curve_i], 0) % GPENCIL_MATERIAL_BUFFER_LEN;
|
||||
|
||||
s_vert.packed_asp_hard_rot = pack_rotation_aspect_hardness(
|
||||
rotations[point_i], stroke_point_aspect_ratios[curve_i], stroke_softness[curve_i]);
|
||||
s_vert.packed_asp_hard_rot = pack_rotation_aspect_hardness_miter(
|
||||
rotations[point_i],
|
||||
stroke_point_aspect_ratios[curve_i],
|
||||
stroke_softness[curve_i],
|
||||
miter_angles[point_i]);
|
||||
s_vert.u_stroke = u_stroke;
|
||||
copy_v2_v2(s_vert.uv_fill, texture_matrix * float4(pos, 1.0f));
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ SHADER_LIBRARY_CREATE_INFO(draw_gpencil)
|
||||
|
||||
#include "gpu_shader_math_constants_lib.glsl"
|
||||
#include "gpu_shader_math_matrix_transform_lib.glsl"
|
||||
#include "gpu_shader_math_vector_lib.glsl"
|
||||
#include "gpu_shader_math_vector_safe_lib.glsl"
|
||||
#include "gpu_shader_utildefines_lib.glsl"
|
||||
|
||||
@@ -21,37 +22,156 @@ SHADER_LIBRARY_CREATE_INFO(draw_gpencil)
|
||||
# error Missing additional info draw_gpencil
|
||||
#endif
|
||||
|
||||
#ifdef GPU_FRAGMENT_SHADER
|
||||
float gpencil_stroke_round_cap_mask(
|
||||
float2 p1, float2 p2, float2 aspect, float thickness, float hardfac)
|
||||
{
|
||||
/* We create our own uv space to avoid issues with triangulation and linear
|
||||
* interpolation artifacts. */
|
||||
float2 line = p2.xy - p1.xy;
|
||||
float2 pos = gl_FragCoord.xy - p1.xy;
|
||||
float line_len = length(line);
|
||||
float half_line_len = line_len * 0.5f;
|
||||
/* Normalize */
|
||||
line = (line_len > 0.0f) ? (line / line_len) : float2(1.0f, 0.0f);
|
||||
/* Create a uv space that englobe the whole segment into a capsule. */
|
||||
float2 uv_end;
|
||||
uv_end.x = max(abs(dot(line, pos) - half_line_len) - half_line_len, 0.0f);
|
||||
uv_end.y = dot(float2(-line.y, line.x), pos);
|
||||
/* Divide by stroke radius. */
|
||||
uv_end /= thickness;
|
||||
uv_end *= aspect;
|
||||
#define MITER_LIMIT_TYPE_BEVEL -1.0f
|
||||
#define MITER_LIMIT_TYPE_ROUND -2.0f
|
||||
|
||||
float dist = clamp(1.0f - length(uv_end) * 2.0f, 0.0f, 1.0f);
|
||||
#ifdef GPU_FRAGMENT_SHADER
|
||||
float gpencil_stroke_hardess_mask(float dist, float hardfac)
|
||||
{
|
||||
dist = clamp(1.0f - dist, 0.0f, 1.0f);
|
||||
if (hardfac > 0.999f) {
|
||||
return step(1e-8f, dist);
|
||||
}
|
||||
else {
|
||||
/* Modulate the falloff profile */
|
||||
float hardness = 1.0f - hardfac;
|
||||
dist = pow(dist, mix(0.01f, 10.0f, hardness));
|
||||
dist = pow(dist, mix(0.0f, 10.0f, hardness));
|
||||
return smoothstep(0.0f, 1.0f, dist);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Calculate the mask for the pixel in the main segment (1) by using the distance factor to
|
||||
* the center line.
|
||||
*
|
||||
* *====================*
|
||||
* \ /
|
||||
* p1------1>------p2
|
||||
* / \ / \
|
||||
* / *============* \
|
||||
* 0 1
|
||||
* / \
|
||||
* / \
|
||||
* p0 p3
|
||||
*
|
||||
* Segments: 0 (p0->p1)
|
||||
* 1 (p1->p2)
|
||||
* 2 (p2->p3)
|
||||
*
|
||||
* Each point can have a different corner type, stored as p1: miter_limit.x, p2: miter_limit.y
|
||||
*
|
||||
*/
|
||||
float gpencil_stroke_segment_mask(
|
||||
float2 p1, float2 p2, float2 p0, float2 p3, float thickness, float hardfac, float2 miter_limit)
|
||||
{
|
||||
bool both_round = miter_limit.x == MITER_LIMIT_TYPE_ROUND &&
|
||||
miter_limit.y == MITER_LIMIT_TYPE_ROUND;
|
||||
|
||||
bool is_start = distance_squared(p0, p1) < 1e-6;
|
||||
bool is_end = distance_squared(p2, p3) < 1e-6;
|
||||
bool both_ends = is_start && is_end;
|
||||
|
||||
float radius = thickness * 0.5f;
|
||||
float2 pos1 = gl_FragCoord.xy - p1;
|
||||
float2 line1 = p2 - p1;
|
||||
float2 tan1 = orthogonal(line1);
|
||||
float len_sq1 = length_squared(line1);
|
||||
|
||||
/* Calculate the factor along the main segment. */
|
||||
float t1 = dot(pos1, line1) / len_sq1;
|
||||
|
||||
/* The distance factor squared to the main segment. This is clamped and will lead to round
|
||||
* corners. */
|
||||
float dist = length_squared(pos1 - saturate(t1) * line1);
|
||||
|
||||
if (both_round || both_ends) {
|
||||
dist = sqrt(dist) / radius;
|
||||
return gpencil_stroke_hardess_mask(dist, hardfac);
|
||||
}
|
||||
|
||||
float2 line0 = p1 - p0;
|
||||
float2 tan0 = orthogonal(line0);
|
||||
float len_sq0 = length_squared(line0);
|
||||
|
||||
float2 pos2 = gl_FragCoord.xy - p2;
|
||||
float2 line2 = p3 - p2;
|
||||
float2 tan2 = orthogonal(line2);
|
||||
float len_sq2 = length_squared(line2);
|
||||
|
||||
/* Calculate the non-normalized factor along the other segments. */
|
||||
float t0 = dot(pos1, line0);
|
||||
float t2 = dot(pos2, line2);
|
||||
|
||||
/* Normalize all segment directions. */
|
||||
float2 tan_norm0 = tan0 / sqrt(len_sq0);
|
||||
float2 tan_norm1 = tan1 / sqrt(len_sq1);
|
||||
float2 tan_norm2 = tan2 / sqrt(len_sq2);
|
||||
|
||||
/* Get the squared distance to the main segment. */
|
||||
float dist_sq_1 = length_squared(pos1 - t1 * line1);
|
||||
|
||||
/* Check if the pixel is within the corner region between segments 1 and 0. */
|
||||
if (t1 <= 0.0f && t0 >= 0.0f && !is_start && miter_limit.x != MITER_LIMIT_TYPE_ROUND) {
|
||||
if (miter_limit.x == MITER_LIMIT_TYPE_BEVEL) {
|
||||
/* Bevel by cutting with a the half angle line. */
|
||||
float2 bevel_tan = orthogonal(tan_norm0 - tan_norm1);
|
||||
|
||||
dist = dot(pos1, bevel_tan) / dot(tan_norm1, bevel_tan);
|
||||
dist *= dist;
|
||||
}
|
||||
else {
|
||||
/* Continue the main line to get a sharp corner. */
|
||||
dist = dist_sq_1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the pixel is within the corner region between segments 1 and 2. */
|
||||
if (t1 >= 1.0f && t2 <= 0.0f && !is_end && miter_limit.y != MITER_LIMIT_TYPE_ROUND) {
|
||||
if (miter_limit.y == MITER_LIMIT_TYPE_BEVEL) {
|
||||
/* Bevel by cutting with a the half angle line. */
|
||||
float2 bevel_tan = orthogonal(tan_norm2 - tan_norm1);
|
||||
|
||||
dist = dot(pos2, bevel_tan) / dot(tan_norm1, bevel_tan);
|
||||
dist *= dist;
|
||||
}
|
||||
else {
|
||||
/* Continue the main line to get a sharp corner. */
|
||||
dist = dist_sq_1;
|
||||
}
|
||||
}
|
||||
|
||||
dist = sqrt(dist) / radius;
|
||||
return gpencil_stroke_hardess_mask(dist, hardfac);
|
||||
}
|
||||
|
||||
float gpencil_stroke_mask(float2 p1,
|
||||
float2 p2,
|
||||
float2 p0,
|
||||
float2 p3,
|
||||
float2 uv,
|
||||
uint mat_flag,
|
||||
float thickness,
|
||||
float hardfac,
|
||||
float2 miter_limit)
|
||||
{
|
||||
if (flag_test(mat_flag, GP_STROKE_ALIGNMENT)) {
|
||||
/* Dot or Squares. */
|
||||
uv = uv * 2.0 - 1.0;
|
||||
if (flag_test(mat_flag, GP_STROKE_DOTS)) {
|
||||
return gpencil_stroke_hardess_mask(length(uv), hardfac);
|
||||
}
|
||||
else {
|
||||
uv = abs(uv);
|
||||
return gpencil_stroke_hardess_mask(max(uv.x, uv.y), hardfac);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Line mask */
|
||||
return gpencil_stroke_segment_mask(p1, p2, p0, p3, thickness, hardfac, miter_limit);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
struct PointData {
|
||||
@@ -93,6 +213,19 @@ float gpencil_decode_hardness(int packed_data)
|
||||
return float((uint(packed_data) & 0x3FC0000u) >> 18u) * (1.0f / 255.0f);
|
||||
}
|
||||
|
||||
float gpencil_decode_miter_limit(int packed_data)
|
||||
{
|
||||
uint miter_data = (uint(packed_data) & 0xFC000000u) >> 26u;
|
||||
if (miter_data == GP_CORNER_TYPE_ROUND_BITS) {
|
||||
return MITER_LIMIT_TYPE_ROUND;
|
||||
}
|
||||
else if (miter_data == GP_CORNER_TYPE_BEVEL_BITS) {
|
||||
return MITER_LIMIT_TYPE_BEVEL;
|
||||
}
|
||||
float miter_angle = float(miter_data) * (M_PI / GP_CORNER_TYPE_MITER_NUMBER);
|
||||
return cos(miter_angle);
|
||||
}
|
||||
|
||||
float2 gpencil_project_to_screenspace(float4 v, float4 viewport_res)
|
||||
{
|
||||
return ((v.xy / v.w) * 0.5f + 0.5f) * viewport_res.xy;
|
||||
@@ -161,14 +294,17 @@ float4 gpencil_vertex(float4 viewport_res,
|
||||
out float2 out_uv,
|
||||
/* Screen-Space segment endpoints. */
|
||||
out float4 out_sspos,
|
||||
/* Screen-Space adjacent segment endpoints. */
|
||||
out float4 out_sspos_adj,
|
||||
/* Stroke aspect ratio. */
|
||||
out float2 out_aspect,
|
||||
/* Stroke thickness (x: clamped, y: unclamped). */
|
||||
out float2 out_thickness,
|
||||
/* Stroke thickness and miter limits (x: clamped, y: unclamped,
|
||||
* z: miter limit segment start, w: miter limit segment end). */
|
||||
out float4 out_thickness,
|
||||
/* Stroke hardness. */
|
||||
out float out_hardness)
|
||||
{
|
||||
int stroke_point_id = (gl_VertexID & ~GP_IS_STROKE_VERTEX_BIT) >> GP_VERTEX_ID_SHIFT;
|
||||
int stroke_point_id = gpencil_stroke_point_id();
|
||||
|
||||
/* Attribute Loading. */
|
||||
float4 pos = texelFetch(gp_pos_tx, (stroke_point_id - 1) * 3 + 0);
|
||||
@@ -240,9 +376,11 @@ float4 gpencil_vertex(float4 viewport_res,
|
||||
|
||||
bool use_curr = is_dot || (x == -1.0f);
|
||||
|
||||
float3 wpos_adj = transform_point(drw_modelmat(), (use_curr) ? pos.xyz : pos3.xyz);
|
||||
float3 wpos0 = transform_point(drw_modelmat(), pos.xyz);
|
||||
float3 wpos1 = transform_point(drw_modelmat(), pos1.xyz);
|
||||
float3 wpos2 = transform_point(drw_modelmat(), pos2.xyz);
|
||||
float3 wpos3 = transform_point(drw_modelmat(), pos3.xyz);
|
||||
float3 wpos_adj = (use_curr) ? wpos0 : wpos3;
|
||||
|
||||
float3 T;
|
||||
if (is_dot) {
|
||||
@@ -260,21 +398,26 @@ float4 gpencil_vertex(float4 viewport_res,
|
||||
float3 B = cross(T, drw_view().viewinv[2].xyz);
|
||||
out_N = normalize(cross(B, T));
|
||||
|
||||
float4 ndc_adj = drw_point_world_to_homogenous(wpos_adj);
|
||||
float4 ndc0 = drw_point_world_to_homogenous(wpos0);
|
||||
float4 ndc1 = drw_point_world_to_homogenous(wpos1);
|
||||
float4 ndc2 = drw_point_world_to_homogenous(wpos2);
|
||||
float4 ndc3 = drw_point_world_to_homogenous(wpos3);
|
||||
|
||||
out_ndc = (use_curr) ? ndc1 : ndc2;
|
||||
out_P = (use_curr) ? wpos1 : wpos2;
|
||||
out_strength = abs((use_curr) ? strength1 : strength2);
|
||||
|
||||
float2 ss_adj = gpencil_project_to_screenspace(ndc_adj, viewport_res);
|
||||
float2 ss0 = gpencil_project_to_screenspace(ndc0, viewport_res);
|
||||
float2 ss1 = gpencil_project_to_screenspace(ndc1, viewport_res);
|
||||
float2 ss2 = gpencil_project_to_screenspace(ndc2, viewport_res);
|
||||
float2 ss3 = gpencil_project_to_screenspace(ndc3, viewport_res);
|
||||
|
||||
/* Screen-space Lines tangents. */
|
||||
float line_len;
|
||||
float2 line = safe_normalize_and_get_length(ss2 - ss1, line_len);
|
||||
float2 line_adj = safe_normalize((use_curr) ? (ss1 - ss_adj) : (ss_adj - ss2));
|
||||
float2 line1 = safe_normalize(ss1 - ss0);
|
||||
float2 line2 = safe_normalize(ss3 - ss2);
|
||||
float2 line_adj = (use_curr) ? line1 : line2;
|
||||
|
||||
float thickness = abs((use_curr) ? thickness1 : thickness2);
|
||||
thickness = gpencil_stroke_thickness_modulate(thickness, out_ndc, viewport_res);
|
||||
@@ -286,6 +429,26 @@ float4 gpencil_vertex(float4 viewport_res,
|
||||
out_hardness = gpencil_decode_hardness(use_curr ? point_data1.packed_data :
|
||||
point_data2.packed_data);
|
||||
|
||||
out_sspos.xy = ss1;
|
||||
if (ma2.x != -1) {
|
||||
out_sspos.zw = ss2;
|
||||
}
|
||||
else {
|
||||
out_sspos.zw = out_sspos.xy;
|
||||
}
|
||||
if (ma.x != -1) {
|
||||
out_sspos_adj.xy = ss0;
|
||||
}
|
||||
else {
|
||||
out_sspos_adj.xy = out_sspos.xy;
|
||||
}
|
||||
if (ma3.x != -1) {
|
||||
out_sspos_adj.zw = ss3;
|
||||
}
|
||||
else {
|
||||
out_sspos_adj.zw = out_sspos.zw;
|
||||
}
|
||||
|
||||
if (is_dot) {
|
||||
uint alignment_mode = material_flags & GP_STROKE_ALIGNMENT;
|
||||
|
||||
@@ -330,28 +493,53 @@ float4 gpencil_vertex(float4 viewport_res,
|
||||
|
||||
out_ndc.xy += (x * x_axis + y * y_axis) * viewport_res.zw * clamped_thickness;
|
||||
|
||||
out_sspos.xy = ss1;
|
||||
out_sspos.zw = ss1 + x_axis * 0.5f;
|
||||
out_thickness.x = (is_squares) ? 1e18f : (clamped_thickness / out_ndc.w);
|
||||
out_thickness.y = (is_squares) ? 1e18f : (thickness / out_ndc.w);
|
||||
out_thickness.z = MITER_LIMIT_TYPE_ROUND;
|
||||
out_thickness.w = MITER_LIMIT_TYPE_ROUND;
|
||||
}
|
||||
else {
|
||||
bool is_stroke_start = (ma.x == -1 && x == -1);
|
||||
bool is_stroke_end = (ma3.x == -1 && x == 1);
|
||||
|
||||
float miter_limit1 = gpencil_decode_miter_limit(point_data1.packed_data);
|
||||
float miter_limit2 = gpencil_decode_miter_limit(point_data2.packed_data);
|
||||
|
||||
float cos_angle1 = -dot(line, line1);
|
||||
float cos_angle2 = -dot(line, line2);
|
||||
|
||||
out_thickness.z = miter_limit1;
|
||||
out_thickness.w = miter_limit2;
|
||||
|
||||
if (cos_angle1 > miter_limit1 && miter_limit1 != MITER_LIMIT_TYPE_ROUND) {
|
||||
out_thickness.z = MITER_LIMIT_TYPE_BEVEL;
|
||||
}
|
||||
if (cos_angle2 > miter_limit2 && miter_limit2 != MITER_LIMIT_TYPE_ROUND) {
|
||||
out_thickness.w = MITER_LIMIT_TYPE_BEVEL;
|
||||
}
|
||||
|
||||
float miter_limit = use_curr ? miter_limit1 : miter_limit2;
|
||||
|
||||
if (miter_limit == MITER_LIMIT_TYPE_BEVEL || miter_limit == MITER_LIMIT_TYPE_ROUND) {
|
||||
miter_limit = 0.5f; /* Default to cos(60) */
|
||||
}
|
||||
|
||||
/* Prevent the limit from becoming to small. */
|
||||
if (miter_limit < 0.5f) {
|
||||
miter_limit = 0.5f;
|
||||
}
|
||||
|
||||
/* Mitter tangent vector. */
|
||||
float2 miter_tan = safe_normalize(line_adj + line);
|
||||
float miter_dot = dot(miter_tan, line_adj);
|
||||
float cos_angle_adj = (use_curr) ? cos_angle1 : cos_angle2;
|
||||
/* Break corners after a certain angle to avoid really thick corners. */
|
||||
const float miter_limit = 0.5f; /* cos(60 degrees) */
|
||||
bool miter_break = (miter_dot < miter_limit);
|
||||
bool miter_break = cos_angle_adj > miter_limit;
|
||||
miter_tan = (miter_break || is_stroke_start || is_stroke_end) ? line :
|
||||
(miter_tan / miter_dot);
|
||||
/* Rotate 90 degrees counter-clockwise. */
|
||||
float2 miter = float2(-miter_tan.y, miter_tan.x);
|
||||
|
||||
out_sspos.xy = ss1;
|
||||
out_sspos.zw = ss2;
|
||||
out_thickness.x = clamped_thickness / out_ndc.w;
|
||||
out_thickness.y = thickness / out_ndc.w;
|
||||
out_aspect = float2(1.0f);
|
||||
@@ -378,9 +566,12 @@ float4 gpencil_vertex(float4 viewport_res,
|
||||
out_uv = uv1.xy;
|
||||
out_thickness.x = 1e18f;
|
||||
out_thickness.y = 1e20f;
|
||||
out_thickness.z = MITER_LIMIT_TYPE_ROUND;
|
||||
out_thickness.w = MITER_LIMIT_TYPE_ROUND;
|
||||
out_hardness = 1.0f;
|
||||
out_aspect = float2(1.0f);
|
||||
out_sspos = float4(0.0f);
|
||||
out_sspos_adj = float4(0.0f);
|
||||
|
||||
/* Flat normal following camera and object bounds. */
|
||||
float3 V = drw_world_incident_vector(drw_modelmat()[3].xyz);
|
||||
@@ -411,8 +602,9 @@ float4 gpencil_vertex(float4 viewport_res,
|
||||
out float out_strength,
|
||||
out float2 out_uv,
|
||||
out float4 out_sspos,
|
||||
out float4 out_sspos_adj,
|
||||
out float2 out_aspect,
|
||||
out float2 out_thickness,
|
||||
out float4 out_thickness,
|
||||
out float out_hardness)
|
||||
{
|
||||
return gpencil_vertex(viewport_res,
|
||||
@@ -424,6 +616,7 @@ float4 gpencil_vertex(float4 viewport_res,
|
||||
out_strength,
|
||||
out_uv,
|
||||
out_sspos,
|
||||
out_sspos_adj,
|
||||
out_aspect,
|
||||
out_thickness,
|
||||
out_hardness);
|
||||
|
||||
@@ -4722,6 +4722,146 @@ static void GREASE_PENCIL_OT_convert_curve_type(wmOperatorType *ot)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Set Corner Type Operator
|
||||
* \{ */
|
||||
|
||||
enum class CornerType : uint8_t {
|
||||
Round = 0,
|
||||
Bevel = 1,
|
||||
Miter = 2,
|
||||
};
|
||||
|
||||
static const EnumPropertyItem prop_corner_types[] = {
|
||||
{int(CornerType::Round), "ROUND", 0, "Round", ""},
|
||||
{int(CornerType::Bevel), "FLAT", 0, "Flat", ""},
|
||||
{int(CornerType::Miter), "SHARP", 0, "Sharp", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static wmOperatorStatus grease_pencil_set_corner_type_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
using bke::greasepencil::Layer;
|
||||
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
Object *object = CTX_data_active_object(C);
|
||||
View3D *v3d = CTX_wm_view3d(C);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
|
||||
|
||||
const CornerType corner_type = CornerType(RNA_enum_get(op->ptr, "corner_type"));
|
||||
float miter_angle = RNA_float_get(op->ptr, "miter_angle");
|
||||
|
||||
if (corner_type == CornerType::Round) {
|
||||
miter_angle = GP_STROKE_MITER_ANGLE_ROUND;
|
||||
}
|
||||
else if (corner_type == CornerType::Bevel) {
|
||||
miter_angle = GP_STROKE_MITER_ANGLE_BEVEL;
|
||||
}
|
||||
else if (corner_type == CornerType::Miter) {
|
||||
/* Prevent the angle from being set to zero, and becoming the `Round` type.*/
|
||||
if (miter_angle == 0.0f) {
|
||||
miter_angle = DEG2RADF(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<bool> changed = false;
|
||||
const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
|
||||
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask selection = ed::greasepencil::retrieve_editable_and_all_selected_points(
|
||||
*object, info.drawing, info.layer_index, v3d->overlay.handle_display, memory);
|
||||
if (selection.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
|
||||
/* Only create the attribute if we are not storing the default. */
|
||||
if (miter_angle == GP_STROKE_MITER_ANGLE_ROUND && !attributes.contains("miter_angle")) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove the attribute if we are storing all default. */
|
||||
if (miter_angle == GP_STROKE_MITER_ANGLE_ROUND && selection == curves.points_range()) {
|
||||
attributes.remove("miter_angle");
|
||||
changed.store(true, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bke::SpanAttributeWriter<float> miter_angles =
|
||||
attributes.lookup_or_add_for_write_span<float>(
|
||||
"miter_angle",
|
||||
bke::AttrDomain::Point,
|
||||
bke::AttributeInitVArray(
|
||||
VArray<float>::from_single(GP_STROKE_MITER_ANGLE_ROUND, curves.points_num()))))
|
||||
{
|
||||
index_mask::masked_fill(miter_angles.span, miter_angle, selection);
|
||||
miter_angles.finish();
|
||||
changed.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void grease_pencil_set_corner_type_ui(bContext *C, wmOperator *op)
|
||||
{
|
||||
uiLayout *layout = op->layout;
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
|
||||
PointerRNA ptr = RNA_pointer_create_discrete(&wm->id, op->type->srna, op->properties);
|
||||
|
||||
layout->use_property_split_set(true);
|
||||
layout->use_property_decorate_set(false);
|
||||
|
||||
layout->prop(&ptr, "corner_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
const CornerType corner_type = CornerType(RNA_enum_get(op->ptr, "corner_type"));
|
||||
|
||||
if (corner_type != CornerType::Miter) {
|
||||
return;
|
||||
}
|
||||
|
||||
layout->prop(&ptr, "miter_angle", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static void GREASE_PENCIL_OT_set_corner_type(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Set Corner Type";
|
||||
ot->idname = "GREASE_PENCIL_OT_set_corner_type";
|
||||
ot->description = "Set the corner type of the selected points";
|
||||
|
||||
/* Callbacks. */
|
||||
ot->exec = grease_pencil_set_corner_type_exec;
|
||||
ot->poll = editable_grease_pencil_poll;
|
||||
ot->ui = grease_pencil_set_corner_type_ui;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
/* Properties */
|
||||
ot->prop = RNA_def_enum(
|
||||
ot->srna, "corner_type", prop_corner_types, int(CornerType::Miter), "Corner Type", "");
|
||||
ot->prop = RNA_def_float_distance(ot->srna,
|
||||
"miter_angle",
|
||||
DEG2RADF(45.0f),
|
||||
0.0f,
|
||||
M_PI,
|
||||
"Miter Cut Angle",
|
||||
"All corners sharper than the Miter angle will be cut flat",
|
||||
0.0f,
|
||||
M_PI);
|
||||
RNA_def_property_subtype(ot->prop, PROP_ANGLE);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::ed::greasepencil
|
||||
|
||||
void ED_operatortypes_grease_pencil_edit()
|
||||
@@ -4765,6 +4905,7 @@ void ED_operatortypes_grease_pencil_edit()
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_remove_fill_guides);
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_outline);
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_convert_curve_type);
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_set_corner_type);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -550,13 +550,14 @@ static void generate_cap(const float3 &point,
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a corner between two segments, with a rounded outer perimeter.
|
||||
/* Generate a corner between two segments, using `miter_limit_angle` as the corner type.
|
||||
* NOTE: The perimeter is considered to be to the right hand side of the stroke. The left side
|
||||
* perimeter can be generated by reversing the order of points. */
|
||||
static void generate_corner(const float3 &pt_a,
|
||||
const float3 &pt_b,
|
||||
const float3 &pt_c,
|
||||
const float radius,
|
||||
const float miter_limit_angle,
|
||||
const int corner_subdivisions,
|
||||
const int src_point_index,
|
||||
Vector<float3> &r_perimeter,
|
||||
@@ -573,7 +574,7 @@ static void generate_corner(const float3 &pt_a,
|
||||
/* Whether the corner is an inside or outside corner.
|
||||
* This determines whether an arc is added or a single miter point. */
|
||||
const bool is_outside_corner = (sin_angle >= 0.0f);
|
||||
if (is_outside_corner) {
|
||||
if (is_outside_corner && miter_limit_angle <= GP_STROKE_MITER_ANGLE_ROUND) {
|
||||
generate_arc_from_point_to_point(pt_b + normal_prev * radius,
|
||||
pt_b + normal * radius,
|
||||
pt_b,
|
||||
@@ -581,21 +582,38 @@ static void generate_corner(const float3 &pt_a,
|
||||
src_point_index,
|
||||
r_perimeter,
|
||||
r_src_indices);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
const float2 avg_tangent = math::normalize(tangent_prev + tangent);
|
||||
const float3 miter = {avg_tangent.y, -avg_tangent.x, 0.0f};
|
||||
const float miter_invscale = math::dot(normal, miter);
|
||||
|
||||
/* Avoid division by tiny values for steep angles. */
|
||||
const float3 miter_point = (radius < length * miter_invscale &&
|
||||
radius < length_prev * miter_invscale) ?
|
||||
pt_b + miter * radius / miter_invscale :
|
||||
pt_b + miter * radius;
|
||||
const float2 avg_tangent = math::normalize(tangent_prev + tangent);
|
||||
const float3 miter = {avg_tangent.y, -avg_tangent.x, 0.0f};
|
||||
const float miter_invscale = math::dot(normal, miter);
|
||||
|
||||
r_perimeter.append(miter_point);
|
||||
r_src_indices.append(src_point_index);
|
||||
if (is_outside_corner) {
|
||||
const bool is_bevel = -math::dot(tangent, tangent_prev) > math::cos(miter_limit_angle);
|
||||
if (is_bevel) {
|
||||
r_perimeter.append(pt_b + normal_prev * radius);
|
||||
r_perimeter.append(pt_b + normal * radius);
|
||||
r_src_indices.append_n_times(src_point_index, 2);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
const float3 miter_point = pt_b + miter * radius / miter_invscale;
|
||||
|
||||
r_perimeter.append(miter_point);
|
||||
r_src_indices.append(src_point_index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Avoid division by tiny values for steep angles. */
|
||||
const float3 miter_point = (radius < length * miter_invscale &&
|
||||
radius < length_prev * miter_invscale) ?
|
||||
pt_b + miter * radius / miter_invscale :
|
||||
pt_b + miter * radius;
|
||||
|
||||
r_perimeter.append(miter_point);
|
||||
r_src_indices.append(src_point_index);
|
||||
}
|
||||
|
||||
static void generate_stroke_perimeter(const Span<float3> all_positions,
|
||||
@@ -606,6 +624,7 @@ static void generate_stroke_perimeter(const Span<float3> all_positions,
|
||||
const bool use_caps,
|
||||
const eGPDstroke_Caps start_cap_type,
|
||||
const eGPDstroke_Caps end_cap_type,
|
||||
const VArray<float> miter_angles,
|
||||
const float outline_offset,
|
||||
Vector<float3> &r_perimeter,
|
||||
Vector<int> &r_point_counts,
|
||||
@@ -636,8 +655,16 @@ static void generate_stroke_perimeter(const Span<float3> all_positions,
|
||||
const float3 pt_b = positions[b];
|
||||
const float3 pt_c = positions[c];
|
||||
const float radius = std::max(all_radii[point] + outline_offset, 0.0f);
|
||||
generate_corner(
|
||||
pt_a, pt_b, pt_c, radius, corner_subdivisions, point, r_perimeter, r_point_indices);
|
||||
const float miter_angle = miter_angles[point];
|
||||
generate_corner(pt_a,
|
||||
pt_b,
|
||||
pt_c,
|
||||
radius,
|
||||
miter_angle,
|
||||
corner_subdivisions,
|
||||
point,
|
||||
r_perimeter,
|
||||
r_point_indices);
|
||||
};
|
||||
auto add_cap = [&](const int center_i, const int next_i, const eGPDstroke_Caps cap_type) {
|
||||
const int point = points[center_i];
|
||||
@@ -748,6 +775,8 @@ bke::CurvesGeometry create_curves_outline(const bke::greasepencil::Drawing &draw
|
||||
"end_cap", bke::AttrDomain::Curve, GP_STROKE_CAP_ROUND);
|
||||
const VArray<int> src_material_index = *src_attributes.lookup_or_default(
|
||||
"material_index", bke::AttrDomain::Curve, 0);
|
||||
const VArray<float> miter_angles = *src_attributes.lookup_or_default<float>(
|
||||
"miter_angle", bke::AttrDomain::Point, GP_STROKE_MITER_ANGLE_ROUND);
|
||||
|
||||
/* Transform positions and radii. */
|
||||
Array<float3> transformed_positions(src_positions.size());
|
||||
@@ -784,6 +813,7 @@ bke::CurvesGeometry create_curves_outline(const bke::greasepencil::Drawing &draw
|
||||
use_caps,
|
||||
eGPDstroke_Caps(src_start_caps[curve_i]),
|
||||
eGPDstroke_Caps(src_end_caps[curve_i]),
|
||||
miter_angles,
|
||||
outline_offset,
|
||||
data.positions,
|
||||
data.point_counts,
|
||||
|
||||
@@ -60,6 +60,9 @@ typedef enum GreasePencilStrokeCapType {
|
||||
GP_STROKE_CAP_TYPE_MAX,
|
||||
} GreasePencilStrokeCapType;
|
||||
|
||||
#define GP_STROKE_MITER_ANGLE_ROUND 0.0f
|
||||
#define GP_STROKE_MITER_ANGLE_BEVEL DEG2RADF(180.0f)
|
||||
|
||||
/**
|
||||
* Type of drawing data.
|
||||
* If `GP_DRAWING` the node is a `GreasePencilDrawing`,
|
||||
|
||||
Reference in New Issue
Block a user