Files
test/source/blender/modifiers/intern/MOD_grease_pencil_offset.cc
2024-02-19 15:59:59 +01:00

495 lines
19 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup modifiers
*/
#include "BLI_hash.h"
#include "BLI_math_matrix.hh"
#include "BLI_rand.h"
#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_material.h"
#include "BKE_modifier.hh"
#include "BKE_screen.hh"
#include "BLO_read_write.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "BLT_translation.h"
#include "WM_types.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
#include "MOD_grease_pencil_util.hh"
#include "MOD_modifiertypes.hh"
#include "MOD_ui_common.hh"
namespace blender {
static void init_data(ModifierData *md)
{
auto *omd = reinterpret_cast<GreasePencilOffsetModifierData *>(md);
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(omd, modifier));
MEMCPY_STRUCT_AFTER(omd, DNA_struct_default_get(GreasePencilOffsetModifierData), modifier);
modifier::greasepencil::init_influence_data(&omd->influence, false);
}
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
{
const auto *omd = reinterpret_cast<const GreasePencilOffsetModifierData *>(md);
auto *tomd = reinterpret_cast<GreasePencilOffsetModifierData *>(target);
modifier::greasepencil::free_influence_data(&tomd->influence);
BKE_modifier_copydata_generic(md, target, flag);
modifier::greasepencil::copy_influence_data(&omd->influence, &tomd->influence, flag);
}
static void free_data(ModifierData *md)
{
auto *omd = reinterpret_cast<GreasePencilOffsetModifierData *>(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<GreasePencilOffsetModifierData *>(md);
modifier::greasepencil::foreach_influence_ID_link(&omd->influence, ob, walk, user_data);
}
static void update_depsgraph(ModifierData * /*md*/, const ModifierUpdateDepsgraphContext *ctx)
{
DEG_add_object_relation(
ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Grease Pencil Offset Modifier");
}
static void apply_stroke_transform(const GreasePencilOffsetModifierData &omd,
const VArray<float> &weights,
const IndexRange &points,
const float3 &loc_factor,
const float3 &rot_factor,
const float3 &scale_factor,
const MutableSpan<float3> positions,
const MutableSpan<float> radii)
{
const bool has_global_offset = !(math::is_zero(float3(omd.loc)) &&
math::is_zero(float3(omd.rot)) &&
math::is_zero(float3(omd.scale)));
const bool has_stroke_offset = !(math::is_zero(float3(omd.stroke_loc)) &&
math::is_zero(float3(omd.stroke_rot)) &&
math::is_zero(float3(omd.stroke_scale)));
for (const int64_t i : points) {
const float weight = weights[i];
float3 &pos = positions[i];
float &radius = radii[i];
/* Add per-stroke offset. */
if (has_stroke_offset) {
const float4x4 matrix = math::from_loc_rot_scale<float4x4>(
omd.stroke_loc * loc_factor * weight,
omd.stroke_rot * rot_factor * weight,
float3(1.0f) + omd.stroke_scale * scale_factor * weight);
pos = math::transform_point(matrix, pos);
}
/* Add global offset. */
if (has_global_offset) {
const float3 scale = float3(1.0f) + float3(omd.scale) * weight;
const float4x4 matrix = math::from_loc_rot_scale<float4x4>(
float3(omd.loc) * weight, float3(omd.rot) * weight, scale);
pos = math::transform_point(matrix, pos);
/* Apply scale to thickness. */
const float unit_scale = (math::abs(scale.x) + math::abs(scale.y) + math::abs(scale.z)) /
3.0f;
radius *= unit_scale;
}
}
}
/** Randomized offset per stroke. */
static void modify_stroke_random(const Object &ob,
const GreasePencilOffsetModifierData &omd,
const IndexMask &curves_mask,
bke::CurvesGeometry &curves)
{
const bool use_uniform_scale = (omd.flag & MOD_GREASE_PENCIL_OFFSET_UNIFORM_RANDOM_SCALE);
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_span<float>(
"radius", bke::AttrDomain::Point);
const MutableSpan<float3> positions = curves.positions_for_write();
const VArray<float> vgroup_weights = modifier::greasepencil::get_influence_vertex_weights(
curves, omd.influence);
/* Make sure different modifiers get different seeds. */
const int seed = omd.seed + BLI_hash_string(ob.id.name + 2) + BLI_hash_string(omd.modifier.name);
const float rand_offset = BLI_hash_int_01(seed);
/* Generates a random number for loc/rot/scale channels, based on seed and a per-stroke random
* value r. */
auto get_random_channel = [&](const char channel, const double r) {
float rand = fmodf(r * 2.0f - 1.0f + rand_offset, 1.0f);
return fmodf(sin(rand * 12.9898f + channel * 78.233f) * 43758.5453f, 1.0f);
};
auto get_random_value = [&](const char channel, const int64_t curve_i) {
const uint halton_primes[3] = {2, 3, 7};
double halton_offset[3] = {0.0f, 0.0f, 0.0f};
double r[3];
/* To ensure a nice distribution, we use halton sequence and offset using the curve index. */
BLI_halton_3d(halton_primes, halton_offset, curve_i, r);
return get_random_channel(channel, r[0]);
};
auto get_random_vector = [&](const char channel, const int64_t curve_i) {
const uint halton_primes[3] = {2, 3, 7};
double halton_offset[3] = {0.0f, 0.0f, 0.0f};
double r[3];
/* To ensure a nice distribution, we use halton sequence and offset using the curve index. */
BLI_halton_3d(halton_primes, halton_offset, curve_i, r);
return float3(get_random_channel(channel, r[0]),
get_random_channel(channel, r[1]),
get_random_channel(channel, r[2]));
};
curves_mask.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
/* Randomness factors for loc/rot/scale per curve. */
const float3 loc_factor = get_random_vector(0, curve_i);
const float3 rot_factor = get_random_vector(1, curve_i);
const float3 scale_factor = use_uniform_scale ? float3(get_random_value(2, curve_i)) :
get_random_vector(2, curve_i);
apply_stroke_transform(
omd, vgroup_weights, points, loc_factor, rot_factor, scale_factor, positions, radii.span);
});
radii.finish();
}
/* This is a very weird/broken formula, but kept for compatibility. */
static float get_factor_from_index(const GreasePencilOffsetModifierData &omd,
const int size,
const int index)
{
const int step = max_ii(omd.stroke_step, 1);
const int start_offset = omd.stroke_start_offset;
return ((size - (index / step + start_offset % size) % size * step % size) - 1) / float(size);
}
/** Offset proportional to stroke index. */
static void modify_stroke_by_index(const GreasePencilOffsetModifierData &omd,
const IndexMask &curves_mask,
bke::CurvesGeometry &curves)
{
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_span<float>(
"radius", bke::AttrDomain::Point);
const MutableSpan<float3> positions = curves.positions_for_write();
const VArray<float> vgroup_weights = modifier::greasepencil::get_influence_vertex_weights(
curves, omd.influence);
curves_mask.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
const float factor = get_factor_from_index(omd, curves.curves_num(), curve_i);
apply_stroke_transform(omd,
vgroup_weights,
points,
float3(factor),
float3(factor),
float3(factor),
positions,
radii.span);
});
radii.finish();
}
/** Offset proportional to material index. */
static void modify_stroke_by_material(const Object &ob,
const GreasePencilOffsetModifierData &omd,
const IndexMask &curves_mask,
bke::CurvesGeometry &curves)
{
const short *totcolp = BKE_object_material_len_p(const_cast<Object *>(&ob));
const short totcol = totcolp ? *totcolp : 0;
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_span<float>(
"radius", bke::AttrDomain::Point);
const MutableSpan<float3> positions = curves.positions_for_write();
const VArray<float> vgroup_weights = modifier::greasepencil::get_influence_vertex_weights(
curves, omd.influence);
const VArray<int> stroke_materials = *attributes.lookup_or_default<int>(
"material_index", bke::AttrDomain::Curve, 0);
curves_mask.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
const float factor = get_factor_from_index(omd, totcol, stroke_materials[curve_i]);
apply_stroke_transform(omd,
vgroup_weights,
points,
float3(factor),
float3(factor),
float3(factor),
positions,
radii.span);
});
radii.finish();
}
/** Offset proportional to layer index. */
static void modify_stroke_by_layer(const GreasePencilOffsetModifierData &omd,
const int layer_index,
const int layers_num,
const IndexMask &curves_mask,
bke::CurvesGeometry &curves)
{
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_span<float>(
"radius", bke::AttrDomain::Point);
const MutableSpan<float3> positions = curves.positions_for_write();
const VArray<float> vgroup_weights = modifier::greasepencil::get_influence_vertex_weights(
curves, omd.influence);
const float factor = get_factor_from_index(omd, layers_num, layer_index);
curves_mask.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
apply_stroke_transform(omd,
vgroup_weights,
points,
float3(factor),
float3(factor),
float3(factor),
positions,
radii.span);
});
radii.finish();
}
static void modify_drawing(const ModifierData &md,
const ModifierEvalContext &ctx,
bke::greasepencil::Drawing &drawing)
{
const auto &omd = reinterpret_cast<const GreasePencilOffsetModifierData &>(md);
bke::CurvesGeometry &curves = drawing.strokes_for_write();
IndexMaskMemory mask_memory;
const IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask(
ctx.object, curves, omd.influence, mask_memory);
switch (omd.offset_mode) {
case MOD_GREASE_PENCIL_OFFSET_RANDOM:
modify_stroke_random(*ctx.object, omd, curves_mask, curves);
break;
case MOD_GREASE_PENCIL_OFFSET_MATERIAL:
modify_stroke_by_material(*ctx.object, omd, curves_mask, curves);
break;
case MOD_GREASE_PENCIL_OFFSET_STROKE:
modify_stroke_by_index(omd, curves_mask, curves);
break;
case MOD_GREASE_PENCIL_OFFSET_LAYER:
BLI_assert_unreachable();
break;
}
}
static void modify_drawing_by_layer(const ModifierData &md,
const ModifierEvalContext &ctx,
bke::greasepencil::Drawing &drawing,
int layer_index,
int layers_num)
{
const auto &omd = reinterpret_cast<const GreasePencilOffsetModifierData &>(md);
bke::CurvesGeometry &curves = drawing.strokes_for_write();
IndexMaskMemory mask_memory;
const IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask(
ctx.object, curves, omd.influence, mask_memory);
switch (omd.offset_mode) {
case MOD_GREASE_PENCIL_OFFSET_LAYER:
modify_stroke_by_layer(omd, layer_index, layers_num, curves_mask, curves);
break;
case MOD_GREASE_PENCIL_OFFSET_RANDOM:
case MOD_GREASE_PENCIL_OFFSET_MATERIAL:
case MOD_GREASE_PENCIL_OFFSET_STROKE:
BLI_assert_unreachable();
break;
}
}
static void modify_geometry_set(ModifierData *md,
const ModifierEvalContext *ctx,
bke::GeometrySet *geometry_set)
{
using bke::greasepencil::Drawing;
using modifier::greasepencil::LayerDrawingInfo;
auto *omd = reinterpret_cast<GreasePencilOffsetModifierData *>(md);
if (!geometry_set->has_grease_pencil()) {
return;
}
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
const int frame = grease_pencil.runtime->eval_frame;
IndexMaskMemory mask_memory;
const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask(
grease_pencil, omd->influence, mask_memory);
if (omd->offset_mode == MOD_GREASE_PENCIL_OFFSET_LAYER) {
const Vector<LayerDrawingInfo> drawings = modifier::greasepencil::get_drawing_infos_by_layer(
grease_pencil, layer_mask, frame);
threading::parallel_for_each(drawings, [&](const LayerDrawingInfo &info) {
modify_drawing_by_layer(
*md, *ctx, *info.drawing, info.layer_index, grease_pencil.layers().size());
});
}
else {
const Vector<Drawing *> drawings = modifier::greasepencil::get_drawings_for_write(
grease_pencil, layer_mask, frame);
threading::parallel_for_each(drawings,
[&](Drawing *drawing) { modify_drawing(*md, *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 offset_mode = GreasePencilOffsetModifierMode(RNA_enum_get(ptr, "offset_mode"));
uiLayoutSetPropSep(layout, true);
uiItemR(layout, ptr, "location", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, ptr, "rotation", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, ptr, "scale", UI_ITEM_NONE, nullptr, ICON_NONE);
LayoutPanelState *advanced_panel_state = BKE_panel_layout_panel_state_ensure(
panel, "advanced", true);
PointerRNA advanced_state_ptr = RNA_pointer_create(
nullptr, &RNA_LayoutPanelState, advanced_panel_state);
if (uiLayout *advanced_panel = uiLayoutPanelProp(
C, layout, &advanced_state_ptr, "is_open", "Advanced"))
{
uiItemR(advanced_panel, ptr, "offset_mode", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(advanced_panel, ptr, "stroke_location", UI_ITEM_NONE, IFACE_("Offset"), ICON_NONE);
uiItemR(advanced_panel, ptr, "stroke_rotation", UI_ITEM_NONE, IFACE_("Rotation"), ICON_NONE);
uiItemR(advanced_panel, ptr, "stroke_scale", UI_ITEM_NONE, IFACE_("Scale"), ICON_NONE);
uiLayout *col = uiLayoutColumn(advanced_panel, true);
switch (offset_mode) {
case MOD_GREASE_PENCIL_OFFSET_RANDOM:
uiItemR(advanced_panel, ptr, "use_uniform_random_scale", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(advanced_panel, ptr, "seed", UI_ITEM_NONE, nullptr, ICON_NONE);
break;
case MOD_GREASE_PENCIL_OFFSET_STROKE:
uiItemR(col, ptr, "stroke_step", UI_ITEM_NONE, IFACE_("Stroke Step"), ICON_NONE);
uiItemR(col, ptr, "stroke_start_offset", UI_ITEM_NONE, IFACE_("Offset"), ICON_NONE);
break;
case MOD_GREASE_PENCIL_OFFSET_MATERIAL:
uiItemR(col, ptr, "stroke_step", UI_ITEM_NONE, IFACE_("Material Step"), ICON_NONE);
uiItemR(col, ptr, "stroke_start_offset", UI_ITEM_NONE, IFACE_("Offset"), ICON_NONE);
break;
case MOD_GREASE_PENCIL_OFFSET_LAYER:
uiItemR(col, ptr, "stroke_step", UI_ITEM_NONE, IFACE_("Layer Step"), ICON_NONE);
uiItemR(col, ptr, "stroke_start_offset", UI_ITEM_NONE, IFACE_("Offset"), ICON_NONE);
break;
}
}
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_GreasePencilOffset, panel_draw);
}
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
{
const auto *omd = reinterpret_cast<const GreasePencilOffsetModifierData *>(md);
BLO_write_struct(writer, GreasePencilOffsetModifierData, omd);
modifier::greasepencil::write_influence_data(writer, &omd->influence);
}
static void blend_read(BlendDataReader *reader, ModifierData *md)
{
auto *omd = reinterpret_cast<GreasePencilOffsetModifierData *>(md);
modifier::greasepencil::read_influence_data(reader, &omd->influence);
}
} // namespace blender
ModifierTypeInfo modifierType_GreasePencilOffset = {
/*idname*/ "GreasePencilOffset",
/*name*/ N_("Offset"),
/*struct_name*/ "GreasePencilOffsetModifierData",
/*struct_size*/ sizeof(GreasePencilOffsetModifierData),
/*srna*/ &RNA_GreasePencilOffsetModifier,
/*type*/ ModifierTypeType::OnlyDeform,
/*flags*/ eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_SupportsEditmode |
eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping,
/*icon*/ ICON_MOD_OFFSET,
/*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,
};