Blender V5.0
MOD_grease_pencil_texture.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_attribute.hh"
10#include "BLI_index_range.hh"
11#include "BLI_math_base.hh"
12#include "BLI_math_matrix.hh"
13#include "BLI_span.hh"
14
15#include "DNA_defaults.h"
16#include "DNA_modifier_types.h"
17
18#include "BKE_curves.hh"
19#include "BKE_geometry_set.hh"
20#include "BKE_grease_pencil.hh"
21#include "BKE_instances.hh"
22#include "BKE_modifier.hh"
23#include "BKE_screen.hh"
24
25#include "BLO_read_write.hh"
26
28
30#include "UI_resources.hh"
31
32#include "BLT_translation.hh"
33
34#include "WM_api.hh"
35#include "WM_types.hh"
36
37#include "RNA_access.hh"
38#include "RNA_prototypes.hh"
39
41#include "MOD_ui_common.hh"
42
43namespace blender {
44
54
55static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
56{
57 const auto *tmd = reinterpret_cast<const GreasePencilTextureModifierData *>(md);
58 auto *tmmd = reinterpret_cast<GreasePencilTextureModifierData *>(target);
59
61
63 modifier::greasepencil::copy_influence_data(&tmd->influence, &tmmd->influence, flag);
64}
65
66static void free_data(ModifierData *md)
67{
68 auto *tmd = reinterpret_cast<GreasePencilTextureModifierData *>(md);
70}
71
72static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
73{
74 auto *tmd = reinterpret_cast<GreasePencilTextureModifierData *>(md);
75 modifier::greasepencil::foreach_influence_ID_link(&tmd->influence, ob, walk, user_data);
76}
77
79 const IndexMask &curves_mask,
80 const float offset,
81 const float rotation,
82 const float scale,
83 const bool normalize_u)
84{
85 bke::CurvesGeometry &curves = drawing.strokes_for_write();
86 const OffsetIndices<int> points_by_curve = curves.points_by_curve();
87 const VArray<bool> cyclic = curves.cyclic();
88
90 bke::SpanAttributeWriter<float> u_translations = attributes.lookup_or_add_for_write_span<float>(
91 "u_translation", bke::AttrDomain::Curve);
93 "rotation", bke::AttrDomain::Point);
95 "u_scale",
98 if (!u_translations || !rotations || !u_scales) {
99 return;
100 }
101
103
104 curves_mask.foreach_index(GrainSize(512), [&](int64_t curve_i) {
105 const IndexRange points = points_by_curve[curve_i];
106 const bool is_cyclic = cyclic[curve_i];
107 const Span<float> lengths = curves.evaluated_lengths_for_curve(curve_i, is_cyclic);
108 const float norm = normalize_u ? math::safe_rcp(lengths.last()) : 1.0f;
109
110 u_translations.span[curve_i] += offset;
111 u_scales.span[curve_i] *= scale * norm;
112 for (const int point_i : points) {
113 rotations.span[point_i] += rotation;
114 }
115 });
116
117 u_translations.finish();
118 u_scales.finish();
119 rotations.finish();
120}
121
122static float2 rotate_by_angle(const float2 &p, const float angle)
123{
124 const float cos_angle = math::cos(angle);
125 const float sin_angle = math::sin(angle);
126 return float2(p.x * cos_angle - p.y * sin_angle, p.x * sin_angle + p.y * cos_angle);
127}
128
129/*
130 * This gets the legacy stroke-space to layer-space matrix.
131 */
132static void get_legacy_stroke_matrix(const Span<float3> positions,
133 float3x4 &stroke_to_layer,
134 float4x3 &layer_to_stroke)
135{
136 using namespace blender;
137 using namespace blender::math;
138
139 if (positions.size() < 2) {
140 stroke_to_layer = float3x4::identity();
141 layer_to_stroke = float4x3::identity();
142 }
143
144 const float3 &pt0 = positions[0];
145 const float3 &pt1 = positions[1];
146 const float3 &pt3 = positions[int(positions.size() * 0.75f)];
147
148 /* Local X axis (p0 -> p1) */
149 const float3 local_x = normalize(pt1 - pt0);
150
151 /* Point vector at 3/4 */
152 const float3 local_3 = (positions.size() == 2) ? (pt3 * 0.001f) - pt0 : pt3 - pt0;
153
154 /* Vector orthogonal to polygon plane. */
155 const float3 normal = cross(local_x, local_3);
156
157 /* Local Y axis (cross to normal/x axis). */
158 const float3 local_y = normalize(cross(normal, local_x));
159
160 /* Get layer space using first point as origin. */
161 stroke_to_layer = float3x4(float4(local_x, 0), float4(local_y, 0), float4(pt0, 1));
162 layer_to_stroke = math::transpose(float3x4(float4(local_x, -dot(pt0, local_x)),
163 float4(local_y, -dot(pt0, local_y)),
164 float4(0, 0, 0, 1)));
165}
166
168 const IndexMask &curves_mask,
169 const float2 &offset,
170 const float rotation,
171 const float scale)
172{
173 /* Texture matrices are a combination of an unknown 3D transform into UV space, with a known 2D
174 * transform on top.
175 *
176 * However, the modifier offset is not applied directly to the UV transform, since it emulates
177 * legacy behavior of the GPv2 modifier, which applied translation first, before rotating about
178 * (0.5, 0.5) and scaling. To achieve the same result as the legacy modifier, the actual offset
179 * is calculated such that the result matches the GPv2 behavior.
180 *
181 * The canonical transform is
182 * uv = T + R / S * xy
183 *
184 * In terms of legacy variables TL, RL, SL the same transform is described as
185 * uv = (RL * (xy / 2 + TL) + 1/2) / SL
186 *
187 * where the 1/2 scaling factor and offset are the "bounds" transform and rotation center.
188 *
189 * Rearranging into canonical loc/rot/scale terms:
190 * uv = (RL * TL + 1/2) / SL + 1/2 * RL / SL * xy
191 * <=>
192 * T = (RL * TL + 1/2) / SL
193 * R = RL
194 * S = 2*SL
195 * <=>
196 * TL = 1/2 * R^T * (T * S - 1)
197 * RL = R
198 * SL = S/2
199 */
200
201 bke::CurvesGeometry &curves = drawing.strokes_for_write();
202 const Span<float3> positions = curves.positions();
203 Array<float4x2> texture_matrices(drawing.texture_matrices());
204
205 curves_mask.foreach_index(GrainSize(512), [&](int64_t curve_i) {
206 const IndexRange points = curves.points_by_curve()[curve_i];
207 float4x2 &texture_matrix = texture_matrices[curve_i];
208 /* Factor out the stroke-to-layer transform part used by GPv2.
209 * This may not be the same as the transform used by GPv3 for concave shapes due to a
210 * simplistic normal calculation, but we want to achieve the same effect as GPv2 so have to use
211 * the same matrix. */
212 float3x4 stroke_to_layer;
213 float4x3 layer_to_stroke;
214 get_legacy_stroke_matrix(positions.slice(points), stroke_to_layer, layer_to_stroke);
215 const float3x2 uv_matrix = texture_matrix * stroke_to_layer;
216 const float2 uv_translation = uv_matrix[2];
217 float2 inv_uv_scale;
218 const float2 axis_u = math::normalize_and_get_length(uv_matrix[0], inv_uv_scale[0]);
219 const float2 axis_v = math::normalize_and_get_length(uv_matrix[1], inv_uv_scale[1]);
220 UNUSED_VARS(axis_v); /* `inv_uv_scale[1]` is used. */
221 const float uv_rotation = math::atan2(axis_u[1], axis_u[0]);
222 const float2 uv_scale = math::safe_rcp(inv_uv_scale);
223
224 const float2 legacy_uv_translation = rotate_by_angle(0.5f * uv_scale * uv_translation - 0.5f,
225 -uv_rotation);
226 const float legacy_uv_rotation = uv_rotation;
227 const float2 legacy_uv_scale = 0.5f * uv_scale;
228
229 const float2 legacy_uv_translation_new = legacy_uv_translation + offset;
230 const float legacy_uv_rotation_new = legacy_uv_rotation + rotation;
231 const float2 legacy_uv_scale_new = legacy_uv_scale * scale;
232
233 const float2 uv_translation_new =
234 (rotate_by_angle(legacy_uv_translation_new, legacy_uv_rotation_new) + 0.5f) *
235 math::safe_rcp(legacy_uv_scale_new);
236 const float uv_rotation_new = legacy_uv_rotation_new;
237 const float2 uv_scale_new = 2.0f * legacy_uv_scale_new;
238
239 const float cos_uv_rotation_new = math::cos(uv_rotation_new);
240 const float sin_uv_rotation_new = math::sin(uv_rotation_new);
241 const float2 inv_uv_scale_new = math::safe_rcp(uv_scale_new);
242 const float3x2 uv_matrix_new = float3x2(
243 inv_uv_scale_new[0] * float2(cos_uv_rotation_new, sin_uv_rotation_new),
244 inv_uv_scale_new[1] * float2(-sin_uv_rotation_new, cos_uv_rotation_new),
245 uv_translation_new);
246 texture_matrix = uv_matrix_new * layer_to_stroke;
247 });
248
249 drawing.set_texture_matrices(texture_matrices, curves_mask);
250}
251
253 const ModifierEvalContext &ctx,
255{
256 IndexMaskMemory mask_memory;
258 ctx.object, drawing.strokes(), tmd.influence, mask_memory);
259
260 const bool normalize_u = (tmd.fit_method == MOD_GREASE_PENCIL_TEXTURE_FIT_STROKE);
264 drawing, curves_mask, tmd.uv_offset, tmd.alignment_rotation, tmd.uv_scale, normalize_u);
265 break;
268 drawing, curves_mask, tmd.fill_offset, tmd.fill_rotation, tmd.fill_scale);
269 break;
272 drawing, curves_mask, tmd.uv_offset, tmd.alignment_rotation, tmd.uv_scale, normalize_u);
274 drawing, curves_mask, tmd.fill_offset, tmd.fill_rotation, tmd.fill_scale);
275 break;
276 }
277}
278
280 const ModifierEvalContext *ctx,
281 bke::GeometrySet *geometry_set)
282{
285
286 const auto &tmd = *reinterpret_cast<const GreasePencilTextureModifierData *>(md);
287
288 if (!geometry_set->has_grease_pencil()) {
289 return;
290 }
291 GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
292
293 IndexMaskMemory mask_memory;
295 grease_pencil, tmd.influence, mask_memory);
296 const int frame = grease_pencil.runtime->eval_frame;
298 grease_pencil, layer_mask, frame);
300 [&](Drawing *drawing) { modify_curves(tmd, *ctx, *drawing); });
301}
302
303static void panel_draw(const bContext *C, Panel *panel)
304{
305 uiLayout *layout = panel->layout;
306
307 PointerRNA ob_ptr;
309 const auto &tmd = *static_cast<GreasePencilTextureModifierData *>(ptr->data);
310 const auto mode = GreasePencilTextureModifierMode(tmd.mode);
311 uiLayout *col;
312
313 layout->use_property_split_set(true);
314
315 layout->prop(ptr, "mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
316
318 col = &layout->column(false);
319 col->prop(ptr, "fit_method", UI_ITEM_NONE, IFACE_("Stroke Fit Method"), ICON_NONE);
320 col->prop(ptr, "uv_offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
321 col->prop(ptr, "alignment_rotation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
322 col->prop(ptr, "uv_scale", UI_ITEM_NONE, IFACE_("Scale"), ICON_NONE);
323 }
324
326 layout->separator();
327 }
328
330 col = &layout->column(false);
331 col->prop(ptr, "fill_rotation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
332 col->prop(ptr, "fill_offset", UI_ITEM_NONE, IFACE_("Offset"), ICON_NONE);
333 col->prop(ptr, "fill_scale", UI_ITEM_NONE, IFACE_("Scale"), ICON_NONE);
334 }
335
336 if (uiLayout *influence_panel = layout->panel_prop(
337 C, ptr, "open_influence_panel", IFACE_("Influence")))
338 {
341 }
342
344}
345
350
351static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
352{
353 const auto *tmd = reinterpret_cast<const GreasePencilTextureModifierData *>(md);
354
356 modifier::greasepencil::write_influence_data(writer, &tmd->influence);
357}
358
359static void blend_read(BlendDataReader *reader, ModifierData *md)
360{
361 auto *tmd = reinterpret_cast<GreasePencilTextureModifierData *>(md);
362
363 modifier::greasepencil::read_influence_data(reader, &tmd->influence);
364}
365
366} // namespace blender
367
369 /*idname*/ "GreasePencilTexture",
370 /*name*/ N_("TextureMapping"),
371 /*struct_name*/ "GreasePencilTextureModifierData",
372 /*struct_size*/ sizeof(GreasePencilTextureModifierData),
373 /*srna*/ &RNA_GreasePencilTextureModifier,
377 /*icon*/ ICON_MOD_UVPROJECT,
378
379 /*copy_data*/ blender::copy_data,
380
381 /*deform_verts*/ nullptr,
382 /*deform_matrices*/ nullptr,
383 /*deform_verts_EM*/ nullptr,
384 /*deform_matrices_EM*/ nullptr,
385 /*modify_mesh*/ nullptr,
386 /*modify_geometry_set*/ blender::modify_geometry_set,
387
388 /*init_data*/ blender::init_data,
389 /*required_data_mask*/ nullptr,
390 /*free_data*/ blender::free_data,
391 /*is_disabled*/ nullptr,
392 /*update_depsgraph*/ nullptr,
393 /*depends_on_time*/ nullptr,
394 /*depends_on_normals*/ nullptr,
395 /*foreach_ID_link*/ blender::foreach_ID_link,
396 /*foreach_tex_link*/ nullptr,
397 /*free_runtime_data*/ nullptr,
398 /*panel_register*/ blender::panel_register,
399 /*blend_write*/ blender::blend_write,
400 /*blend_read*/ blender::blend_read,
401};
Low-level operations for curves.
Low-level operations for grease pencil.
void(*)(void *user_data, Object *ob, ID **idpoin, LibraryForeachIDCallbackFlag cb_flag) IDWalkFunc
void BKE_modifier_copydata_generic(const ModifierData *md, ModifierData *md_dst, int flag)
@ eModifierTypeFlag_SupportsMapping
@ eModifierTypeFlag_AcceptsGreasePencil
@ eModifierTypeFlag_EnableInEditmode
@ eModifierTypeFlag_SupportsEditmode
#define BLI_assert(a)
Definition BLI_assert.h:46
#define UNUSED_VARS(...)
#define ELEM(...)
#define MEMCMP_STRUCT_AFTER_IS_ZERO(struct_var, member)
#define MEMCPY_STRUCT_AFTER(struct_dst, struct_src, member)
#define BLO_write_struct(writer, struct_name, data_ptr)
#define IFACE_(msgid)
#define DNA_struct_default_get(struct_name)
@ eModifierType_GreasePencilTexture
GreasePencilTextureModifierMode
@ MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL
@ MOD_GREASE_PENCIL_TEXTURE_FILL
@ MOD_GREASE_PENCIL_TEXTURE_STROKE
@ MOD_GREASE_PENCIL_TEXTURE_FIT_STROKE
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
ModifierTypeInfo modifierType_GreasePencilTexture
PanelType * modifier_panel_register(ARegionType *region_type, ModifierType type, PanelDrawFn draw)
PointerRNA * modifier_panel_get_property_pointers(Panel *panel, PointerRNA *r_ob_ptr)
void modifier_error_message_draw(uiLayout *layout, PointerRNA *ptr)
#define C
Definition RandGen.cpp:29
#define UI_ITEM_NONE
long long int int64_t
SIMD_FORCE_INLINE btScalar norm() const
Return the norm (length) of the vector.
Definition btVector3.h:263
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
static VArray from_single(T value, const int64_t size)
OffsetIndices< int > points_by_curve() const
MutableAttributeAccessor attributes_for_write()
Span< float > evaluated_lengths_for_curve(int curve_index, bool cyclic) const
Span< float3 > positions() const
VArray< bool > cyclic() const
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, AttrType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
Span< float4x2 > texture_matrices() const
bke::CurvesGeometry & strokes_for_write()
const bke::CurvesGeometry & strokes() const
void set_texture_matrices(Span< float4x2 > matrices, const IndexMask &selection)
void foreach_index(Fn &&fn) const
dot(value.rgb, luminance_coefficients)") DEFINE_VALUE("REDUCE(lhs
static bool is_cyclic(const Nurb *nu)
uint col
VecBase< float, D > normalize(VecOp< float, D >) RET
VecBase< float, 3 > cross(VecOp< float, 3 >, VecOp< float, 3 >) RET
T cos(const AngleRadianBase< T > &a)
MatBase< T, NumCol, NumRow > transpose(const MatBase< T, NumRow, NumCol > &mat)
T safe_rcp(const T &a)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
QuaternionBase< T > normalize_and_get_length(const QuaternionBase< T > &q, T &out_length)
T atan2(const T &y, const T &x)
T sin(const AngleRadianBase< T > &a)
void read_influence_data(BlendDataReader *reader, GreasePencilModifierInfluenceData *influence_data)
void init_influence_data(GreasePencilModifierInfluenceData *influence_data, const bool has_custom_curve)
static IndexMask get_filtered_stroke_mask(const Object *ob, const bke::CurvesGeometry &curves, const Material *material_filter, const std::optional< int > material_pass_filter, const bool material_filter_invert, const bool material_pass_filter_invert, IndexMaskMemory &memory)
void write_influence_data(BlendWriter *writer, const GreasePencilModifierInfluenceData *influence_data)
static IndexMask get_filtered_layer_mask(const GreasePencil &grease_pencil, const std::optional< StringRef > tree_node_name_filter, const std::optional< int > layer_pass_filter, const bool layer_filter_invert, const bool layer_pass_filter_invert, IndexMaskMemory &memory)
Vector< bke::greasepencil::Drawing * > get_drawings_for_write(GreasePencil &grease_pencil, const IndexMask &layer_mask, const int frame)
void draw_material_filter_settings(const bContext *, uiLayout *layout, PointerRNA *ptr)
void draw_layer_filter_settings(const bContext *, uiLayout *layout, PointerRNA *ptr)
void free_influence_data(GreasePencilModifierInfluenceData *influence_data)
void foreach_influence_ID_link(GreasePencilModifierInfluenceData *influence_data, Object *ob, IDWalkFunc walk, void *user_data)
void copy_influence_data(const GreasePencilModifierInfluenceData *influence_data_src, GreasePencilModifierInfluenceData *influence_data_dst, const int)
void parallel_for_each(Range &&range, const Function &function)
Definition BLI_task.hh:56
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
static void blend_write(BlendWriter *writer, const ID *, const ModifierData *md)
static float2 rotate_by_angle(const float2 &p, const float angle)
static void modify_curves(ModifierData &md, const ModifierEvalContext &ctx, Drawing &drawing, bke::GreasePencilDrawingEditHints *edit_hints)
static void init_data(ModifierData *md)
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
static void panel_draw(const bContext *C, Panel *panel)
static void write_fill_transforms(bke::greasepencil::Drawing &drawing, const IndexMask &curves_mask, const float2 &offset, const float rotation, const float scale)
MatBase< float, 3, 4 > float3x4
VecBase< float, 4 > float4
static void modify_geometry_set(ModifierData *md, const ModifierEvalContext *ctx, bke::GeometrySet *geometry_set)
VecBase< float, 2 > float2
static void free_data(ModifierData *md)
MatBase< float, 4, 2 > float4x2
static void panel_register(ARegionType *region_type)
MatBase< float, 3, 2 > float3x2
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)
MatBase< float, 4, 3 > float4x3
static void get_legacy_stroke_matrix(const Span< float3 > positions, float3x4 &stroke_to_layer, float4x3 &layer_to_stroke)
VecBase< float, 3 > float3
static void blend_read(BlendDataReader *reader, ModifierData *md)
GreasePencilModifierInfluenceData influence
GreasePencilRuntimeHandle * runtime
Definition DNA_ID.h:414
struct uiLayout * layout
GreasePencil * get_grease_pencil_for_write()
PanelLayout panel_prop(const bContext *C, PointerRNA *open_prop_owner, blender::StringRefNull open_prop_name)
uiLayout & column(bool align)
void separator(float factor=1.0f, LayoutSeparatorType type=LayoutSeparatorType::Auto)
void use_property_split_set(bool value)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
#define N_(msgid)
PointerRNA * ptr
Definition wm_files.cc:4238
uint8_t flag
Definition wm_window.cc:145