diff --git a/source/blender/blenkernel/intern/grease_pencil.cc b/source/blender/blenkernel/intern/grease_pencil.cc index f2fd1fbfe59..ee1e0cb185d 100644 --- a/source/blender/blenkernel/intern/grease_pencil.cc +++ b/source/blender/blenkernel/intern/grease_pencil.cc @@ -464,7 +464,8 @@ static float4x2 get_local_to_stroke_matrix(const Span positions, const f */ static float3x2 get_stroke_to_texture_matrix(const float uv_rotation, const float2 uv_translation, - const float2 uv_scale) + const float2 uv_scale, + const float uv_shear) { using namespace blender::math; @@ -480,8 +481,11 @@ static float3x2 get_stroke_to_texture_matrix(const float uv_rotation, * * The translation is applied last so that the origin goes to `uv_translation` * The rotation is applied after the scale so that the `u` direction's angle is `uv_rotation` - * Scale is the only transform that changes the length of the basis vectors and if it is applied - * first it's independent of the other transforms. + * Scale is the only transform that changes the length of the `u` basis vector and if it is + * applied first it's independent of the other transforms. + * The shear can be applied in any order + * as long as it is after scale so that the shear factor is in units of scale. The shear only + * changes the `v` basis vector. * * These properties are not true with a different order. */ @@ -492,6 +496,9 @@ static float3x2 get_stroke_to_texture_matrix(const float uv_rotation, /* Apply rotation. */ texture_matrix = rot * texture_matrix; + /* Apply shear. */ + texture_matrix[1] += texture_matrix[0] * uv_shear; + /* Apply translation. */ texture_matrix[2] += uv_translation; @@ -530,6 +537,8 @@ Span Drawing::texture_matrices() const "uv_translation", AttrDomain::Curve, float2(0.0f, 0.0f)); const VArray uv_scales = *attributes.lookup_or_default( "uv_scale", AttrDomain::Curve, float2(1.0f, 1.0f)); + const VArray uv_shears = *attributes.lookup_or_default( + "uv_shear", AttrDomain::Curve, 0.0f); const OffsetIndices points_by_curve = curves.points_by_curve(); const Span positions = curves.positions(); @@ -541,8 +550,10 @@ Span Drawing::texture_matrices() const const IndexRange points = points_by_curve[curve_i]; const float3 normal = normals[curve_i]; const float4x2 strokemat = get_local_to_stroke_matrix(positions.slice(points), normal); - const float3x2 texture_matrix = get_stroke_to_texture_matrix( - uv_rotations[curve_i], uv_translations[curve_i], uv_scales[curve_i]); + const float3x2 texture_matrix = get_stroke_to_texture_matrix(uv_rotations[curve_i], + uv_translations[curve_i], + uv_scales[curve_i], + uv_shears[curve_i]); const float4x2 texspace = texture_matrix * expand_4x2_mat(strokemat); @@ -558,6 +569,7 @@ void Drawing::set_texture_matrices(Span matrices, const IndexMask &sel using namespace blender::math; CurvesGeometry &curves = this->strokes_for_write(); MutableAttributeAccessor attributes = curves.attributes_for_write(); + const bool did_shear_exist = attributes.contains("uv_shear"); SpanAttributeWriter uv_rotations = attributes.lookup_or_add_for_write_span( "uv_rotation", AttrDomain::Curve); SpanAttributeWriter uv_translations = attributes.lookup_or_add_for_write_span( @@ -566,11 +578,15 @@ void Drawing::set_texture_matrices(Span matrices, const IndexMask &sel "uv_scale", AttrDomain::Curve, AttributeInitVArray(VArray::ForSingle(float2(1.0f, 1.0f), curves.curves_num()))); + SpanAttributeWriter uv_shears = attributes.lookup_or_add_for_write_span( + "uv_shear", AttrDomain::Curve); const OffsetIndices points_by_curve = curves.points_by_curve(); const Span positions = curves.positions(); const Span normals = this->curve_plane_normals(); + std::atomic is_shear_used = false; + selection.foreach_index(GrainSize(256), [&](const int64_t curve_i, const int64_t pos) { const IndexRange points = points_by_curve[curve_i]; const float3 normal = normals[curve_i]; @@ -612,18 +628,37 @@ void Drawing::set_texture_matrices(Span matrices, const IndexMask &sel /* Calculate the determinant to check if the `v` scale is negative. */ const float det = determinant(float2x2(texture_matrix)); - /* Solve scale, scaling is the only transformation that changes the length, so scale factor - * is simply the length. And flip the sign of `v` if the determinant is negative. */ + /* Solve shear, without shear the `u` and `v` basis vectors are orthogonal, so the shear is + * just their dot product with scaling. */ + const float uv_shear = dot(texture_matrix[0], texture_matrix[1]) / + dot(texture_matrix[0], texture_matrix[0]); + /* Calculate the what the `v` basis would be without the shear. */ + const float2 V_basis_without_shear = texture_matrix[1] - texture_matrix[0] * uv_shear; + + /* Solve scale, scaling is the only transformation that changes the length of `u` basis, so + * scale factor is simply the length. And flip the sign of `v` if the determinant is negative. + */ const float2 uv_scale = safe_rcp( - float2(length(texture_matrix[0]), sign(det) * length(texture_matrix[1]))); + float2(length(texture_matrix[0]), sign(det) * length(V_basis_without_shear))); uv_rotations.span[curve_i] = uv_rotation; uv_translations.span[curve_i] = uv_translation; uv_scales.span[curve_i] = uv_scale; + uv_shears.span[curve_i] = uv_shear; + + if (abs(uv_shear) > std::numeric_limits::epsilon() * 10.0f) { + is_shear_used.store(true, std::memory_order_relaxed); + } }); uv_rotations.finish(); uv_translations.finish(); uv_scales.finish(); + uv_shears.finish(); + + /* Remove the attribute if it is only storing zeros. */ + if (!is_shear_used && (!did_shear_exist || selection.size() == curves.curves_num())) { + attributes.remove("uv_shear"); + } this->tag_texture_matrices_changed(); }