diff --git a/scripts/startup/bl_ui/properties_data_modifier.py b/scripts/startup/bl_ui/properties_data_modifier.py index e234a0ee41e..452a3688fd8 100644 --- a/scripts/startup/bl_ui/properties_data_modifier.py +++ b/scripts/startup/bl_ui/properties_data_modifier.py @@ -155,6 +155,7 @@ class OBJECT_MT_modifier_add_generate(ModifierAddMenu, Menu): self.operator_modifier_add(layout, 'WIREFRAME') if ob_type == 'GREASEPENCIL': self.operator_modifier_add(layout, 'GREASE_PENCIL_ARRAY') + self.operator_modifier_add(layout, 'GREASE_PENCIL_BUILD') self.operator_modifier_add(layout, 'GREASE_PENCIL_DASH') self.operator_modifier_add(layout, 'GREASE_PENCIL_ENVELOPE') self.operator_modifier_add(layout, 'GREASE_PENCIL_LENGTH') diff --git a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc index f27ad0b873f..718e6581548 100644 --- a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc +++ b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc @@ -1737,6 +1737,97 @@ static void legacy_object_modifier_weight_lineart(Object &object, GpencilModifie greasepencil::convert::lineart_wrap_v3(&legacy_md_lineart, &md_lineart); } +static void legacy_object_modifier_build(Object &object, GpencilModifierData &legacy_md) +{ + ModifierData &md = legacy_object_modifier_common( + object, eModifierType_GreasePencilBuild, legacy_md); + auto &md_build = reinterpret_cast(md); + auto &legacy_md_build = reinterpret_cast(legacy_md); + + md_build.flag = 0; + if (legacy_md_build.flag & GP_BUILD_RESTRICT_TIME) { + md_build.flag |= MOD_GREASE_PENCIL_BUILD_RESTRICT_TIME; + } + if (legacy_md_build.flag & GP_BUILD_USE_FADING) { + md_build.flag |= MOD_GREASE_PENCIL_BUILD_USE_FADING; + } + + switch (legacy_md_build.mode) { + case GP_BUILD_MODE_ADDITIVE: + md_build.mode = MOD_GREASE_PENCIL_BUILD_MODE_ADDITIVE; + break; + case GP_BUILD_MODE_CONCURRENT: + md_build.mode = MOD_GREASE_PENCIL_BUILD_MODE_CONCURRENT; + break; + case GP_BUILD_MODE_SEQUENTIAL: + default: + md_build.mode = MOD_GREASE_PENCIL_BUILD_MODE_SEQUENTIAL; + break; + } + + switch (legacy_md_build.time_alignment) { + default: + case GP_BUILD_TIMEALIGN_START: + md_build.mode = MOD_GREASE_PENCIL_BUILD_TIMEALIGN_START; + break; + case GP_BUILD_TIMEALIGN_END: + md_build.mode = MOD_GREASE_PENCIL_BUILD_TIMEALIGN_END; + break; + } + + switch (legacy_md_build.time_mode) { + default: + case GP_BUILD_TIMEMODE_FRAMES: + md_build.mode = MOD_GREASE_PENCIL_BUILD_TIMEMODE_FRAMES; + break; + case GP_BUILD_TIMEMODE_PERCENTAGE: + md_build.mode = MOD_GREASE_PENCIL_BUILD_TIMEMODE_PERCENTAGE; + break; + case GP_BUILD_TIMEMODE_DRAWSPEED: + md_build.mode = MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED; + break; + } + + switch (legacy_md_build.transition) { + default: + case GP_BUILD_TRANSITION_GROW: + md_build.mode = MOD_GREASE_PENCIL_BUILD_TRANSITION_GROW; + break; + case GP_BUILD_TRANSITION_SHRINK: + md_build.mode = MOD_GREASE_PENCIL_BUILD_TRANSITION_SHRINK; + break; + case GP_BUILD_TRANSITION_VANISH: + md_build.mode = MOD_GREASE_PENCIL_BUILD_TRANSITION_VANISH; + break; + } + + md_build.start_frame = legacy_md_build.start_frame; + md_build.end_frame = legacy_md_build.end_frame; + md_build.start_delay = legacy_md_build.start_delay; + md_build.length = legacy_md_build.length; + md_build.fade_fac = legacy_md_build.fade_fac; + md_build.fade_opacity_strength = legacy_md_build.fade_opacity_strength; + md_build.fade_thickness_strength = legacy_md_build.fade_thickness_strength; + md_build.percentage_fac = legacy_md_build.percentage_fac; + md_build.speed_fac = legacy_md_build.speed_fac; + md_build.speed_maxgap = legacy_md_build.speed_maxgap; + STRNCPY(md_build.target_vgname, legacy_md_build.target_vgname); + + legacy_object_modifier_influence(md_build.influence, + legacy_md_build.layername, + legacy_md_build.layer_pass, + legacy_md_build.flag & GP_WEIGHT_INVERT_LAYER, + legacy_md_build.flag & GP_WEIGHT_INVERT_LAYERPASS, + &legacy_md_build.material, + legacy_md_build.pass_index, + legacy_md_build.flag & GP_WEIGHT_INVERT_MATERIAL, + legacy_md_build.flag & GP_WEIGHT_INVERT_PASS, + legacy_md_build.target_vgname, + legacy_md_build.flag & GP_WEIGHT_INVERT_VGROUP, + nullptr, + false); +} + static void legacy_object_modifiers(Main & /*bmain*/, Object &object) { BLI_assert(BLI_listbase_is_empty(&object.modifiers)); @@ -1818,6 +1909,8 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object) legacy_object_modifier_weight_lineart(object, *gpd_md); break; case eGpencilModifierType_Build: + legacy_object_modifier_build(object, *gpd_md); + break; case eGpencilModifierType_Simplify: case eGpencilModifierType_Texture: break; diff --git a/source/blender/makesdna/DNA_modifier_defaults.h b/source/blender/makesdna/DNA_modifier_defaults.h index 98e0b2c5c2c..7678d9e2700 100644 --- a/source/blender/makesdna/DNA_modifier_defaults.h +++ b/source/blender/makesdna/DNA_modifier_defaults.h @@ -1019,7 +1019,7 @@ .strength = 1.0f, \ .skip = 0, \ } - + #define _DNA_DEFAULT_GreasePencilOutlineModifierData \ { \ .flag = MOD_GREASE_PENCIL_OUTLINE_KEEP_SHAPE, \ @@ -1044,4 +1044,22 @@ .smooth_step = 1, \ } +/* Here we deliberately set effective range to the half the default + * frame-range to have an immediate effect to suggest use-cases. */ +#define _DNA_DEFAULT_GreasePencilBuildModifierData \ + { \ + .start_frame = 1, \ + .end_frame = 125, \ + .start_delay = 0.0f, \ + .length = 100.0f, \ + .flag = 0, \ + .mode = 0, \ + .transition = 0, \ + .time_alignment = 0, \ + .time_mode = 0, \ + .speed_fac = 1.2f, \ + .speed_maxgap = 0.5f, \ + .percentage_fac = 0.0f, \ + } + /* clang-format off */ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index f64c40a2550..6bcad6a780a 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -118,6 +118,7 @@ typedef enum ModifierType { eModifierType_GreasePencilEnvelope = 81, eModifierType_GreasePencilOutline = 82, eModifierType_GreasePencilShrinkwrap = 83, + eModifierType_GreasePencilBuild = 84, NUM_MODIFIER_TYPES, } ModifierType; @@ -3310,3 +3311,96 @@ typedef struct GreasePencilShrinkwrapModifierData { /** Runtime only. */ struct ShrinkwrapTreeData *cache_data; } GreasePencilShrinkwrapModifierData; + +typedef struct GreasePencilBuildModifierData { + ModifierData modifier; + GreasePencilModifierInfluenceData influence; + /** + * If GP_BUILD_RESTRICT_TIME is set, + * the defines the frame range where GP frames are considered. + */ + float start_frame; + float end_frame; + + /** Start time added on top of the drawing frame number */ + float start_delay; + float length; + + /** #GreasePencilBuildFlag. */ + short flag; + + /** #GreasePencilBuildMode. */ + short mode; + /** #GreasePencilBuildTransition. */ + short transition; + + /** + * #GreasePencilBuildTimeAlignment. + * For the "Concurrent" mode, when should "shorter" strips start/end. + */ + short time_alignment; + + /** Speed factor for #GP_BUILD_TIMEMODE_DRAWSPEED. */ + float speed_fac; + /** Maximum time gap between strokes for #GP_BUILD_TIMEMODE_DRAWSPEED. */ + float speed_maxgap; + /** GreasePencilBuildTimeMode. */ + short time_mode; + char _pad[6]; + + /** Build origin control object. */ + struct Object *object; + + /** Factor of the stroke (used instead of frame evaluation). */ + float percentage_fac; + + /** Weight fading at the end of the stroke. */ + float fade_fac; + /** Target vertex-group name, #MAX_VGROUP_NAME. */ + char target_vgname[64]; + /** Fading strength of opacity and thickness */ + float fade_opacity_strength; + float fade_thickness_strength; +} GreasePencilBuildModifierData; + +typedef enum GreasePencilBuildMode { + /* Strokes are shown one by one until all have appeared */ + MOD_GREASE_PENCIL_BUILD_MODE_SEQUENTIAL = 0, + /* All strokes start at the same time */ + MOD_GREASE_PENCIL_BUILD_MODE_CONCURRENT = 1, + /* Only the new strokes are built */ + MOD_GREASE_PENCIL_BUILD_MODE_ADDITIVE = 2, +} GreasePencilBuildMode; + +typedef enum GreasePencilBuildTransition { + /* Show in forward order */ + MOD_GREASE_PENCIL_BUILD_TRANSITION_GROW = 0, + /* Hide in reverse order */ + MOD_GREASE_PENCIL_BUILD_TRANSITION_SHRINK = 1, + /* Hide in forward order */ + MOD_GREASE_PENCIL_BUILD_TRANSITION_VANISH = 2, +} GreasePencilBuildTransition; + +typedef enum GreasePencilBuildTimeAlignment { + /* All strokes start at same time */ + MOD_GREASE_PENCIL_BUILD_TIMEALIGN_START = 0, + /* All strokes end at same time */ + MOD_GREASE_PENCIL_BUILD_TIMEALIGN_END = 1, + + /* TODO: Random Offsets, Stretch-to-Fill */ +} GreasePencilBuildTimeAlignment; + +typedef enum GreasePencilBuildTimeMode { + /** Use a number of frames build. */ + MOD_GREASE_PENCIL_BUILD_TIMEMODE_FRAMES = 0, + /** Use manual percentage to build. */ + MOD_GREASE_PENCIL_BUILD_TIMEMODE_PERCENTAGE = 1, + /** Use factor of recorded speed to build. */ + MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED = 2, +} GreasePencilBuildTimeMode; + +typedef enum GreasePencilBuildFlag { + /* Restrict modifier to only operating between the nominated frames */ + MOD_GREASE_PENCIL_BUILD_RESTRICT_TIME = (1 << 0), + MOD_GREASE_PENCIL_BUILD_USE_FADING = (1 << 14), +} GreasePencilBuildFlag; diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index f5a65bd5c63..a341781ff4c 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -305,6 +305,7 @@ SDNA_DEFAULT_DECL_STRUCT(GreasePencilSubdivModifierData); SDNA_DEFAULT_DECL_STRUCT(GreasePencilNoiseModifierData); SDNA_DEFAULT_DECL_STRUCT(GreasePencilLengthModifierData); SDNA_DEFAULT_DECL_STRUCT(GreasePencilLineartModifierData); +SDNA_DEFAULT_DECL_STRUCT(GreasePencilBuildModifierData); /* Grease Pencil 3.0 modifiers. */ SDNA_DEFAULT_DECL_STRUCT(GreasePencilSmoothModifierData); @@ -581,6 +582,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { SDNA_DEFAULT_DECL(GreasePencilLengthModifierData), SDNA_DEFAULT_DECL(GreasePencilHookModifierData), SDNA_DEFAULT_DECL(GreasePencilLineartModifierData), + SDNA_DEFAULT_DECL(GreasePencilBuildModifierData), /* Grease Pencil 3.0 defaults. */ SDNA_DEFAULT_DECL(GreasePencilSmoothModifierData), diff --git a/source/blender/makesrna/intern/rna_modifier.cc b/source/blender/makesrna/intern/rna_modifier.cc index cad26a073e8..862ede4a6d4 100644 --- a/source/blender/makesrna/intern/rna_modifier.cc +++ b/source/blender/makesrna/intern/rna_modifier.cc @@ -234,6 +234,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = { ICON_MOD_ARRAY, "Array strokes", "Duplicate strokes into an array"}, + {eModifierType_GreasePencilBuild, + "GREASE_PENCIL_BUILD", + ICON_MOD_BUILD, + "Build", + "Grease Pencil build modifier"}, {eModifierType_GreasePencilLength, "GREASE_PENCIL_LENGTH", ICON_MOD_LENGTH, @@ -784,6 +789,25 @@ const EnumPropertyItem rna_enum_subdivision_boundary_smooth_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +static const EnumPropertyItem grease_pencil_build_time_mode_items[] = { + {MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED, + "DRAWSPEED", + 0, + "Natural Drawing Speed", + "Use recorded speed multiplied by a factor"}, + {MOD_GREASE_PENCIL_BUILD_TIMEMODE_FRAMES, + "FRAMES", + 0, + "Number of Frames", + "Set a fixed number of frames for all build animations"}, + {MOD_GREASE_PENCIL_BUILD_TIMEMODE_PERCENTAGE, + "PERCENTAGE", + 0, + "Percentage Factor", + "Set a manual percentage to build"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + #ifdef RNA_RUNTIME # include @@ -944,6 +968,7 @@ RNA_MOD_VGROUP_NAME_SET(Wireframe, defgrp_name); RNA_MOD_VGROUP_NAME_SET(GreasePencilWeightAngle, target_vgname); RNA_MOD_VGROUP_NAME_SET(GreasePencilWeightProximity, target_vgname); RNA_MOD_VGROUP_NAME_SET(GreasePencilLineart, vgname); +RNA_MOD_VGROUP_NAME_SET(GreasePencilBuild, target_vgname); static void rna_ExplodeModifier_vgroup_get(PointerRNA *ptr, char *value) { @@ -1029,6 +1054,7 @@ RNA_MOD_OBJECT_SET(GreasePencilArmature, object, OB_ARMATURE); RNA_MOD_OBJECT_SET(GreasePencilOutline, object, OB_EMPTY); RNA_MOD_OBJECT_SET(GreasePencilShrinkwrap, target, OB_MESH); RNA_MOD_OBJECT_SET(GreasePencilShrinkwrap, aux_target, OB_MESH); +RNA_MOD_OBJECT_SET(GreasePencilBuild, object, OB_EMPTY); static void rna_HookModifier_object_set(PointerRNA *ptr, PointerRNA value, @@ -1969,6 +1995,7 @@ RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilHook); 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_VERTEX_GROUP_SET(GreasePencilOffset); RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOpacity); @@ -2089,6 +2116,35 @@ static void rna_GreasePencilDashModifier_segments_begin(CollectionPropertyIterat nullptr); } +const EnumPropertyItem *grease_pencil_build_time_mode_filter(bContext * /*C*/, + PointerRNA *ptr, + PropertyRNA * /*prop*/, + bool *r_free) +{ + + auto *md = static_cast(ptr->data); + auto *mmd = reinterpret_cast(md); + const bool is_concurrent = (mmd->mode == MOD_GREASE_PENCIL_BUILD_MODE_CONCURRENT); + + EnumPropertyItem *item_list = nullptr; + int totitem = 0; + + for (const EnumPropertyItem *item = grease_pencil_build_time_mode_items; + item->identifier != nullptr; + item++) + { + if (is_concurrent && (item->value == MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED)) { + continue; + } + RNA_enum_item_add(&item_list, &totitem, item); + } + + RNA_enum_item_end(&item_list, &totitem); + *r_free = true; + + return item_list; +} + static const GreasePencilTimeModifierData *find_grease_pencil_time_modifier_of_segment( const Object &ob, const GreasePencilTimeModifierSegment &time_segment) { @@ -10444,6 +10500,217 @@ static void rna_def_modifier_grease_pencil_shrinkwrap(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_modifier_grease_pencil_build(BlenderRNA *brna) +{ + static EnumPropertyItem prop_gpencil_build_mode_items[] = { + {MOD_GREASE_PENCIL_BUILD_MODE_SEQUENTIAL, + "SEQUENTIAL", + 0, + "Sequential", + "Strokes appear/disappear one after the other, but only a single one changes at a time"}, + {MOD_GREASE_PENCIL_BUILD_MODE_CONCURRENT, + "CONCURRENT", + 0, + "Concurrent", + "Multiple strokes appear/disappear at once"}, + {MOD_GREASE_PENCIL_BUILD_MODE_ADDITIVE, + "ADDITIVE", + 0, + "Additive", + "Builds only new strokes (assuming 'additive' drawing)"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + static EnumPropertyItem prop_gpencil_build_transition_items[] = { + {MOD_GREASE_PENCIL_BUILD_TRANSITION_GROW, + "GROW", + 0, + "Grow", + "Show points in the order they occur in each stroke " + "(e.g. for animating lines being drawn)"}, + {MOD_GREASE_PENCIL_BUILD_TRANSITION_SHRINK, + "SHRINK", + 0, + "Shrink", + "Hide points from the end of each stroke to the start " + "(e.g. for animating lines being erased)"}, + {MOD_GREASE_PENCIL_BUILD_TRANSITION_VANISH, + "FADE", /* "Fade" is the original id string kept for compatibility purpose. */ + 0, + "Vanish", + "Hide points in the order they occur in each stroke " + "(e.g. for animating ink fading or vanishing after getting drawn)"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + static EnumPropertyItem prop_gpencil_build_time_align_items[] = { + {MOD_GREASE_PENCIL_BUILD_TIMEALIGN_START, + "START", + 0, + "Align Start", + "All strokes start at same time (i.e. short strokes finish earlier)"}, + {MOD_GREASE_PENCIL_BUILD_TIMEALIGN_END, + "END", + 0, + "Align End", + "All strokes end at same time (i.e. short strokes start later)"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "GreasePencilBuildModifier", "Modifier"); + RNA_def_struct_ui_text(srna, "Build Modifier", "Animate strokes appearing and disappearing"); + RNA_def_struct_sdna(srna, "GreasePencilBuildModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MOD_BUILD); + + rna_def_modifier_grease_pencil_layer_filter(srna); + rna_def_modifier_grease_pencil_material_filter( + srna, "rna_GreasePencilBuildModifier_material_filter_set"); + + rna_def_modifier_panel_open_prop(srna, "open_influence_panel", 0); + rna_def_modifier_panel_open_prop(srna, "open_frame_range_panel", 1); + rna_def_modifier_panel_open_prop(srna, "open_fading_panel", 2); + + RNA_define_lib_overridable(true); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, prop_gpencil_build_mode_items); + RNA_def_property_ui_text(prop, "Mode", "How strokes are being built"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "transition", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, prop_gpencil_build_transition_items); + RNA_def_property_ui_text( + prop, "Transition", "How are strokes animated (i.e. are they appearing or disappearing)"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "start_delay", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "start_delay"); + RNA_def_property_ui_text( + prop, "Delay", "Number of frames after each GP keyframe before the modifier has any effect"); + RNA_def_property_range(prop, 0, MAXFRAMEF); + RNA_def_property_ui_range(prop, 0, 200, 1, -1); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "length", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "length"); + RNA_def_property_ui_text(prop, + "Length", + "Maximum number of frames that the build effect can run for " + "(unless another GP keyframe occurs before this time has elapsed)"); + RNA_def_property_range(prop, 1, MAXFRAMEF); + RNA_def_property_ui_range(prop, 1, 1000, 1, -1); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "concurrent_time_alignment", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "time_alignment"); + RNA_def_property_enum_items(prop, prop_gpencil_build_time_align_items); + RNA_def_property_ui_text(prop, "Time Alignment", "How should strokes start to appear/disappear"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "time_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "time_mode"); + RNA_def_property_enum_items(prop, grease_pencil_build_time_mode_items); + RNA_def_property_enum_funcs(prop, nullptr, nullptr, "grease_pencil_build_time_mode_filter"); + RNA_def_property_ui_text( + prop, + "Timing", + "Use drawing speed, a number of frames, or a manual factor to build strokes"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + /* Speed factor for MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED. */ + /* Todo: Does it work? */ + prop = RNA_def_property(srna, "speed_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "speed_fac"); + RNA_def_property_ui_text(prop, "Speed Factor", "Multiply recorded drawing speed by a factor"); + RNA_def_property_range(prop, 0.0f, 100.0f); + RNA_def_property_ui_range(prop, 0, 5, 0.001, -1); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + /* Max gap in seconds between strokes for MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED. */ + prop = RNA_def_property(srna, "speed_maxgap", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "speed_maxgap"); + RNA_def_property_ui_text(prop, "Maximum Gap", "The maximum gap between strokes in seconds"); + RNA_def_property_range(prop, 0.0f, 100.0f); + RNA_def_property_ui_range(prop, 0, 4, 0.01, -1); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "use_restrict_frame_range", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOD_GREASE_PENCIL_BUILD_RESTRICT_TIME); + RNA_def_property_ui_text( + prop, "Restrict Frame Range", "Only modify strokes during the specified frame range"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + /* Use percentage bool (used by sequential & concurrent modes) */ + prop = RNA_def_property(srna, "use_percentage", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "time_mode", MOD_GREASE_PENCIL_BUILD_TIMEMODE_PERCENTAGE); + RNA_def_property_ui_text( + prop, "Restrict Visible Points", "Use a percentage factor to determine the visible points"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + prop = RNA_def_property(srna, "percentage_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "percentage_fac"); + RNA_def_property_ui_text(prop, "Factor", "Defines how much of the stroke is visible"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "frame_start", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "start_frame"); + RNA_def_property_ui_text( + prop, "Start Frame", "Start Frame (when Restrict Frame Range is enabled)"); + RNA_def_property_range(prop, MINAFRAMEF, MAXFRAMEF); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "frame_end", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "end_frame"); + RNA_def_property_ui_text(prop, "End Frame", "End Frame (when Restrict Frame Range is enabled)"); + RNA_def_property_range(prop, MINAFRAMEF, MAXFRAMEF); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "use_fading", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOD_GREASE_PENCIL_BUILD_USE_FADING); + RNA_def_property_ui_text(prop, "Use Fading", "Fade out strokes instead of directly cutting off"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "fade_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "fade_fac"); + RNA_def_property_ui_text(prop, "Fade Factor", "Defines how much of the stroke is fading in/out"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "target_vertex_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, nullptr, "target_vgname"); + RNA_def_property_ui_text(prop, "Vertex Group", "Output Vertex group"); + RNA_def_property_string_funcs( + prop, nullptr, nullptr, "rna_GreasePencilBuildModifier_target_vgname_set"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "fade_opacity_strength", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "fade_opacity_strength"); + RNA_def_property_ui_text( + prop, "Opacity Strength", "How much strength fading applies on top of stroke opacity"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "fade_thickness_strength", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, nullptr, "fade_thickness_strength"); + RNA_def_property_ui_text( + prop, "Thickness Strength", "How much strength fading applies on top of stroke thickness"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE); + RNA_def_property_ui_text(prop, "Object", "Object used as build starting position"); + RNA_def_property_pointer_funcs( + prop, nullptr, "rna_GreasePencilBuildModifier_object_set", nullptr, nullptr); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update"); + + RNA_define_lib_overridable(false); +} + void RNA_def_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -10636,6 +10903,7 @@ void RNA_def_modifier(BlenderRNA *brna) rna_def_modifier_grease_pencil_envelope(brna); rna_def_modifier_grease_pencil_outline(brna); rna_def_modifier_grease_pencil_shrinkwrap(brna); + rna_def_modifier_grease_pencil_build(brna); } #endif diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index e0ac4cfaafd..3d7e7b903fa 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -51,6 +51,7 @@ set(SRC intern/MOD_fluid.cc intern/MOD_grease_pencil_armature.cc intern/MOD_grease_pencil_array.cc + intern/MOD_grease_pencil_build.cc intern/MOD_grease_pencil_color.cc intern/MOD_grease_pencil_dash.cc intern/MOD_grease_pencil_envelope.cc diff --git a/source/blender/modifiers/MOD_modifiertypes.hh b/source/blender/modifiers/MOD_modifiertypes.hh index 4bd949ba47b..a1ee8c8fdd2 100644 --- a/source/blender/modifiers/MOD_modifiertypes.hh +++ b/source/blender/modifiers/MOD_modifiertypes.hh @@ -96,6 +96,7 @@ extern ModifierTypeInfo modifierType_GreasePencilTime; extern ModifierTypeInfo modifierType_GreasePencilEnvelope; extern ModifierTypeInfo modifierType_GreasePencilOutline; extern ModifierTypeInfo modifierType_GreasePencilShrinkwrap; +extern ModifierTypeInfo modifierType_GreasePencilBuild; /* MOD_util.cc */ diff --git a/source/blender/modifiers/intern/MOD_grease_pencil_build.cc b/source/blender/modifiers/intern/MOD_grease_pencil_build.cc new file mode 100644 index 00000000000..920a438db82 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_grease_pencil_build.cc @@ -0,0 +1,801 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup modifiers + */ + +#include "BLI_array.hh" +#include "BLI_hash.h" +#include "BLI_rand.h" +#include "BLI_sort.hh" +#include "BLI_task.h" + +#include "BLT_translation.hh" + +#include "BLO_read_write.hh" + +#include "DNA_defaults.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "DEG_depsgraph_query.hh" + +#include "BKE_curves.hh" +#include "BKE_geometry_set.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_lib_query.hh" +#include "BKE_modifier.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "MOD_grease_pencil_util.hh" +#include "MOD_modifiertypes.hh" +#include "MOD_ui_common.hh" + +#include "RNA_access.hh" +#include "RNA_prototypes.h" + +#include "GEO_reorder.hh" + +namespace blender { + +static void init_data(ModifierData *md) +{ + auto *gpmd = reinterpret_cast(md); + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier)); + + MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(GreasePencilBuildModifierData), modifier); + modifier::greasepencil::init_influence_data(&gpmd->influence, false); +} + +static void copy_data(const ModifierData *md, ModifierData *target, int flags) +{ + const auto *omd = reinterpret_cast(md); + auto *tomd = reinterpret_cast(target); + + modifier::greasepencil::free_influence_data(&tomd->influence); + + BKE_modifier_copydata_generic(md, target, flags); + modifier::greasepencil::copy_influence_data(&omd->influence, &tomd->influence, flags); +} + +static void free_data(ModifierData *md) +{ + auto *omd = reinterpret_cast(md); + modifier::greasepencil::free_influence_data(&omd->influence); +} + +static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data) +{ + auto *omd = reinterpret_cast(md); + modifier::greasepencil::foreach_influence_ID_link(&omd->influence, ob, walk, user_data); +} + +static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +{ + auto *mmd = reinterpret_cast(md); + if (mmd->object != nullptr) { + DEG_add_object_relation(ctx->node, mmd->object, DEG_OB_COMP_TRANSFORM, "Build Modifier"); + } + DEG_add_object_relation(ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Build Modifier"); +} + +static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md) +{ + const auto *mmd = reinterpret_cast(md); + + BLO_write_struct(writer, GreasePencilBuildModifierData, mmd); + modifier::greasepencil::write_influence_data(writer, &mmd->influence); +} + +static void blend_read(BlendDataReader *reader, ModifierData *md) +{ + auto *mmd = reinterpret_cast(md); + + modifier::greasepencil::read_influence_data(reader, &mmd->influence); +} + +static Array point_counts_to_keep_concurrent(const bke::CurvesGeometry &curves, + const IndexMask &selection, + const int time_alignment, + const int transition, + const float factor, + const bool clamp_points, + int &r_curves_num, + int &r_points_num) +{ + const int stroke_count = curves.curves_num(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + + curves.ensure_evaluated_lengths(); + float max_length = 0; + for (const int stroke : curves.curves_range()) { + const float len = curves.evaluated_length_total_for_curve(stroke, false); + max_length = math::max(max_length, len); + } + + float factor_to_keep = transition == MOD_GREASE_PENCIL_BUILD_TRANSITION_GROW ? factor : + 1.0f - factor; + if (clamp_points) { + r_curves_num = r_points_num = 0; + factor_to_keep = std::clamp(factor_to_keep, 0.0f, 1.0f); + } + + auto get_stroke_factor = [&](const float factor, const int index) { + const float max_factor = max_length / curves.evaluated_length_total_for_curve(index, false); + if (time_alignment == MOD_GREASE_PENCIL_BUILD_TIMEALIGN_START) { + if (clamp_points) { + return std::clamp(factor * max_factor, 0.0f, 1.0f); + } + return factor * max_factor; + } + if (time_alignment == MOD_GREASE_PENCIL_BUILD_TIMEALIGN_END) { + const float min_factor = max_factor - 1.0f; + const float use_factor = factor * max_factor; + if (clamp_points) { + return std::clamp(use_factor - min_factor, 0.0f, 1.0f); + } + return use_factor - min_factor; + } + return 0.0f; + }; + + Array select(stroke_count); + selection.to_bools(select.as_mutable_span()); + Array result(stroke_count); + for (const int curve : curves.curves_range()) { + const float local_factor = select[curve] ? get_stroke_factor(factor_to_keep, curve) : 1.0f; + const int num_points = points_by_curve[curve].size() * local_factor; + result[curve] = num_points; + if (clamp_points) { + r_points_num += num_points; + if (num_points > 0) { + r_curves_num++; + } + } + } + return result; +} + +static bke::CurvesGeometry build_concurrent(bke::greasepencil::Drawing &drawing, + bke::CurvesGeometry &curves, + const IndexMask &selection, + const int time_alignment, + const int transition, + const float factor, + const float factor_start, + const float factor_opacity, + const float factor_radii, + StringRefNull target_vgname) +{ + int dst_curves_num, dst_points_num; + const bool has_fade = factor_start != factor; + const Array point_counts_to_keep = point_counts_to_keep_concurrent( + curves, selection, time_alignment, transition, factor, true, dst_curves_num, dst_points_num); + if (dst_curves_num == 0) { + return {}; + } + const Array starts_per_curve = has_fade ? point_counts_to_keep_concurrent(curves, + selection, + time_alignment, + transition, + factor_start, + false, + dst_curves_num, + dst_points_num) : + Array(0); + const Array ends_per_curve = has_fade ? point_counts_to_keep_concurrent(curves, + selection, + time_alignment, + transition, + factor, + false, + dst_curves_num, + dst_points_num) : + Array(0); + + const OffsetIndices points_by_curve = curves.points_by_curve(); + MutableSpan opacities = drawing.opacities_for_write(); + MutableSpan radii = drawing.radii_for_write(); + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + bke::SpanAttributeWriter weights = attributes.lookup_for_write_span(target_vgname); + + const bool is_vanishing = transition == MOD_GREASE_PENCIL_BUILD_TRANSITION_VANISH; + + bke::CurvesGeometry dst_curves(dst_points_num, dst_curves_num); + Array dst_to_src_point(dst_points_num); + Array dst_to_src_curve(dst_curves_num); + MutableSpan dst_offsets = dst_curves.offsets_for_write(); + dst_offsets[0] = 0; + + int next_curve = 0; + int next_point = 0; + for (const int curve : curves.curves_range()) { + if (!point_counts_to_keep[curve]) { + continue; + } + const IndexRange points = points_by_curve[curve]; + dst_offsets[next_curve] = point_counts_to_keep[curve]; + const int curve_size = points.size(); + + auto get_fade_weight = [&](const int local_index) { + const float fade_range = std::abs(ends_per_curve[curve] - starts_per_curve[curve]); + if (is_vanishing) { + const float factor_from_start = local_index - curve_size + ends_per_curve[curve]; + return 1.0f - std::clamp(factor_from_start / fade_range, 0.0f, 1.0f); + } + const float factor_from_start = local_index - starts_per_curve[curve]; + return std::clamp(factor_from_start / fade_range, 0.0f, 1.0f); + }; + + const int extra_offset = is_vanishing ? points.size() - point_counts_to_keep[curve] : 0; + for (const int stroke_point : IndexRange(point_counts_to_keep[curve])) { + const int src_point_index = points.first() + extra_offset + stroke_point; + if (has_fade) { + const float fade_weight = get_fade_weight(extra_offset + stroke_point); + opacities[src_point_index] = opacities[src_point_index] * + (1.0f - fade_weight * factor_opacity); + radii[src_point_index] = radii[src_point_index] * (1.0f - fade_weight * factor_radii); + if (!weights.span.is_empty()) { + weights.span[src_point_index] = fade_weight; + } + } + dst_to_src_point[next_point] = src_point_index; + next_point++; + } + dst_to_src_curve[next_curve] = curve; + next_curve++; + } + weights.finish(); + + offset_indices::accumulate_counts_to_offsets(dst_offsets); + + const bke::AttributeAccessor src_attributes = curves.attributes(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); + + gather_attributes( + src_attributes, bke::AttrDomain::Point, {}, {}, dst_to_src_point, dst_attributes); + gather_attributes( + src_attributes, bke::AttrDomain::Curve, {}, {}, dst_to_src_curve, dst_attributes); + + dst_curves.update_curve_types(); + + return dst_curves; +} + +static void points_info_sequential(const bke::CurvesGeometry &curves, + const IndexMask &selection, + const int transition, + const float factor, + const bool clamp_points, + int &r_curves_num, + int &r_points_num) +{ + const int stroke_count = curves.curves_num(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + + float factor_to_keep = transition == MOD_GREASE_PENCIL_BUILD_TRANSITION_GROW ? factor : + (1.0f - factor); + if (clamp_points) { + factor_to_keep = std::clamp(factor_to_keep, 0.0f, 1.0f); + } + + const bool is_vanishing = transition == MOD_GREASE_PENCIL_BUILD_TRANSITION_VANISH; + + int effective_points_num = 0; + selection.foreach_index( + [&](const int index) { effective_points_num += points_by_curve[index].size(); }); + + const int untouched_points_num = points_by_curve.total_size() - effective_points_num; + effective_points_num *= factor_to_keep; + effective_points_num += untouched_points_num; + + r_points_num = effective_points_num; + r_curves_num = 0; + + Array select(stroke_count); + selection.to_bools(select.as_mutable_span()); + + int counted_points_num = 0; + for (const int i : curves.curves_range()) { + const int stroke = is_vanishing ? stroke_count - i - 1 : i; + if (select[stroke] && counted_points_num >= effective_points_num) { + continue; + } + counted_points_num += points_by_curve[stroke].size(); + r_curves_num++; + } +} + +static bke::CurvesGeometry build_sequential(bke::greasepencil::Drawing &drawing, + bke::CurvesGeometry &curves, + const IndexMask &selection, + const int transition, + const float factor, + const float factor_start, + const float factor_opacity, + const float factor_radii, + StringRefNull target_vgname) +{ + const bool has_fade = factor_start != factor; + int dst_curves_num, dst_points_num; + int start_points_num, end_points_num, dummy_curves_num; + points_info_sequential( + curves, selection, transition, factor, true, dst_curves_num, dst_points_num); + + if (dst_curves_num == 0) { + return {}; + } + + points_info_sequential( + curves, selection, transition, factor_start, false, dummy_curves_num, start_points_num); + points_info_sequential( + curves, selection, transition, factor, false, dummy_curves_num, end_points_num); + + const OffsetIndices points_by_curve = curves.points_by_curve(); + MutableSpan opacities = drawing.opacities_for_write(); + MutableSpan radii = drawing.radii_for_write(); + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + bke::SpanAttributeWriter weights = attributes.lookup_for_write_span(target_vgname); + + const bool is_vanishing = transition == MOD_GREASE_PENCIL_BUILD_TRANSITION_VANISH; + + bke::CurvesGeometry dst_curves(dst_points_num, dst_curves_num); + MutableSpan dst_offsets = dst_curves.offsets_for_write(); + Array dst_to_src_point(dst_points_num); + Array dst_to_src_curve(dst_curves_num); + + dst_offsets[0] = 0; + + int next_curve = 1, next_point = 0; + IndexMaskMemory memory; + selection.complement(curves.curves_range(), memory).foreach_index([&](const int stroke) { + for (const int point : points_by_curve[stroke]) { + dst_to_src_point[next_point] = point; + next_point++; + } + dst_offsets[next_curve] = next_point; + next_curve++; + }); + + const int stroke_count = curves.curves_num(); + bool done_scanning = false; + selection.foreach_index([&](const int i) { + const int stroke = is_vanishing ? stroke_count - i - 1 : i; + if (done_scanning || next_point >= dst_points_num) { + done_scanning = true; + return; + } + + auto get_fade_weight = [&](const int next_point_count) { + return std::clamp(float(next_point_count - start_points_num) / + float(abs(end_points_num - start_points_num)), + 0.0f, + 1.0f); + }; + + const IndexRange points = points_by_curve[stroke]; + for (const int point : points) { + const int local_index = point - points.first(); + const int src_point_index = is_vanishing ? points.last() - local_index : point; + dst_to_src_point[next_point] = src_point_index; + + if (has_fade) { + const float fade_weight = get_fade_weight(next_point); + opacities[src_point_index] = opacities[src_point_index] * + (1.0f - fade_weight * factor_opacity); + radii[src_point_index] = radii[src_point_index] * (1.0f - fade_weight * factor_radii); + if (!weights.span.is_empty()) { + weights.span[src_point_index] = fade_weight; + } + } + + next_point++; + if (next_point >= dst_points_num) { + done_scanning = true; + break; + } + } + dst_offsets[next_curve] = next_point; + dst_to_src_curve[next_curve - 1] = i; + next_curve++; + }); + weights.finish(); + + BLI_assert(next_curve == (dst_curves_num + 1)); + BLI_assert(next_point == dst_points_num); + + const bke::AttributeAccessor src_attributes = curves.attributes(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); + + gather_attributes( + src_attributes, bke::AttrDomain::Point, {}, {}, dst_to_src_point, dst_attributes); + gather_attributes( + src_attributes, bke::AttrDomain::Curve, {}, {}, dst_to_src_curve, dst_attributes); + + dst_curves.update_curve_types(); + + return dst_curves; +} + +static bke::CurvesGeometry reorder_strokes(const bke::CurvesGeometry &curves, + const Span select, + const Object &object, + MutableSpan r_selection) +{ + const OffsetIndices points_by_curve = curves.points_by_curve(); + const Span positions = curves.positions(); + const float3 center = object.object_to_world().location(); + + struct Pair { + float value; + int index; + bool selected; + }; + + Array distances(curves.curves_num()); + for (const int stroke : curves.curves_range()) { + const IndexRange points = points_by_curve[stroke]; + const float3 p1 = positions[points.first()]; + const float3 p2 = positions[points.last()]; + distances[stroke].value = math::max(math::distance(p1, center), math::distance(p2, center)); + distances[stroke].index = stroke; + distances[stroke].selected = select[stroke]; + } + + parallel_sort( + distances.begin(), distances.end(), [](Pair &a, Pair &b) { return a.value < b.value; }); + + Array new_order(curves.curves_num()); + for (const int i : curves.curves_range()) { + new_order[i] = distances[i].index; + r_selection[i] = distances[i].selected; + } + + return geometry::reorder_curves_geometry(curves, new_order.as_span(), {}); +} + +static float get_factor_from_draw_speed(const bke::CurvesGeometry &curves, + const float time_elapsed, + const float speed_fac, + const float max_gap) +{ + const OffsetIndices points_by_curve = curves.points_by_curve(); + const bke::AttributeAccessor attributes = curves.attributes(); + const VArray init_times = + attributes.lookup_or_default("init_time", bke::AttrDomain::Curve, 0.0f).varray; + const VArray delta_times = + attributes.lookup_or_default("delta_time", bke::AttrDomain::Point, 0.0f).varray; + + Array times(curves.points_num()); + float current_time = 0; + float previous_init_time = init_times[0]; + for (const int curve : curves.curves_range()) { + if (curve > 0) { + current_time += math::max(init_times[curve] - previous_init_time, max_gap); + previous_init_time = init_times[curve]; + } + for (const int point : points_by_curve[curve]) { + current_time += delta_times[point]; + times[point] = current_time; + } + } + for (const int point : curves.points_range()) { + const float limit = time_elapsed * speed_fac; + if (times[point] >= limit) { + return math::clamp(float(point) / float(curves.points_num()), 0.0f, 1.0f); + } + } + return 1.0f; +} + +static float get_build_factor(const GreasePencilBuildTimeMode time_mode, + const int current_frame, + const int start_frame, + const int length, + const float percentage, + const bke::CurvesGeometry &curves, + const float scene_fps, + const float speed_fac, + const float max_gap, + const float fade) +{ + switch (time_mode) { + case MOD_GREASE_PENCIL_BUILD_TIMEMODE_FRAMES: + return math::clamp(float(current_frame - start_frame) / length, 0.0f, 1.0f) * (1.0f + fade); + case MOD_GREASE_PENCIL_BUILD_TIMEMODE_PERCENTAGE: + return percentage * (1.0f + fade); + case MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED: + return get_factor_from_draw_speed( + curves, float(current_frame) / scene_fps, speed_fac, max_gap) * + (1.0f + fade); + } + BLI_assert_unreachable(); + return 0.0f; +} + +static void build_drawing(const GreasePencilBuildModifierData &mmd, + const Object &ob, + bke::greasepencil::Drawing &drawing, + const bke::greasepencil::Drawing *previous_drawing, + const int current_time, + const float scene_fps) +{ + bke::CurvesGeometry &curves = drawing.strokes_for_write(); + + if (curves.points_num() == 0) { + return; + } + + IndexMaskMemory memory; + IndexMask selection = modifier::greasepencil::get_filtered_stroke_mask( + &ob, curves, mmd.influence, memory); + + /* Remove a count of #prev_strokes. */ + if (mmd.mode == MOD_GREASE_PENCIL_BUILD_MODE_ADDITIVE && previous_drawing != nullptr) { + const bke::CurvesGeometry &prev_curves = previous_drawing->strokes(); + const int prev_strokes = prev_curves.curves_num(); + const int added_strokes = curves.curves_num() - prev_strokes; + if (added_strokes > 0) { + Array work_on_select(curves.curves_num()); + selection.to_bools(work_on_select.as_mutable_span()); + work_on_select.as_mutable_span().take_front(prev_strokes).fill(false); + selection = IndexMask::from_bools(work_on_select, memory); + } + } + + if (mmd.object) { + const int curves_num = curves.curves_num(); + Array select(curves_num), reordered_select(curves_num); + selection.to_bools(select); + curves = reorder_strokes( + curves, select.as_span(), *mmd.object, reordered_select.as_mutable_span()); + selection = IndexMask::from_bools(reordered_select, memory); + } + + const float fade_factor = ((mmd.flag & MOD_GREASE_PENCIL_BUILD_USE_FADING) != 0) ? mmd.fade_fac : + 0.0f; + float factor = get_build_factor(GreasePencilBuildTimeMode(mmd.time_mode), + current_time, + mmd.start_delay, + mmd.length, + mmd.percentage_fac, + curves, + scene_fps, + mmd.speed_fac, + mmd.speed_maxgap, + fade_factor); + float factor_start = factor - fade_factor; + if (mmd.transition != MOD_GREASE_PENCIL_BUILD_TRANSITION_GROW) { + std::swap(factor, factor_start); + } + + const float use_time_alignment = mmd.transition != MOD_GREASE_PENCIL_BUILD_TRANSITION_GROW ? + !mmd.time_alignment : + mmd.time_alignment; + switch (mmd.mode) { + default: + case MOD_GREASE_PENCIL_BUILD_MODE_SEQUENTIAL: + curves = build_sequential(drawing, + curves, + selection, + mmd.transition, + factor, + factor_start, + mmd.fade_opacity_strength, + mmd.fade_thickness_strength, + mmd.target_vgname); + break; + case MOD_GREASE_PENCIL_BUILD_MODE_CONCURRENT: + curves = build_concurrent(drawing, + curves, + selection, + use_time_alignment, + mmd.transition, + factor, + factor_start, + mmd.fade_opacity_strength, + mmd.fade_thickness_strength, + mmd.target_vgname); + break; + case MOD_GREASE_PENCIL_BUILD_MODE_ADDITIVE: + curves = build_sequential(drawing, + curves, + selection, + mmd.transition, + factor, + factor_start, + mmd.fade_opacity_strength, + mmd.fade_thickness_strength, + mmd.target_vgname); + break; + } + + drawing.tag_topology_changed(); +} + +static void modify_geometry_set(ModifierData *md, + const ModifierEvalContext *ctx, + blender::bke::GeometrySet *geometry_set) +{ + const auto *mmd = reinterpret_cast(md); + + if (!geometry_set->has_grease_pencil()) { + return; + } + + GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write(); + const int eval_frame = grease_pencil.runtime->eval_frame; + + IndexMaskMemory mask_memory; + const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask( + grease_pencil, mmd->influence, mask_memory); + const Vector drawing_infos = + modifier::greasepencil::get_drawing_infos_by_layer(grease_pencil, layer_mask, eval_frame); + + if (mmd->flag & MOD_GREASE_PENCIL_BUILD_RESTRICT_TIME) { + if (eval_frame < mmd->start_frame || eval_frame > mmd->end_frame) { + return; + } + } + + const Scene &scene = *DEG_get_evaluated_scene(ctx->depsgraph); + const float scene_fps = float(scene.r.frs_sec) / scene.r.frs_sec_base; + const Span layers = grease_pencil.layers(); + + threading::parallel_for_each( + drawing_infos, [&](modifier::greasepencil::LayerDrawingInfo drawing_info) { + const bke::greasepencil::Drawing *prev_drawing = grease_pencil.get_drawing_at( + *layers[drawing_info.layer_index], eval_frame - 1); + build_drawing( + *mmd, *ctx->object, *drawing_info.drawing, prev_drawing, eval_frame, scene_fps); + }); +} + +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 GreasePencilBuildMode mode = GreasePencilBuildMode(RNA_enum_get(ptr, "mode")); + GreasePencilBuildTimeMode time_mode = GreasePencilBuildTimeMode(RNA_enum_get(ptr, "time_mode")); + + uiLayoutSetPropSep(layout, true); + + /* First: Build mode and build settings. */ + uiItemR(layout, ptr, "mode", UI_ITEM_NONE, nullptr, ICON_NONE); + if (mode == MOD_GREASE_PENCIL_BUILD_MODE_SEQUENTIAL) { + uiItemR(layout, ptr, "transition", UI_ITEM_NONE, nullptr, ICON_NONE); + } + if (mode == MOD_GREASE_PENCIL_BUILD_MODE_CONCURRENT) { + /* Concurrent mode doesn't support MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED, so unset it. */ + if (time_mode == MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED) { + RNA_enum_set(ptr, "time_mode", MOD_GREASE_PENCIL_BUILD_TIMEMODE_FRAMES); + time_mode = MOD_GREASE_PENCIL_BUILD_TIMEMODE_FRAMES; + } + uiItemR(layout, ptr, "transition", UI_ITEM_NONE, nullptr, ICON_NONE); + } + uiItemS(layout); + + /* Second: Time mode and time settings. */ + + uiItemR(layout, ptr, "time_mode", UI_ITEM_NONE, nullptr, ICON_NONE); + if (mode == MOD_GREASE_PENCIL_BUILD_MODE_CONCURRENT) { + uiItemR(layout, ptr, "concurrent_time_alignment", UI_ITEM_NONE, nullptr, ICON_NONE); + } + switch (time_mode) { + case MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED: + uiItemR(layout, ptr, "speed_factor", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(layout, ptr, "speed_maxgap", UI_ITEM_NONE, nullptr, ICON_NONE); + break; + case MOD_GREASE_PENCIL_BUILD_TIMEMODE_FRAMES: + uiItemR(layout, ptr, "length", UI_ITEM_NONE, IFACE_("Frames"), ICON_NONE); + if (mode != MOD_GREASE_PENCIL_BUILD_MODE_ADDITIVE) { + uiItemR(layout, ptr, "start_delay", UI_ITEM_NONE, nullptr, ICON_NONE); + } + break; + case MOD_GREASE_PENCIL_BUILD_TIMEMODE_PERCENTAGE: + uiItemR(layout, ptr, "percentage_factor", UI_ITEM_NONE, nullptr, ICON_NONE); + break; + default: + break; + } + uiItemS(layout); + uiItemR(layout, ptr, "object", UI_ITEM_NONE, nullptr, ICON_NONE); + + if (uiLayout *panel = uiLayoutPanelProp( + C, layout, ptr, "open_frame_range_panel", "Effective Range")) + { + uiLayoutSetPropSep(panel, true); + uiItemR( + panel, ptr, "use_restrict_frame_range", UI_ITEM_NONE, IFACE_("Custom Range"), ICON_NONE); + + const bool active = RNA_boolean_get(ptr, "use_restrict_frame_range"); + uiLayout *col = uiLayoutColumn(panel, false); + uiLayoutSetActive(col, active); + uiItemR(col, ptr, "frame_start", UI_ITEM_NONE, IFACE_("Start"), ICON_NONE); + uiItemR(col, ptr, "frame_end", UI_ITEM_NONE, IFACE_("End"), ICON_NONE); + } + + if (uiLayout *panel = uiLayoutPanelProp(C, layout, ptr, "open_fading_panel", "Fading")) { + uiLayoutSetPropSep(panel, true); + uiItemR(panel, ptr, "use_fading", UI_ITEM_NONE, IFACE_("Fade"), ICON_NONE); + + const bool active = RNA_boolean_get(ptr, "use_fading"); + uiLayout *col = uiLayoutColumn(panel, false); + uiLayoutSetActive(col, active); + + uiItemR(col, ptr, "fade_factor", UI_ITEM_NONE, IFACE_("Factor"), ICON_NONE); + + uiLayout *subcol = uiLayoutColumn(col, true); + uiItemR(subcol, ptr, "fade_thickness_strength", UI_ITEM_NONE, IFACE_("Thickness"), ICON_NONE); + uiItemR(subcol, ptr, "fade_opacity_strength", UI_ITEM_NONE, IFACE_("Opacity"), ICON_NONE); + + uiItemPointerR(col, + ptr, + "target_vertex_group", + &ob_ptr, + "vertex_groups", + IFACE_("Weight Output"), + 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_panel_end(layout, ptr); +} + +static void panel_register(ARegionType *region_type) +{ + modifier_panel_register(region_type, eModifierType_GreasePencilBuild, panel_draw); +} + +} // namespace blender + +ModifierTypeInfo modifierType_GreasePencilBuild = { + /*idname*/ "GreasePencilBuildModifier", + /*name*/ N_("Build"), + /*struct_name*/ "GreasePencilBuildModifierData", + /*struct_size*/ sizeof(GreasePencilBuildModifierData), + /*srna*/ &RNA_GreasePencilBuildModifier, + /*type*/ ModifierTypeType::Nonconstructive, + /*flags*/ + eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_EnableInEditmode | + eModifierTypeFlag_SupportsEditmode, + /*icon*/ ICON_MOD_LENGTH, + + /*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*/ blender::update_depsgraph, + /*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 85766fa5894..f7f361dda5b 100644 --- a/source/blender/modifiers/intern/MOD_util.cc +++ b/source/blender/modifiers/intern/MOD_util.cc @@ -287,5 +287,6 @@ void modifier_type_init(ModifierTypeInfo *types[]) INIT_TYPE(GreasePencilEnvelope); INIT_TYPE(GreasePencilOutline); INIT_TYPE(GreasePencilShrinkwrap); + INIT_TYPE(GreasePencilBuild); #undef INIT_TYPE }