diff --git a/scripts/startup/bl_ui/properties_data_modifier.py b/scripts/startup/bl_ui/properties_data_modifier.py index 32ffc546955..5f5a5f154d8 100644 --- a/scripts/startup/bl_ui/properties_data_modifier.py +++ b/scripts/startup/bl_ui/properties_data_modifier.py @@ -108,6 +108,7 @@ class OBJECT_MT_modifier_add_edit(ModifierAddMenu, Menu): self.operator_modifier_add(layout, 'VERTEX_WEIGHT_MIX') self.operator_modifier_add(layout, 'VERTEX_WEIGHT_PROXIMITY') if ob_type == 'GREASEPENCIL': + self.operator_modifier_add(layout, 'GREASE_PENCIL_TEXTURE') self.operator_modifier_add(layout, 'GREASE_PENCIL_TIME') self.operator_modifier_add(layout, 'GREASE_PENCIL_VERTEX_WEIGHT_PROXIMITY') self.operator_modifier_add(layout, 'GREASE_PENCIL_VERTEX_WEIGHT_ANGLE') diff --git a/source/blender/blenkernel/BKE_grease_pencil.hh b/source/blender/blenkernel/BKE_grease_pencil.hh index 5290abdfe40..813daea214a 100644 --- a/source/blender/blenkernel/BKE_grease_pencil.hh +++ b/source/blender/blenkernel/BKE_grease_pencil.hh @@ -76,12 +76,12 @@ class Drawing : public ::GreasePencilDrawing { void tag_positions_changed(); void tag_topology_changed(); - /* + /** * Returns the matrices that transform from a 3D point in layer-space to a 2D point in * texture-space. */ Span texture_matrices() const; - /* + /** * Sets the matrices the that transform from a 3D point in layer-space to a 2D point in * texture-space */ diff --git a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc index 5d7445fc1ea..5314248d44a 100644 --- a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc +++ b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc @@ -1840,6 +1840,55 @@ static void legacy_object_modifier_subdiv(Object &object, GpencilModifierData &l false); } +static void legacy_object_modifier_texture(Object &object, GpencilModifierData &legacy_md) +{ + ModifierData &md = legacy_object_modifier_common( + object, eModifierType_GreasePencilTexture, legacy_md); + auto &md_texture = reinterpret_cast(md); + auto &legacy_md_texture = reinterpret_cast(legacy_md); + + switch (eTextureGpencil_Mode(legacy_md_texture.mode)) { + case STROKE: + md_texture.mode = MOD_GREASE_PENCIL_TEXTURE_STROKE; + break; + case FILL: + md_texture.mode = MOD_GREASE_PENCIL_TEXTURE_FILL; + break; + case STROKE_AND_FILL: + md_texture.mode = MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL; + break; + } + switch (eTextureGpencil_Fit(legacy_md_texture.fit_method)) { + case GP_TEX_FIT_STROKE: + md_texture.fit_method = MOD_GREASE_PENCIL_TEXTURE_FIT_STROKE; + break; + case GP_TEX_CONSTANT_LENGTH: + md_texture.fit_method = MOD_GREASE_PENCIL_TEXTURE_CONSTANT_LENGTH; + break; + } + md_texture.uv_offset = legacy_md_texture.uv_offset; + md_texture.uv_scale = legacy_md_texture.uv_scale; + md_texture.fill_rotation = legacy_md_texture.fill_rotation; + copy_v2_v2(md_texture.fill_offset, legacy_md_texture.fill_offset); + md_texture.fill_scale = legacy_md_texture.fill_scale; + md_texture.layer_pass = legacy_md_texture.layer_pass; + md_texture.alignment_rotation = legacy_md_texture.alignment_rotation; + + legacy_object_modifier_influence(md_texture.influence, + legacy_md_texture.layername, + legacy_md_texture.layer_pass, + legacy_md_texture.flag & GP_TEX_INVERT_LAYER, + legacy_md_texture.flag & GP_TEX_INVERT_LAYERPASS, + &legacy_md_texture.material, + legacy_md_texture.pass_index, + legacy_md_texture.flag & GP_TEX_INVERT_MATERIAL, + legacy_md_texture.flag & GP_TEX_INVERT_PASS, + legacy_md_texture.vgname, + legacy_md_texture.flag & GP_TEX_INVERT_VGROUP, + nullptr, + false); +} + static void legacy_object_modifier_thickness(Object &object, GpencilModifierData &legacy_md) { ModifierData &md = legacy_object_modifier_common( @@ -2286,6 +2335,9 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object) case eGpencilModifierType_Subdiv: legacy_object_modifier_subdiv(object, *gpd_md); break; + case eGpencilModifierType_Texture: + legacy_object_modifier_texture(object, *gpd_md); + break; case eGpencilModifierType_Thick: legacy_object_modifier_thickness(object, *gpd_md); break; @@ -2310,7 +2362,6 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object) case eGpencilModifierType_Simplify: legacy_object_modifier_simplify(object, *gpd_md); break; - case eGpencilModifierType_Texture: break; } diff --git a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc index 41690c54f19..7b5d314427d 100644 --- a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc +++ b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc @@ -538,6 +538,10 @@ static void grease_pencil_geom_batch_ensure(Object &object, "fill_color", bke::AttrDomain::Curve, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); const VArray materials = *attributes.lookup_or_default( "material_index", bke::AttrDomain::Curve, 0); + const VArray u_translations = *attributes.lookup_or_default( + "u_translation", bke::AttrDomain::Curve, 0.0f); + const VArray u_scales = *attributes.lookup_or_default( + "u_scale", bke::AttrDomain::Curve, 1.0f); const Span triangles = info.drawing.triangles(); const Span texture_matrices = info.drawing.texture_matrices(); const Span verts_start_offsets = verts_start_offsets_per_visible_drawing[drawing_i]; @@ -554,7 +558,7 @@ static void grease_pencil_geom_batch_ensure(Object &object, int8_t end_cap, int point_i, int idx, - float length, + float u_stroke, const float4x2 &texture_matrix, GreasePencilStrokeVert &s_vert, GreasePencilColorVert &c_vert) { @@ -572,7 +576,7 @@ static void grease_pencil_geom_batch_ensure(Object &object, s_vert.packed_asp_hard_rot = pack_rotation_aspect_hardness( rotations[point_i], stroke_point_aspect_ratios[curve_i], stroke_hardnesses[curve_i]); - s_vert.u_stroke = length; + s_vert.u_stroke = u_stroke; copy_v2_v2(s_vert.uv_fill, texture_matrix * float4(pos, 1.0f)); copy_v4_v4(c_vert.vcol, vertex_colors[point_i]); @@ -612,16 +616,18 @@ static void grease_pencil_geom_batch_ensure(Object &object, } /* Write all the point attributes to the vertex buffers. Create a quad for each point. */ + const float u_scale = u_scales[curve_i]; + const float u_translation = u_translations[curve_i]; for (const int i : IndexRange(points.size())) { const int idx = i + 1; - const float length = (i >= 1) ? lengths[i - 1] : 0.0f; + const float u_stroke = u_scale * (i > 0 ? lengths[i - 1] : 0.0f) + u_translation; populate_point(verts_range, curve_i, start_caps[curve_i], end_caps[curve_i], points[i], idx, - length, + u_stroke, texture_matrix, verts_slice[idx], cols_slice[idx]); @@ -629,14 +635,14 @@ static void grease_pencil_geom_batch_ensure(Object &object, if (is_cyclic) { const int idx = points.size() + 1; - const float length = points.size() > 1 ? lengths[points.size() - 1] : 0.0f; + const float u_stroke = u_scale * lengths[points.size() - 1] + u_translation; populate_point(verts_range, curve_i, start_caps[curve_i], end_caps[curve_i], points[0], idx, - length, + u_stroke, texture_matrix, verts_slice[idx], cols_slice[idx]); diff --git a/source/blender/makesdna/DNA_modifier_defaults.h b/source/blender/makesdna/DNA_modifier_defaults.h index 8359ba8e06a..98583a9b045 100644 --- a/source/blender/makesdna/DNA_modifier_defaults.h +++ b/source/blender/makesdna/DNA_modifier_defaults.h @@ -1070,6 +1070,16 @@ .length = 0.1f, \ .distance = 0.1f, \ } - - + +#define _DNA_DEFAULT_GreasePencilTextureModifierData \ + { \ + .uv_offset = 0.0f, \ + .uv_scale = 1.0f, \ + .fill_rotation = 0.0f, \ + .fill_offset = {0.0f, 0.0f}, \ + .fill_scale = 1.0f, \ + .fit_method = GP_TEX_CONSTANT_LENGTH, \ + .mode = 0, \ + } + /* clang-format off */ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 0e93ee93194..da449202f0c 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -120,6 +120,7 @@ typedef enum ModifierType { eModifierType_GreasePencilShrinkwrap = 83, eModifierType_GreasePencilBuild = 84, eModifierType_GreasePencilSimplify = 85, + eModifierType_GreasePencilTexture = 86, NUM_MODIFIER_TYPES, } ModifierType; @@ -3430,3 +3431,35 @@ typedef enum GreasePencilSimplifyModifierMode { MOD_GREASE_PENCIL_SIMPLIFY_SAMPLE = 2, MOD_GREASE_PENCIL_SIMPLIFY_MERGE = 3, } GreasePencilSimplifyModifierMode; + +typedef struct GreasePencilTextureModifierData { + ModifierData modifier; + GreasePencilModifierInfluenceData influence; + /* Offset value to add to uv_fac. */ + float uv_offset; + float uv_scale; + float fill_rotation; + float fill_offset[2]; + float fill_scale; + /* Custom index for passes. */ + int layer_pass; + /* Texture fit options. */ + short fit_method; + short mode; + /* Dot texture rotation. */ + float alignment_rotation; + char _pad[4]; +} GreasePencilTextureModifierData; + +/* Texture->fit_method */ +typedef enum GreasePencilTextureModifierFit { + MOD_GREASE_PENCIL_TEXTURE_FIT_STROKE = 0, + MOD_GREASE_PENCIL_TEXTURE_CONSTANT_LENGTH = 1, +} GreasePencilTextureModifierFit; + +/* Texture->mode */ +typedef enum GreasePencilTextureModifierMode { + MOD_GREASE_PENCIL_TEXTURE_STROKE = 0, + MOD_GREASE_PENCIL_TEXTURE_FILL = 1, + MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL = 2, +} GreasePencilTextureModifierMode; diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index e06642ff84c..fa37868e589 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -360,6 +360,7 @@ SDNA_DEFAULT_DECL_STRUCT(GreasePencilSimplifyModifierData); SDNA_DEFAULT_DECL_STRUCT(GreasePencilEnvelopeModifierData); SDNA_DEFAULT_DECL_STRUCT(GreasePencilOutlineModifierData); SDNA_DEFAULT_DECL_STRUCT(GreasePencilShrinkwrapModifierData); +SDNA_DEFAULT_DECL_STRUCT(GreasePencilTextureModifierData); #undef SDNA_DEFAULT_DECL_STRUCT @@ -637,6 +638,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { SDNA_DEFAULT_DECL(GreasePencilEnvelopeModifierData), SDNA_DEFAULT_DECL(GreasePencilOutlineModifierData), SDNA_DEFAULT_DECL(GreasePencilShrinkwrapModifierData), + SDNA_DEFAULT_DECL(GreasePencilTextureModifierData), }; #undef SDNA_DEFAULT_DECL #undef SDNA_DEFAULT_DECL_EX diff --git a/source/blender/makesrna/intern/rna_modifier.cc b/source/blender/makesrna/intern/rna_modifier.cc index 1fcc6284cf5..96874837064 100644 --- a/source/blender/makesrna/intern/rna_modifier.cc +++ b/source/blender/makesrna/intern/rna_modifier.cc @@ -134,6 +134,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = { ICON_MOD_TIME, "Time Offset", "Offset keyframes"}, + {eModifierType_GreasePencilTexture, + "GREASE_PENCIL_TEXTURE", + ICON_MOD_UVPROJECT, + "Texture Mapping", + "Change stroke UV texture values"}, RNA_ENUM_ITEM_HEADING(N_("Generate"), nullptr), {eModifierType_Array, @@ -2002,6 +2007,7 @@ RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilEnvelope); RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilOutline); RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilShrinkwrap); RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilBuild); +RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilTexture); RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOffset); RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOpacity); @@ -2017,6 +2023,7 @@ RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilArmature); RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilSimplify); RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilEnvelope); RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilShrinkwrap); +RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilTexture); static void rna_GreasePencilLineartModifier_material_set(PointerRNA *ptr, PointerRNA value, @@ -10805,6 +10812,116 @@ static void rna_def_modifier_grease_pencil_build(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_modifier_grease_pencil_texture(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static const EnumPropertyItem fit_type_items[] = { + {MOD_GREASE_PENCIL_TEXTURE_CONSTANT_LENGTH, + "CONSTANT_LENGTH", + 0, + "Constant Length", + "Keep the texture at a constant length regardless of the length of each stroke"}, + {MOD_GREASE_PENCIL_TEXTURE_FIT_STROKE, + "FIT_STROKE", + 0, + "Stroke Length", + "Scale the texture to fit the length of each stroke"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + static const EnumPropertyItem mode_items[] = { + {MOD_GREASE_PENCIL_TEXTURE_STROKE, + "STROKE", + 0, + "Stroke", + "Manipulate only stroke texture coordinates"}, + {MOD_GREASE_PENCIL_TEXTURE_FILL, + "FILL", + 0, + "Fill", + "Manipulate only fill texture coordinates"}, + {MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL, + "STROKE_AND_FILL", + 0, + "Stroke & Fill", + "Manipulate both stroke and fill texture coordinates"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + srna = RNA_def_struct(brna, "GreasePencilTextureModifier", "Modifier"); + RNA_def_struct_ui_text( + srna, "Grease Pencil Texture Modifier", "Transform stroke texture coordinates Modifier"); + RNA_def_struct_sdna(srna, "GreasePencilTextureModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MOD_UVPROJECT); + + rna_def_modifier_grease_pencil_layer_filter(srna); + rna_def_modifier_grease_pencil_material_filter( + srna, "rna_GreasePencilTextureModifier_material_filter_set"); + rna_def_modifier_grease_pencil_vertex_group( + srna, "rna_GreasePencilTextureModifier_vertex_group_name_set"); + + rna_def_modifier_panel_open_prop(srna, "open_influence_panel", 0); + + RNA_define_lib_overridable(true); + + prop = RNA_def_property(srna, "uv_offset", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "uv_offset"); + RNA_def_property_range(prop, -FLT_MAX, FLT_MAX); + RNA_def_property_ui_range(prop, -100.0, 100.0, 0.1, 3); + RNA_def_property_ui_text(prop, "UV Offset", "Offset value to add to stroke UVs"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "uv_scale", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "uv_scale"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0, 100.0, 0.1, 3); + RNA_def_property_ui_text(prop, "UV Scale", "Factor to scale the UVs"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + /* Rotation of Dot Texture. */ + prop = RNA_def_property(srna, "alignment_rotation", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, nullptr, "alignment_rotation"); + RNA_def_property_float_default(prop, 0.0f); + RNA_def_property_range(prop, -DEG2RADF(90.0f), DEG2RADF(90.0f)); + RNA_def_property_ui_range(prop, -DEG2RADF(90.0f), DEG2RADF(90.0f), 10, 3); + RNA_def_property_ui_text( + prop, "Rotation", "Additional rotation applied to dots and square strokes"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "fill_rotation", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, nullptr, "fill_rotation"); + RNA_def_property_ui_text(prop, "Fill Rotation", "Additional rotation of the fill UV"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "fill_offset", PROP_FLOAT, PROP_COORDS); + RNA_def_property_float_sdna(prop, nullptr, "fill_offset"); + RNA_def_property_array(prop, 2); + RNA_def_property_ui_text(prop, "Fill Offset", "Additional offset of the fill UV"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "fill_scale", PROP_FLOAT, PROP_COORDS); + RNA_def_property_float_sdna(prop, nullptr, "fill_scale"); + RNA_def_property_range(prop, 0.01f, 100.0f); + RNA_def_property_ui_text(prop, "Fill Scale", "Additional scale of the fill UV"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "fit_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "fit_method"); + RNA_def_property_enum_items(prop, fit_type_items); + RNA_def_property_ui_text(prop, "Fit Method", ""); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "mode"); + RNA_def_property_enum_items(prop, mode_items); + RNA_def_property_ui_text(prop, "Mode", ""); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + RNA_define_lib_overridable(false); +} + void RNA_def_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -10999,6 +11116,7 @@ void RNA_def_modifier(BlenderRNA *brna) rna_def_modifier_grease_pencil_outline(brna); rna_def_modifier_grease_pencil_shrinkwrap(brna); rna_def_modifier_grease_pencil_build(brna); + rna_def_modifier_grease_pencil_texture(brna); } #endif diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 347e1c58658..690e00aee35 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -68,6 +68,7 @@ set(SRC intern/MOD_grease_pencil_shrinkwrap.cc intern/MOD_grease_pencil_smooth.cc intern/MOD_grease_pencil_subdiv.cc + intern/MOD_grease_pencil_texture.cc intern/MOD_grease_pencil_thickness.cc intern/MOD_grease_pencil_time.cc intern/MOD_grease_pencil_tint.cc diff --git a/source/blender/modifiers/MOD_modifiertypes.hh b/source/blender/modifiers/MOD_modifiertypes.hh index e3cf65455d7..00ec59be17e 100644 --- a/source/blender/modifiers/MOD_modifiertypes.hh +++ b/source/blender/modifiers/MOD_modifiertypes.hh @@ -98,6 +98,7 @@ extern ModifierTypeInfo modifierType_GreasePencilEnvelope; extern ModifierTypeInfo modifierType_GreasePencilOutline; extern ModifierTypeInfo modifierType_GreasePencilShrinkwrap; extern ModifierTypeInfo modifierType_GreasePencilBuild; +extern ModifierTypeInfo modifierType_GreasePencilTexture; /* MOD_util.cc */ diff --git a/source/blender/modifiers/intern/MOD_grease_pencil_texture.cc b/source/blender/modifiers/intern/MOD_grease_pencil_texture.cc new file mode 100644 index 00000000000..8a281af94a4 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_grease_pencil_texture.cc @@ -0,0 +1,397 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup modifiers + */ + +#include "BKE_attribute.hh" +#include "BLI_index_range.hh" +#include "BLI_math_base.hh" +#include "BLI_span.hh" + +#include "DNA_defaults.h" +#include "DNA_modifier_types.h" + +#include "BKE_curves.hh" +#include "BKE_geometry_set.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_instances.hh" +#include "BKE_modifier.hh" +#include "BKE_screen.hh" + +#include "BLO_read_write.hh" + +#include "DEG_depsgraph_query.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "BLT_translation.hh" + +#include "WM_api.hh" +#include "WM_types.hh" + +#include "RNA_access.hh" +#include "RNA_prototypes.h" + +#include "MOD_grease_pencil_util.hh" +#include "MOD_ui_common.hh" + +namespace blender { + +static void init_data(ModifierData *md) +{ + auto *tmd = reinterpret_cast(md); + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(tmd, modifier)); + + MEMCPY_STRUCT_AFTER(tmd, DNA_struct_default_get(GreasePencilTextureModifierData), modifier); + modifier::greasepencil::init_influence_data(&tmd->influence, false); +} + +static void copy_data(const ModifierData *md, ModifierData *target, const int flag) +{ + const auto *tmd = reinterpret_cast(md); + auto *tmmd = reinterpret_cast(target); + + modifier::greasepencil::free_influence_data(&tmmd->influence); + + BKE_modifier_copydata_generic(md, target, flag); + modifier::greasepencil::copy_influence_data(&tmd->influence, &tmmd->influence, flag); +} + +static void free_data(ModifierData *md) +{ + auto *tmd = reinterpret_cast(md); + modifier::greasepencil::free_influence_data(&tmd->influence); +} + +static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data) +{ + auto *tmd = reinterpret_cast(md); + modifier::greasepencil::foreach_influence_ID_link(&tmd->influence, ob, walk, user_data); +} + +static void write_stroke_transforms(bke::greasepencil::Drawing &drawing, + const IndexMask &curves_mask, + const float offset, + const float rotation, + const float scale, + const bool normalize_u) +{ + bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + const VArray cyclic = curves.cyclic(); + + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + bke::SpanAttributeWriter u_translations = attributes.lookup_or_add_for_write_span( + "u_translation", bke::AttrDomain::Curve); + bke::SpanAttributeWriter rotations = attributes.lookup_or_add_for_write_span( + "rotation", bke::AttrDomain::Point); + bke::SpanAttributeWriter u_scales = attributes.lookup_or_add_for_write_span( + "u_scale", + bke::AttrDomain::Curve, + bke::AttributeInitVArray(VArray::ForSingle(1.0f, curves.curves_num()))); + + curves.ensure_evaluated_lengths(); + + curves_mask.foreach_index(GrainSize(512), [&](int64_t curve_i) { + const IndexRange points = points_by_curve[curve_i]; + const bool is_cyclic = cyclic[curve_i]; + const Span lengths = curves.evaluated_lengths_for_curve(curve_i, is_cyclic); + const float norm = normalize_u ? math::safe_rcp(lengths.last()) : 1.0f; + + u_translations.span[curve_i] += offset; + u_scales.span[curve_i] *= scale * norm; + for (const int point_i : points) { + rotations.span[point_i] += rotation; + } + }); + + u_translations.finish(); + u_scales.finish(); + rotations.finish(); +} + +static float2 rotate_by_angle(const float2 &p, const float angle) +{ + const float cos_angle = math::cos(angle); + const float sin_angle = math::sin(angle); + return float2(p.x * cos_angle - p.y * sin_angle, p.x * sin_angle + p.y * cos_angle); +} + +/* + * This gets the legacy stroke-space to layer-space matrix. + */ +static void get_legacy_stroke_matrix(const Span positions, + float3x4 &stroke_to_layer, + float4x3 &layer_to_stroke) +{ + using namespace blender; + using namespace blender::math; + + if (positions.size() < 2) { + stroke_to_layer = float3x4::identity(); + layer_to_stroke = float4x3::identity(); + } + + const float3 &pt0 = positions[0]; + const float3 &pt1 = positions[1]; + const float3 &pt3 = positions[int(positions.size() * 0.75f)]; + + /* Local X axis (p0 -> p1) */ + const float3 local_x = normalize(pt1 - pt0); + + /* Point vector at 3/4 */ + const float3 local_3 = (positions.size() == 2) ? (pt3 * 0.001f) - pt0 : pt3 - pt0; + + /* Vector orthogonal to polygon plane. */ + const float3 normal = cross(local_x, local_3); + + /* Local Y axis (cross to normal/x axis). */ + const float3 local_y = normalize(cross(normal, local_x)); + + /* Get layer space using first point as origin. */ + stroke_to_layer = float3x4(float4(local_x, 0), float4(local_y, 0), float4(pt0, 1)); + layer_to_stroke = math::transpose(float3x4(float4(local_x, -dot(pt0, local_x)), + float4(local_y, -dot(pt0, local_y)), + float4(0, 0, 0, 1))); +} + +static void write_fill_transforms(bke::greasepencil::Drawing &drawing, + const IndexMask &curves_mask, + const float2 &offset, + const float rotation, + const float scale) +{ + /* Texture matrices are a combination of an unknown 3D transform into UV space, with a known 2D + * transform on top. + * + * However, the modifier offset is not applied directly to the UV transform, since it emulates + * legacy behavior of the GPv2 modifier, which applied translation first, before rotating about + * (0.5, 0.5) and scaling. To achieve the same result as the legacy modifier, the actual offset + * is calculated such that the result matches the GPv2 behavior. + * + * The canonical transform is + * uv = T + R / S * xy + * + * In terms of legacy variables TL, RL, SL the same transform is described as + * uv = (RL * (xy / 2 + TL) + 1/2) / SL + * + * where the 1/2 scaling factor and offset are the "bounds" transform and rotation center. + * + * Rearranging into canonical loc/rot/scale terms: + * uv = (RL * TL + 1/2) / SL + 1/2 * RL / SL * xy + * <=> + * T = (RL * TL + 1/2) / SL + * R = RL + * S = 2*SL + * <=> + * TL = 1/2 * R^T * (T * S - 1) + * RL = R + * SL = S/2 + */ + + bke::CurvesGeometry &curves = drawing.strokes_for_write(); + const Span positions = curves.positions(); + Array texture_matrices(drawing.texture_matrices()); + + curves_mask.foreach_index(GrainSize(512), [&](int64_t curve_i) { + const IndexRange points = curves.points_by_curve()[curve_i]; + float4x2 &texture_matrix = texture_matrices[curve_i]; + /* Factor out the stroke-to-layer transform part used by GPv2. + * This may not be the same as the transform used by GPv3 for concave shapes due to a + * simplistic normal calculation, but we want to achieve the same effect as GPv2 so have to use + * the same matrix. */ + float3x4 stroke_to_layer; + float4x3 layer_to_stroke; + get_legacy_stroke_matrix(positions.slice(points), stroke_to_layer, layer_to_stroke); + const float3x2 uv_matrix = texture_matrix * stroke_to_layer; + const float2 uv_translation = uv_matrix[2]; + float2 inv_uv_scale; + const float2 axis_u = math::normalize_and_get_length(uv_matrix[0], inv_uv_scale[0]); + const float2 axis_v = math::normalize_and_get_length(uv_matrix[1], inv_uv_scale[1]); + const float uv_rotation = math::atan2(axis_u[1], axis_u[0]); + const float2 uv_scale = math::safe_rcp(inv_uv_scale); + + const float2 legacy_uv_translation = rotate_by_angle(0.5f * uv_scale * uv_translation - 0.5f, + -uv_rotation); + const float legacy_uv_rotation = uv_rotation; + const float2 legacy_uv_scale = 0.5f * uv_scale; + + const float2 legacy_uv_translation_new = legacy_uv_translation + offset; + const float legacy_uv_rotation_new = legacy_uv_rotation + rotation; + const float2 legacy_uv_scale_new = legacy_uv_scale * scale; + + const float2 uv_translation_new = + (rotate_by_angle(legacy_uv_translation_new, legacy_uv_rotation_new) + 0.5f) * + math::safe_rcp(legacy_uv_scale_new); + const float uv_rotation_new = legacy_uv_rotation_new; + const float2 uv_scale_new = 2.0f * legacy_uv_scale_new; + + const float cos_uv_rotation_new = math::cos(uv_rotation_new); + const float sin_uv_rotation_new = math::sin(uv_rotation_new); + const float2 inv_uv_scale_new = math::safe_rcp(uv_scale_new); + const float3x2 uv_matrix_new = float3x2( + inv_uv_scale_new[0] * float2(cos_uv_rotation_new, sin_uv_rotation_new), + inv_uv_scale_new[1] * float2(-sin_uv_rotation_new, cos_uv_rotation_new), + uv_translation_new); + texture_matrix = uv_matrix_new * layer_to_stroke; + }); + + drawing.set_texture_matrices(texture_matrices, curves_mask); +} + +static void modify_curves(const GreasePencilTextureModifierData &tmd, + const ModifierEvalContext &ctx, + bke::greasepencil::Drawing &drawing) +{ + IndexMaskMemory mask_memory; + const IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask( + ctx.object, drawing.strokes(), tmd.influence, mask_memory); + + const bool normalize_u = (tmd.fit_method == MOD_GREASE_PENCIL_TEXTURE_FIT_STROKE); + switch (GreasePencilTextureModifierMode(tmd.mode)) { + case MOD_GREASE_PENCIL_TEXTURE_STROKE: + write_stroke_transforms( + drawing, curves_mask, tmd.uv_offset, tmd.alignment_rotation, tmd.uv_scale, normalize_u); + break; + case MOD_GREASE_PENCIL_TEXTURE_FILL: + write_fill_transforms( + drawing, curves_mask, tmd.fill_offset, tmd.fill_rotation, tmd.fill_scale); + break; + case MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL: + write_stroke_transforms( + drawing, curves_mask, tmd.uv_offset, tmd.alignment_rotation, tmd.uv_scale, normalize_u); + write_fill_transforms( + drawing, curves_mask, tmd.fill_offset, tmd.fill_rotation, tmd.fill_scale); + break; + } +} + +static void modify_geometry_set(ModifierData *md, + const ModifierEvalContext *ctx, + bke::GeometrySet *geometry_set) +{ + using bke::greasepencil::Drawing; + using bke::greasepencil::Layer; + + const auto &tmd = *reinterpret_cast(md); + + if (!geometry_set->has_grease_pencil()) { + return; + } + GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write(); + + IndexMaskMemory mask_memory; + const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask( + grease_pencil, tmd.influence, mask_memory); + const int frame = grease_pencil.runtime->eval_frame; + const Vector drawings = modifier::greasepencil::get_drawings_for_write( + grease_pencil, layer_mask, frame); + threading::parallel_for_each(drawings, + [&](Drawing *drawing) { modify_curves(tmd, *ctx, *drawing); }); +} + +static void panel_draw(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA ob_ptr; + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); + const auto &tmd = *static_cast(ptr->data); + const auto mode = GreasePencilTextureModifierMode(tmd.mode); + uiLayout *col; + + uiLayoutSetPropSep(layout, true); + + uiItemR(layout, ptr, "mode", UI_ITEM_NONE, nullptr, ICON_NONE); + + if (ELEM(mode, MOD_GREASE_PENCIL_TEXTURE_STROKE, MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL)) { + col = uiLayoutColumn(layout, false); + uiItemR(col, ptr, "fit_method", UI_ITEM_NONE, IFACE_("Stroke Fit Method"), ICON_NONE); + uiItemR(col, ptr, "uv_offset", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(col, ptr, "alignment_rotation", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(col, ptr, "uv_scale", UI_ITEM_NONE, IFACE_("Scale"), ICON_NONE); + } + + if (mode == MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL) { + uiItemS(layout); + } + + if (ELEM(mode, MOD_GREASE_PENCIL_TEXTURE_FILL, MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL)) { + col = uiLayoutColumn(layout, false); + uiItemR(col, ptr, "fill_rotation", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(col, ptr, "fill_offset", UI_ITEM_NONE, IFACE_("Offset"), ICON_NONE); + uiItemR(col, ptr, "fill_scale", UI_ITEM_NONE, IFACE_("Scale"), ICON_NONE); + } + + if (uiLayout *influence_panel = uiLayoutPanelProp( + C, layout, ptr, "open_influence_panel", "Influence")) + { + modifier::greasepencil::draw_layer_filter_settings(C, influence_panel, ptr); + modifier::greasepencil::draw_material_filter_settings(C, influence_panel, ptr); + modifier::greasepencil::draw_vertex_group_settings(C, influence_panel, ptr); + } + + modifier_panel_end(layout, ptr); +} + +static void panel_register(ARegionType *region_type) +{ + modifier_panel_register(region_type, eModifierType_GreasePencilTexture, panel_draw); +} + +static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md) +{ + const auto *tmd = reinterpret_cast(md); + + BLO_write_struct(writer, GreasePencilTextureModifierData, tmd); + modifier::greasepencil::write_influence_data(writer, &tmd->influence); +} + +static void blend_read(BlendDataReader *reader, ModifierData *md) +{ + auto *tmd = reinterpret_cast(md); + + modifier::greasepencil::read_influence_data(reader, &tmd->influence); +} + +} // namespace blender + +ModifierTypeInfo modifierType_GreasePencilTexture = { + /*idname*/ "GreasePencilTexture", + /*name*/ N_("TimeOffset"), + /*struct_name*/ "GreasePencilTextureModifierData", + /*struct_size*/ sizeof(GreasePencilTextureModifierData), + /*srna*/ &RNA_GreasePencilTextureModifier, + /*type*/ ModifierTypeType::NonGeometrical, + /*flags*/ eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_SupportsEditmode | + eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping, + /*icon*/ ICON_MOD_UVPROJECT, + + /*copy_data*/ blender::copy_data, + + /*deform_verts*/ nullptr, + /*deform_matrices*/ nullptr, + /*deform_verts_EM*/ nullptr, + /*deform_matrices_EM*/ nullptr, + /*modify_mesh*/ nullptr, + /*modify_geometry_set*/ blender::modify_geometry_set, + + /*init_data*/ blender::init_data, + /*required_data_mask*/ nullptr, + /*free_data*/ blender::free_data, + /*is_disabled*/ nullptr, + /*update_depsgraph*/ nullptr, + /*depends_on_time*/ nullptr, + /*depends_on_normals*/ nullptr, + /*foreach_ID_link*/ blender::foreach_ID_link, + /*foreach_tex_link*/ nullptr, + /*free_runtime_data*/ nullptr, + /*panel_register*/ blender::panel_register, + /*blend_write*/ blender::blend_write, + /*blend_read*/ blender::blend_read, +}; diff --git a/source/blender/modifiers/intern/MOD_util.cc b/source/blender/modifiers/intern/MOD_util.cc index 6f592cbd4bc..8894f750f58 100644 --- a/source/blender/modifiers/intern/MOD_util.cc +++ b/source/blender/modifiers/intern/MOD_util.cc @@ -289,5 +289,6 @@ void modifier_type_init(ModifierTypeInfo *types[]) INIT_TYPE(GreasePencilOutline); INIT_TYPE(GreasePencilShrinkwrap); INIT_TYPE(GreasePencilBuild); + INIT_TYPE(GreasePencilTexture); #undef INIT_TYPE }