This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/sculpt_paint/sculpt_expand.c
Pablo Dobarro 0e3475a0ec Fix wrong loop count in Sculpt Expand code
Introduced when refactoring the function in
8815e3e330
2021-05-11 23:31:43 +02:00

2264 lines
78 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup edsculpt
*/
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_linklist_stack.h"
#include "BLI_math.h"
#include "BLI_task.h"
#include "BLT_translation.h"
#include "DNA_brush_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "BKE_brush.h"
#include "BKE_ccg.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_image.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_multires.h"
#include "BKE_node.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_subdiv_ccg.h"
#include "DEG_depsgraph.h"
#include "WM_api.h"
#include "WM_message.h"
#include "WM_toolsystem.h"
#include "WM_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_view3d.h"
#include "paint_intern.h"
#include "sculpt_intern.h"
#include "IMB_colormanagement.h"
#include "IMB_imbuf.h"
#include "bmesh.h"
#include <math.h>
#include <stdlib.h>
/* Sculpt Expand. */
/* Operator for creating selections and patterns in Sculpt Mode. Expand can create masks, face sets
* and fill vertex colors. */
/* The main functionality of the operator
* - The operator initializes a value per vertex, called "falloff". There are multiple algorithms
* to generate these falloff values which will create different patterns in the result when using
* the operator. These falloff values require algorithms that rely on mesh connectivity, so they
* are only valid on parts of the mesh that are in the same connected component as the given
* initial vertices. If needed, these falloff values are propagated from vertex or grids into the
* base mesh faces.
*
* - On each modal callback, the operator gets the active vertex and face and gets its falloff
* value from its precalculated falloff. This is now the active falloff value.
* - Using the active falloff value and the settings of the expand operation (which can be modified
* during execution using the modal key-map), the operator loops over all elements in the mesh to
* check if they are enabled of not.
* - Based on each element state after evaluating the settings, the desired mesh data (mask, face
* sets, colors...) is updated.
*/
/**
* Used for defining an invalid vertex state (for example, when the cursor is not over the mesh).
*/
#define SCULPT_EXPAND_VERTEX_NONE -1
/** Used for defining an uninitialized active component index for an unused symmetry pass. */
#define EXPAND_ACTIVE_COMPONENT_NONE -1
/**
* Defines how much each time the texture distortion is increased/decreased
* when using the modal key-map.
*/
#define SCULPT_EXPAND_TEXTURE_DISTORTION_STEP 0.01f
/**
* This threshold offsets the required falloff value to start a new loop. This is needed because in
* some situations, vertices which have the same falloff value as max_falloff will start a new
* loop, which is undesired.
*/
#define SCULPT_EXPAND_LOOP_THRESHOLD 0.00001f
/**
* Defines how much changes in curvature in the mesh affect the falloff shape when using normal
* falloff. This default was found experimentally and it works well in most cases, but can be
* exposed for tweaking if needed.
*/
#define SCULPT_EXPAND_NORMALS_FALLOFF_EDGE_SENSITIVITY 300
/* Expand Modal Key-map. */
enum {
SCULPT_EXPAND_MODAL_CONFIRM = 1,
SCULPT_EXPAND_MODAL_CANCEL,
SCULPT_EXPAND_MODAL_INVERT,
SCULPT_EXPAND_MODAL_PRESERVE_TOGGLE,
SCULPT_EXPAND_MODAL_GRADIENT_TOGGLE,
SCULPT_EXPAND_MODAL_FALLOFF_CYCLE,
SCULPT_EXPAND_MODAL_RECURSION_STEP_GEODESIC,
SCULPT_EXPAND_MODAL_RECURSION_STEP_TOPOLOGY,
SCULPT_EXPAND_MODAL_MOVE_TOGGLE,
SCULPT_EXPAND_MODAL_FALLOFF_GEODESIC,
SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY,
SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS,
SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL,
SCULPT_EXPAND_MODAL_SNAP_TOGGLE,
SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE,
SCULPT_EXPAND_MODAL_LOOP_COUNT_DECREASE,
SCULPT_EXPAND_MODAL_BRUSH_GRADIENT_TOGGLE,
SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_INCREASE,
SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_DECREASE,
};
/* Functions for getting the state of mesh elements (vertices and base mesh faces). When the main
* functions for getting the state of an element return true it means that data associated to that
* element will be modified by expand. */
/**
* Returns true if the vertex is in a connected component with correctly initialized falloff
* values.
*/
static bool sculpt_expand_is_vert_in_active_component(SculptSession *ss,
ExpandCache *expand_cache,
const int v)
{
for (int i = 0; i < EXPAND_SYMM_AREAS; i++) {
if (ss->vertex_info.connected_component[v] == expand_cache->active_connected_components[i]) {
return true;
}
}
return false;
}
/**
* Returns true if the face is in a connected component with correctly initialized falloff values.
*/
static bool sculpt_expand_is_face_in_active_component(SculptSession *ss,
ExpandCache *expand_cache,
const int f)
{
const MLoop *loop = &ss->mloop[ss->mpoly[f].loopstart];
return sculpt_expand_is_vert_in_active_component(ss, expand_cache, loop->v);
}
/**
* Returns the falloff value of a vertex. This function includes texture distortion, which is not
* precomputed into the initial falloff values.
*/
static float sculpt_expand_falloff_value_vertex_get(SculptSession *ss,
ExpandCache *expand_cache,
const int v)
{
if (expand_cache->texture_distortion_strength == 0.0f) {
return expand_cache->vert_falloff[v];
}
if (!expand_cache->brush->mtex.tex) {
return expand_cache->vert_falloff[v];
}
float rgba[4];
const float *vertex_co = SCULPT_vertex_co_get(ss, v);
const float avg = BKE_brush_sample_tex_3d(
expand_cache->scene, expand_cache->brush, vertex_co, rgba, 0, ss->tex_pool);
const float distortion = (avg - 0.5f) * expand_cache->texture_distortion_strength *
expand_cache->max_vert_falloff;
return expand_cache->vert_falloff[v] + distortion;
}
/**
* Returns the maximum valid falloff value stored in the falloff array, taking the maximum possible
* texture distortion into account.
*/
static float sculpt_expand_max_vertex_falloff_get(ExpandCache *expand_cache)
{
if (expand_cache->texture_distortion_strength == 0.0f) {
return expand_cache->max_vert_falloff;
}
if (!expand_cache->brush->mtex.tex) {
return expand_cache->max_vert_falloff;
}
return expand_cache->max_vert_falloff +
(0.5f * expand_cache->texture_distortion_strength * expand_cache->max_vert_falloff);
}
/**
* Main function to get the state of a vertex for the current state and settings of a #ExpandCache.
* Returns true when the target data should be modified by expand.
*/
static bool sculpt_expand_state_get(SculptSession *ss, ExpandCache *expand_cache, const int v)
{
if (!SCULPT_vertex_visible_get(ss, v)) {
return false;
}
if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, v)) {
return false;
}
if (expand_cache->all_enabled) {
return true;
}
bool enabled = false;
if (expand_cache->snap) {
/* Face Sets are not being modified when using this function, so it is ok to get this directly
* from the Sculpt API instead of implementing a custom function to get them from
* expand_cache->original_face_sets. */
const int face_set = SCULPT_vertex_face_set_get(ss, v);
enabled = BLI_gset_haskey(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(face_set));
}
else {
const float max_falloff_factor = sculpt_expand_max_vertex_falloff_get(expand_cache);
const float loop_len = (max_falloff_factor / expand_cache->loop_count) +
SCULPT_EXPAND_LOOP_THRESHOLD;
const float vertex_falloff_factor = sculpt_expand_falloff_value_vertex_get(
ss, expand_cache, v);
const float active_factor = fmod(expand_cache->active_falloff, loop_len);
const float falloff_factor = fmod(vertex_falloff_factor, loop_len);
enabled = falloff_factor < active_factor;
}
if (expand_cache->invert) {
enabled = !enabled;
}
return enabled;
}
/**
* Main function to get the state of a face for the current state and settings of a #ExpandCache.
* Returns true when the target data should be modified by expand.
*/
static bool sculpt_expand_face_state_get(SculptSession *ss, ExpandCache *expand_cache, const int f)
{
if (ss->face_sets[f] <= 0) {
return false;
}
if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, f)) {
return false;
}
if (expand_cache->all_enabled) {
return true;
}
bool enabled = false;
if (expand_cache->snap_enabled_face_sets) {
const int face_set = expand_cache->original_face_sets[f];
enabled = BLI_gset_haskey(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(face_set));
}
else {
const float loop_len = (expand_cache->max_face_falloff / expand_cache->loop_count) +
SCULPT_EXPAND_LOOP_THRESHOLD;
const float active_factor = fmod(expand_cache->active_falloff, loop_len);
const float falloff_factor = fmod(expand_cache->face_falloff[f], loop_len);
enabled = falloff_factor < active_factor;
}
if (expand_cache->falloff_type == SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET) {
if (ss->face_sets[f] == expand_cache->initial_active_face_set) {
enabled = false;
}
}
if (expand_cache->invert) {
enabled = !enabled;
}
return enabled;
}
/**
* For target modes that support gradients (such as sculpt masks or colors), this function returns
* the corresponding gradient value for an enabled vertex.
*/
static float sculpt_expand_gradient_value_get(SculptSession *ss,
ExpandCache *expand_cache,
const int v)
{
if (!expand_cache->falloff_gradient) {
return 1.0f;
}
const float max_falloff_factor = sculpt_expand_max_vertex_falloff_get(expand_cache);
const float loop_len = (max_falloff_factor / expand_cache->loop_count) +
SCULPT_EXPAND_LOOP_THRESHOLD;
const float vertex_falloff_factor = sculpt_expand_falloff_value_vertex_get(ss, expand_cache, v);
const float active_factor = fmod(expand_cache->active_falloff, loop_len);
const float falloff_factor = fmod(vertex_falloff_factor, loop_len);
float linear_falloff;
if (expand_cache->invert) {
/* Active factor is the result of a modulus operation using loop_len, so they will never be
* equal and loop_len - active_factor should never be 0. */
BLI_assert((loop_len - active_factor) != 0.0f);
linear_falloff = (falloff_factor - active_factor) / (loop_len - active_factor);
}
else {
linear_falloff = 1.0f - (falloff_factor / active_factor);
}
if (!expand_cache->brush_gradient) {
return linear_falloff;
}
return BKE_brush_curve_strength(expand_cache->brush, linear_falloff, 1.0f);
}
/* Utility functions for getting all vertices state during expand. */
/**
* Returns a bitmap indexed by vertex index which contains if the vertex was enabled or not for a
* give expand_cache state.
*/
static BLI_bitmap *sculpt_expand_bitmap_from_enabled(SculptSession *ss, ExpandCache *expand_cache)
{
const int totvert = SCULPT_vertex_count_get(ss);
BLI_bitmap *enabled_vertices = BLI_BITMAP_NEW(totvert, "enabled vertices");
for (int i = 0; i < totvert; i++) {
const bool enabled = sculpt_expand_state_get(ss, expand_cache, i);
BLI_BITMAP_SET(enabled_vertices, i, enabled);
}
return enabled_vertices;
}
/**
* Returns a bitmap indexed by vertex index which contains if the vertex is in the boundary of the
* enabled vertices. This is defined as vertices that are enabled and at least have one connected
* vertex that is not enabled.
*/
static BLI_bitmap *sculpt_expand_boundary_from_enabled(SculptSession *ss,
const BLI_bitmap *enabled_vertices,
const bool use_mesh_boundary)
{
const int totvert = SCULPT_vertex_count_get(ss);
BLI_bitmap *boundary_vertices = BLI_BITMAP_NEW(totvert, "boundary vertices");
for (int i = 0; i < totvert; i++) {
if (!BLI_BITMAP_TEST(enabled_vertices, i)) {
continue;
}
bool is_expand_boundary = false;
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) {
if (!BLI_BITMAP_TEST(enabled_vertices, ni.index)) {
is_expand_boundary = true;
}
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
if (use_mesh_boundary && SCULPT_vertex_is_boundary(ss, i)) {
is_expand_boundary = true;
}
BLI_BITMAP_SET(boundary_vertices, i, is_expand_boundary);
}
return boundary_vertices;
}
/* Functions implementing different algorithms for initializing falloff values. */
/**
* Utility function to get the closet vertex after flipping an original vertex position based on
* an symmetry pass iteration index.
*/
static int sculpt_expand_get_vertex_index_for_symmetry_pass(Object *ob,
const char symm_it,
const int original_vertex)
{
SculptSession *ss = ob->sculpt;
int symm_vertex = SCULPT_EXPAND_VERTEX_NONE;
if (symm_it == 0) {
symm_vertex = original_vertex;
}
else {
float location[3];
flip_v3_v3(location, SCULPT_vertex_co_get(ss, original_vertex), symm_it);
symm_vertex = SCULPT_nearest_vertex_get(NULL, ob, location, FLT_MAX, false);
}
return symm_vertex;
}
/**
* Geodesic: Initializes the falloff with geodesic distances from the given active vertex, taking
* symmetry into account.
*/
static float *sculpt_expand_geodesic_falloff_create(Sculpt *sd, Object *ob, const int v)
{
return SCULPT_geodesic_from_vertex_and_symm(sd, ob, v, FLT_MAX);
}
/**
* Topology: Initializes the falloff using a flood-fill operation,
* increasing the falloff value by 1 when visiting a new vertex.
*/
typedef struct ExpandFloodFillData {
float original_normal[3];
float edge_sensitivity;
float *dists;
float *edge_factor;
} ExpandFloodFillData;
static bool expand_topology_floodfill_cb(
SculptSession *UNUSED(ss), int from_v, int to_v, bool is_duplicate, void *userdata)
{
ExpandFloodFillData *data = userdata;
if (!is_duplicate) {
const float to_it = data->dists[from_v] + 1.0f;
data->dists[to_v] = to_it;
}
else {
data->dists[to_v] = data->dists[from_v];
}
return true;
}
static float *sculpt_expand_topology_falloff_create(Sculpt *sd, Object *ob, const int v)
{
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "topology dist");
SculptFloodFill flood;
SCULPT_floodfill_init(ss, &flood);
SCULPT_floodfill_add_initial_with_symmetry(sd, ob, ss, &flood, v, FLT_MAX);
ExpandFloodFillData fdata;
fdata.dists = dists;
SCULPT_floodfill_execute(ss, &flood, expand_topology_floodfill_cb, &fdata);
SCULPT_floodfill_free(&flood);
return dists;
}
/**
* Normals: Flood-fills the mesh and reduces the falloff depending on the normal difference between
* each vertex and the previous one.
* This creates falloff patterns that follow and snap to the hard edges of the object.
*/
static bool mask_expand_normal_floodfill_cb(
SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata)
{
ExpandFloodFillData *data = userdata;
if (!is_duplicate) {
float current_normal[3], prev_normal[3];
SCULPT_vertex_normal_get(ss, to_v, current_normal);
SCULPT_vertex_normal_get(ss, from_v, prev_normal);
const float from_edge_factor = data->edge_factor[from_v];
data->edge_factor[to_v] = dot_v3v3(current_normal, prev_normal) * from_edge_factor;
data->dists[to_v] = dot_v3v3(data->original_normal, current_normal) *
powf(from_edge_factor, data->edge_sensitivity);
CLAMP(data->dists[to_v], 0.0f, 1.0f);
}
else {
/* PBVH_GRIDS duplicate handling. */
data->edge_factor[to_v] = data->edge_factor[from_v];
data->dists[to_v] = data->dists[from_v];
}
return true;
}
static float *sculpt_expand_normal_falloff_create(Sculpt *sd,
Object *ob,
const int v,
const float edge_sensitivity)
{
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
float *dists = MEM_malloc_arrayN(sizeof(float), totvert, "normal dist");
float *edge_factor = MEM_callocN(sizeof(float) * totvert, "mask edge factor");
for (int i = 0; i < totvert; i++) {
edge_factor[i] = 1.0f;
}
SculptFloodFill flood;
SCULPT_floodfill_init(ss, &flood);
SCULPT_floodfill_add_initial_with_symmetry(sd, ob, ss, &flood, v, FLT_MAX);
ExpandFloodFillData fdata;
fdata.dists = dists;
fdata.edge_factor = edge_factor;
fdata.edge_sensitivity = edge_sensitivity;
SCULPT_vertex_normal_get(ss, v, fdata.original_normal);
SCULPT_floodfill_execute(ss, &flood, mask_expand_normal_floodfill_cb, &fdata);
SCULPT_floodfill_free(&flood);
for (int repeat = 0; repeat < 2; repeat++) {
for (int i = 0; i < totvert; i++) {
float avg = 0.0f;
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) {
avg += dists[ni.index];
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
dists[i] = avg / ni.size;
}
}
MEM_SAFE_FREE(edge_factor);
return dists;
}
/**
* Spherical: Initializes the falloff based on the distance from a vertex, taking symmetry into
* account.
*/
static float *sculpt_expand_spherical_falloff_create(Object *ob, const int v)
{
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
float *dists = MEM_malloc_arrayN(sizeof(float), totvert, "spherical dist");
for (int i = 0; i < totvert; i++) {
dists[i] = FLT_MAX;
}
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
for (char symm_it = 0; symm_it <= symm; symm_it++) {
if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
continue;
}
const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v);
if (symm_vertex != -1) {
const float *co = SCULPT_vertex_co_get(ss, symm_vertex);
for (int i = 0; i < totvert; i++) {
dists[i] = min_ff(dists[i], len_v3v3(co, SCULPT_vertex_co_get(ss, i)));
}
}
}
return dists;
}
/**
* Boundary: This falloff mode uses the code from sculpt_boundary to initialize the closest mesh
* boundary to a falloff value of 0. Then, it propagates that falloff to the rest of the mesh so it
* stays parallel to the boundary, increasing the falloff value by 1 on each step.
*/
static float *sculpt_expand_boundary_topology_falloff_create(Object *ob, const int v)
{
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "spherical dist");
BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(totvert, "visited vertices");
GSQueue *queue = BLI_gsqueue_new(sizeof(int));
/* Search and initialize a boundary per symmetry pass, then mark those vertices as visited. */
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
for (char symm_it = 0; symm_it <= symm; symm_it++) {
if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
continue;
}
const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v);
SculptBoundary *boundary = SCULPT_boundary_data_init(ob, NULL, symm_vertex, FLT_MAX);
if (!boundary) {
continue;
}
for (int i = 0; i < boundary->num_vertices; i++) {
BLI_gsqueue_push(queue, &boundary->vertices[i]);
BLI_BITMAP_ENABLE(visited_vertices, boundary->vertices[i]);
}
SCULPT_boundary_data_free(boundary);
}
/* If there are no boundaries, return a falloff with all values set to 0. */
if (BLI_gsqueue_is_empty(queue)) {
return dists;
}
/* Propagate the values from the boundaries to the rest of the mesh. */
while (!BLI_gsqueue_is_empty(queue)) {
int v_next;
BLI_gsqueue_pop(queue, &v_next);
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v_next, ni) {
if (BLI_BITMAP_TEST(visited_vertices, ni.index)) {
continue;
}
dists[ni.index] = dists[v_next] + 1.0f;
BLI_BITMAP_ENABLE(visited_vertices, ni.index);
BLI_gsqueue_push(queue, &ni.index);
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
}
BLI_gsqueue_free(queue);
MEM_freeN(visited_vertices);
return dists;
}
/**
* Topology diagonals. This falloff is similar to topology, but it also considers the diagonals of
* the base mesh faces when checking a vertex neighbor. For this reason, this is not implement
* using the general flood-fill and sculpt neighbors accessors.
*/
static float *sculpt_expand_diagonals_falloff_create(Object *ob, const int v)
{
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "spherical dist");
/* This algorithm uses mesh data (polys and loops), so this falloff type can't be initialized for
* Multires. It also does not make sense to implement it for dyntopo as the result will be the
* same as Topology falloff. */
if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
return dists;
}
/* Search and mask as visited the initial vertices using the enabled symmetry passes. */
BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(totvert, "visited vertices");
GSQueue *queue = BLI_gsqueue_new(sizeof(int));
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
for (char symm_it = 0; symm_it <= symm; symm_it++) {
if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
continue;
}
const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v);
BLI_gsqueue_push(queue, &symm_vertex);
BLI_BITMAP_ENABLE(visited_vertices, symm_vertex);
}
if (BLI_gsqueue_is_empty(queue)) {
return dists;
}
/* Propagate the falloff increasing the value by 1 each time a new vertex is visited. */
Mesh *mesh = ob->data;
while (!BLI_gsqueue_is_empty(queue)) {
int v_next;
BLI_gsqueue_pop(queue, &v_next);
for (int j = 0; j < ss->pmap[v_next].count; j++) {
MPoly *p = &ss->mpoly[ss->pmap[v_next].indices[j]];
for (int l = 0; l < p->totloop; l++) {
const int neighbor_v = mesh->mloop[p->loopstart + l].v;
if (BLI_BITMAP_TEST(visited_vertices, neighbor_v)) {
continue;
}
dists[neighbor_v] = dists[v_next] + 1.0f;
BLI_BITMAP_ENABLE(visited_vertices, neighbor_v);
BLI_gsqueue_push(queue, &neighbor_v);
}
}
}
BLI_gsqueue_free(queue);
MEM_freeN(visited_vertices);
return dists;
}
/* Functions to update the max_falloff value in the #ExpandCache. These functions are called after
* initializing a new falloff to make sure that this value is always updated. */
/**
* Updates the max_falloff value for vertices in a #ExpandCache based on the current values of the
* falloff, skipping any invalid values initialized to FLT_MAX and not initialized components.
*/
static void sculpt_expand_update_max_vert_falloff_value(SculptSession *ss,
ExpandCache *expand_cache)
{
const int totvert = SCULPT_vertex_count_get(ss);
expand_cache->max_vert_falloff = -FLT_MAX;
for (int i = 0; i < totvert; i++) {
if (expand_cache->vert_falloff[i] == FLT_MAX) {
continue;
}
if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, i)) {
continue;
}
expand_cache->max_vert_falloff = max_ff(expand_cache->max_vert_falloff,
expand_cache->vert_falloff[i]);
}
}
/**
* Updates the max_falloff value for faces in a ExpandCache based on the current values of the
* falloff, skipping any invalid values initialized to FLT_MAX and not initialized components.
*/
static void sculpt_expand_update_max_face_falloff_factor(SculptSession *ss,
ExpandCache *expand_cache)
{
const int totface = ss->totfaces;
expand_cache->max_face_falloff = -FLT_MAX;
for (int i = 0; i < totface; i++) {
if (expand_cache->face_falloff[i] == FLT_MAX) {
continue;
}
if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, i)) {
continue;
}
expand_cache->max_face_falloff = max_ff(expand_cache->max_face_falloff,
expand_cache->face_falloff[i]);
}
}
/**
* Functions to get falloff values for faces from the values from the vertices. This is used for
* expanding Face Sets. Depending on the data type of the #SculptSession, this needs to get the per
* face falloff value from the connected vertices of each face or from the grids stored per loops
* for each face.
*/
static void sculpt_expand_grids_to_faces_falloff(SculptSession *ss,
Mesh *mesh,
ExpandCache *expand_cache)
{
const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh);
for (int p = 0; p < mesh->totpoly; p++) {
MPoly *poly = &mesh->mpoly[p];
float accum = 0.0f;
for (int l = 0; l < poly->totloop; l++) {
const int grid_loop_index = (poly->loopstart + l) * key->grid_area;
for (int g = 0; g < key->grid_area; g++) {
accum += expand_cache->vert_falloff[grid_loop_index + g];
}
}
expand_cache->face_falloff[p] = accum / (poly->totloop * key->grid_area);
}
}
static void sculpt_expand_vertex_to_faces_falloff(Mesh *mesh, ExpandCache *expand_cache)
{
for (int p = 0; p < mesh->totpoly; p++) {
MPoly *poly = &mesh->mpoly[p];
float accum = 0.0f;
for (int l = 0; l < poly->totloop; l++) {
MLoop *loop = &mesh->mloop[l + poly->loopstart];
accum += expand_cache->vert_falloff[loop->v];
}
expand_cache->face_falloff[p] = accum / poly->totloop;
}
}
/**
* Main function to update the faces falloff from a already calculated vertex falloff.
*/
static void sculpt_expand_mesh_face_falloff_from_vertex_falloff(SculptSession *ss,
Mesh *mesh,
ExpandCache *expand_cache)
{
BLI_assert(expand_cache->vert_falloff != NULL);
if (!expand_cache->face_falloff) {
expand_cache->face_falloff = MEM_malloc_arrayN(
mesh->totpoly, sizeof(float), "face falloff factors");
}
if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
sculpt_expand_vertex_to_faces_falloff(mesh, expand_cache);
}
else if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
sculpt_expand_grids_to_faces_falloff(ss, mesh, expand_cache);
}
else {
BLI_assert(false);
}
}
/* Recursions. These functions will generate new falloff values based on the state of the vertices
* from the current ExpandCache options and falloff values. */
/**
* Geodesic recursion: Initializes falloff values using geodesic distances from the boundary of the
* current vertices state.
*/
static void sculpt_expand_geodesics_from_state_boundary(Object *ob,
ExpandCache *expand_cache,
BLI_bitmap *enabled_vertices)
{
SculptSession *ss = ob->sculpt;
BLI_assert(BKE_pbvh_type(ss->pbvh) == PBVH_FACES);
GSet *initial_vertices = BLI_gset_int_new("initial_vertices");
BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(ss, enabled_vertices, false);
const int totvert = SCULPT_vertex_count_get(ss);
for (int i = 0; i < totvert; i++) {
if (!BLI_BITMAP_TEST(boundary_vertices, i)) {
continue;
}
BLI_gset_add(initial_vertices, POINTER_FROM_INT(i));
}
MEM_freeN(boundary_vertices);
MEM_SAFE_FREE(expand_cache->vert_falloff);
MEM_SAFE_FREE(expand_cache->face_falloff);
expand_cache->vert_falloff = SCULPT_geodesic_distances_create(ob, initial_vertices, FLT_MAX);
BLI_gset_free(initial_vertices, NULL);
}
/**
* Topology recursion: Initializes falloff values using topology steps from the boundary of the
* current vertices state, increasing the value by 1 each time a new vertex is visited.
*/
static void sculpt_expand_topology_from_state_boundary(Object *ob,
ExpandCache *expand_cache,
BLI_bitmap *enabled_vertices)
{
MEM_SAFE_FREE(expand_cache->vert_falloff);
MEM_SAFE_FREE(expand_cache->face_falloff);
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "topology dist");
BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(ss, enabled_vertices, false);
SculptFloodFill flood;
SCULPT_floodfill_init(ss, &flood);
for (int i = 0; i < totvert; i++) {
if (!BLI_BITMAP_TEST(boundary_vertices, i)) {
continue;
}
SCULPT_floodfill_add_and_skip_initial(&flood, i);
}
MEM_freeN(boundary_vertices);
ExpandFloodFillData fdata;
fdata.dists = dists;
SCULPT_floodfill_execute(ss, &flood, expand_topology_floodfill_cb, &fdata);
SCULPT_floodfill_free(&flood);
expand_cache->vert_falloff = dists;
}
/**
* Main function to create a recursion step from the current #ExpandCache state.
*/
static void sculpt_expand_resursion_step_add(Object *ob,
ExpandCache *expand_cache,
const eSculptExpandRecursionType recursion_type)
{
SculptSession *ss = ob->sculpt;
if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
return;
}
BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache);
/* Each time a new recursion step is created, reset the distortion strength. This is the expected
* result from the recursion, as otherwise the new falloff will render with undesired distortion
* from the beginning. */
expand_cache->texture_distortion_strength = 0.0f;
switch (recursion_type) {
case SCULPT_EXPAND_RECURSION_GEODESICS:
sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices);
break;
case SCULPT_EXPAND_RECURSION_TOPOLOGY:
sculpt_expand_topology_from_state_boundary(ob, expand_cache, enabled_vertices);
break;
}
sculpt_expand_update_max_vert_falloff_value(ss, expand_cache);
if (expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS) {
sculpt_expand_mesh_face_falloff_from_vertex_falloff(ss, ob->data, expand_cache);
sculpt_expand_update_max_face_falloff_factor(ss, expand_cache);
}
MEM_freeN(enabled_vertices);
}
/* Face Set Boundary falloff. */
/**
* When internal falloff is set to true, the falloff will fill the active Face Set with a gradient,
* otherwise the active Face Set will be filled with a constant falloff of 0.0f.
*/
static void sculpt_expand_initialize_from_face_set_boundary(Object *ob,
ExpandCache *expand_cache,
const int active_face_set,
const bool internal_falloff)
{
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
BLI_bitmap *enabled_vertices = BLI_BITMAP_NEW(totvert, "enabled vertices");
for (int i = 0; i < totvert; i++) {
if (!SCULPT_vertex_has_unique_face_set(ss, i)) {
continue;
}
if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) {
continue;
}
BLI_BITMAP_ENABLE(enabled_vertices, i);
}
if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices);
}
else {
sculpt_expand_topology_from_state_boundary(ob, expand_cache, enabled_vertices);
}
MEM_freeN(enabled_vertices);
if (internal_falloff) {
for (int i = 0; i < totvert; i++) {
if (!(SCULPT_vertex_has_face_set(ss, i, active_face_set) &&
SCULPT_vertex_has_unique_face_set(ss, i))) {
continue;
}
expand_cache->vert_falloff[i] *= -1.0f;
}
float min_factor = FLT_MAX;
for (int i = 0; i < totvert; i++) {
min_factor = min_ff(expand_cache->vert_falloff[i], min_factor);
}
const float additional_falloff = fabsf(min_factor);
for (int i = 0; i < totvert; i++) {
expand_cache->vert_falloff[i] += additional_falloff;
}
}
else {
for (int i = 0; i < totvert; i++) {
if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) {
continue;
}
expand_cache->vert_falloff[i] = 0.0f;
}
}
}
/**
* Main function to initialize new falloff values in a #ExpandCache given an initial vertex and a
* falloff type.
*/
static void sculpt_expand_falloff_factors_from_vertex_and_symm_create(
ExpandCache *expand_cache,
Sculpt *sd,
Object *ob,
const int v,
eSculptExpandFalloffType falloff_type)
{
MEM_SAFE_FREE(expand_cache->vert_falloff);
expand_cache->falloff_type = falloff_type;
SculptSession *ss = ob->sculpt;
const bool has_topology_info = BKE_pbvh_type(ss->pbvh) == PBVH_FACES;
switch (falloff_type) {
case SCULPT_EXPAND_FALLOFF_GEODESIC:
expand_cache->vert_falloff = has_topology_info ?
sculpt_expand_geodesic_falloff_create(sd, ob, v) :
sculpt_expand_spherical_falloff_create(ob, v);
break;
case SCULPT_EXPAND_FALLOFF_TOPOLOGY:
expand_cache->vert_falloff = sculpt_expand_topology_falloff_create(sd, ob, v);
break;
case SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS:
expand_cache->vert_falloff = has_topology_info ?
sculpt_expand_diagonals_falloff_create(ob, v) :
sculpt_expand_topology_falloff_create(sd, ob, v);
break;
case SCULPT_EXPAND_FALLOFF_NORMALS:
expand_cache->vert_falloff = sculpt_expand_normal_falloff_create(
sd, ob, v, SCULPT_EXPAND_NORMALS_FALLOFF_EDGE_SENSITIVITY);
break;
case SCULPT_EXPAND_FALLOFF_SPHERICAL:
expand_cache->vert_falloff = sculpt_expand_spherical_falloff_create(ob, v);
break;
case SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY:
expand_cache->vert_falloff = sculpt_expand_boundary_topology_falloff_create(ob, v);
break;
case SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET:
sculpt_expand_initialize_from_face_set_boundary(
ob, expand_cache, expand_cache->initial_active_face_set, true);
break;
case SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET:
sculpt_expand_initialize_from_face_set_boundary(
ob, expand_cache, expand_cache->initial_active_face_set, false);
break;
}
/* Update max falloff values and propagate to base mesh faces if needed. */
sculpt_expand_update_max_vert_falloff_value(ss, expand_cache);
if (expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS) {
sculpt_expand_mesh_face_falloff_from_vertex_falloff(ss, ob->data, expand_cache);
sculpt_expand_update_max_face_falloff_factor(ss, expand_cache);
}
}
/**
* Adds to the snapping Face Set `gset` all Face Sets which contain all enabled vertices for the
* current #ExpandCache state. This improves the usability of snapping, as already enabled elements
* won't switch their state when toggling snapping with the modal key-map.
*/
static void sculpt_expand_snap_initialize_from_enabled(SculptSession *ss,
ExpandCache *expand_cache)
{
if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
return;
}
/* Make sure this code runs with snapping and invert disabled. This simplifies the code and
* prevents using this function with snapping already enabled. */
const bool prev_snap_state = expand_cache->snap;
const bool prev_invert_state = expand_cache->invert;
expand_cache->snap = false;
expand_cache->invert = false;
BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache);
const int totface = ss->totfaces;
for (int i = 0; i < totface; i++) {
const int face_set = expand_cache->original_face_sets[i];
BLI_gset_add(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(face_set));
}
for (int p = 0; p < totface; p++) {
MPoly *poly = &ss->mpoly[p];
bool any_disabled = false;
for (int l = 0; l < poly->totloop; l++) {
MLoop *loop = &ss->mloop[l + poly->loopstart];
if (!BLI_BITMAP_TEST(enabled_vertices, loop->v)) {
any_disabled = true;
break;
}
}
if (any_disabled) {
const int face_set = expand_cache->original_face_sets[p];
BLI_gset_remove(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(face_set), NULL);
}
}
MEM_freeN(enabled_vertices);
expand_cache->snap = prev_snap_state;
expand_cache->invert = prev_invert_state;
}
/**
* Functions to free a #ExpandCache.
*/
static void sculpt_expand_cache_data_free(ExpandCache *expand_cache)
{
if (expand_cache->snap_enabled_face_sets) {
BLI_gset_free(expand_cache->snap_enabled_face_sets, NULL);
}
MEM_SAFE_FREE(expand_cache->nodes);
MEM_SAFE_FREE(expand_cache->vert_falloff);
MEM_SAFE_FREE(expand_cache->face_falloff);
MEM_SAFE_FREE(expand_cache->original_mask);
MEM_SAFE_FREE(expand_cache->original_face_sets);
MEM_SAFE_FREE(expand_cache->initial_face_sets);
MEM_SAFE_FREE(expand_cache->original_colors);
MEM_SAFE_FREE(expand_cache);
}
static void sculpt_expand_cache_free(SculptSession *ss)
{
sculpt_expand_cache_data_free(ss->expand_cache);
/* Needs to be set to NULL as the paint cursor relies on checking this pointer detecting if an
* expand operation is running. */
ss->expand_cache = NULL;
}
/**
* Functions to restore the original state from the #ExpandCache when canceling the operator.
*/
static void sculpt_expand_restore_face_set_data(SculptSession *ss, ExpandCache *expand_cache)
{
PBVHNode **nodes;
int totnode;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
for (int n = 0; n < totnode; n++) {
PBVHNode *node = nodes[n];
BKE_pbvh_node_mark_redraw(node);
}
MEM_freeN(nodes);
for (int i = 0; i < ss->totfaces; i++) {
ss->face_sets[i] = expand_cache->original_face_sets[i];
}
}
static void sculpt_expand_restore_color_data(SculptSession *ss, ExpandCache *expand_cache)
{
PBVHNode **nodes;
int totnode;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
for (int n = 0; n < totnode; n++) {
PBVHNode *node = nodes[n];
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
copy_v4_v4(vd.col, expand_cache->original_colors[vd.index]);
}
BKE_pbvh_vertex_iter_end;
BKE_pbvh_node_mark_redraw(node);
}
MEM_freeN(nodes);
}
static void sculpt_expand_restore_mask_data(SculptSession *ss, ExpandCache *expand_cache)
{
PBVHNode **nodes;
int totnode;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
for (int n = 0; n < totnode; n++) {
PBVHNode *node = nodes[n];
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
*vd.mask = expand_cache->original_mask[vd.index];
}
BKE_pbvh_vertex_iter_end;
BKE_pbvh_node_mark_redraw(node);
}
MEM_freeN(nodes);
}
/* Main function to restore the original state of the data to how it was before starting the expand
* operation. */
static void sculpt_expand_restore_original_state(bContext *C,
Object *ob,
ExpandCache *expand_cache)
{
SculptSession *ss = ob->sculpt;
switch (expand_cache->target) {
case SCULPT_EXPAND_TARGET_MASK:
sculpt_expand_restore_mask_data(ss, expand_cache);
SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
SCULPT_tag_update_overlays(C);
break;
case SCULPT_EXPAND_TARGET_FACE_SETS:
sculpt_expand_restore_face_set_data(ss, expand_cache);
SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
SCULPT_tag_update_overlays(C);
break;
case SCULPT_EXPAND_TARGET_COLORS:
sculpt_expand_restore_color_data(ss, expand_cache);
SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR);
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR);
break;
}
}
/**
* Cancel operator callback.
*/
static void sculpt_expand_cancel(bContext *C, wmOperator *UNUSED(op))
{
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
sculpt_expand_restore_original_state(C, ob, ss->expand_cache);
SCULPT_undo_push_end();
sculpt_expand_cache_free(ss);
}
/* Functions to update the sculpt mesh data. */
/**
* Callback to update mask data per PBVH node.
*/
static void sculpt_expand_mask_update_task_cb(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
PBVHNode *node = data->nodes[i];
ExpandCache *expand_cache = ss->expand_cache;
bool any_changed = false;
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) {
const float initial_mask = *vd.mask;
const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.index);
float new_mask;
if (enabled) {
new_mask = sculpt_expand_gradient_value_get(ss, expand_cache, vd.index);
}
else {
new_mask = 0.0f;
}
if (expand_cache->preserve) {
new_mask = max_ff(new_mask, expand_cache->original_mask[vd.index]);
}
if (new_mask == initial_mask) {
continue;
}
*vd.mask = clamp_f(new_mask, 0.0f, 1.0f);
any_changed = true;
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BKE_pbvh_vertex_iter_end;
if (any_changed) {
BKE_pbvh_node_mark_update_mask(node);
}
}
/**
* Update Face Set data. Not multi-threaded per node as nodes don't contain face arrays.
*/
static void sculpt_expand_face_sets_update(SculptSession *ss, ExpandCache *expand_cache)
{
const int totface = ss->totfaces;
for (int f = 0; f < totface; f++) {
const bool enabled = sculpt_expand_face_state_get(ss, expand_cache, f);
if (!enabled) {
continue;
}
if (expand_cache->preserve) {
ss->face_sets[f] += expand_cache->next_face_set;
}
else {
ss->face_sets[f] = expand_cache->next_face_set;
}
}
for (int i = 0; i < expand_cache->totnode; i++) {
BKE_pbvh_node_mark_redraw(ss->expand_cache->nodes[i]);
}
}
/**
* Callback to update vertex colors per PBVH node.
*/
static void sculpt_expand_colors_update_task_cb(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
PBVHNode *node = data->nodes[i];
ExpandCache *expand_cache = ss->expand_cache;
bool any_changed = false;
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) {
float initial_color[4];
copy_v4_v4(initial_color, vd.col);
const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.index);
float fade;
if (enabled) {
fade = sculpt_expand_gradient_value_get(ss, expand_cache, vd.index);
}
else {
fade = 0.0f;
}
fade *= 1.0f - *vd.mask;
fade = clamp_f(fade, 0.0f, 1.0f);
float final_color[4];
float final_fill_color[4];
mul_v4_v4fl(final_fill_color, expand_cache->fill_color, fade);
IMB_blend_color_float(final_color,
expand_cache->original_colors[vd.index],
final_fill_color,
expand_cache->blend_mode);
if (equals_v4v4(initial_color, final_color)) {
continue;
}
copy_v4_v4(vd.col, final_color);
any_changed = true;
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BKE_pbvh_vertex_iter_end;
if (any_changed) {
BKE_pbvh_node_mark_update_color(node);
}
}
static void sculpt_expand_flush_updates(bContext *C)
{
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
switch (ss->expand_cache->target) {
case SCULPT_EXPAND_TARGET_MASK:
SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
break;
case SCULPT_EXPAND_TARGET_FACE_SETS:
SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
break;
case SCULPT_EXPAND_TARGET_COLORS:
SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR);
break;
default:
break;
}
}
/* Store the original mesh data state in the expand cache. */
static void sculpt_expand_original_state_store(Object *ob, ExpandCache *expand_cache)
{
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
const int totface = ss->totfaces;
/* Face Sets are always stored as they are needed for snapping. */
expand_cache->initial_face_sets = MEM_malloc_arrayN(totface, sizeof(int), "initial face set");
expand_cache->original_face_sets = MEM_malloc_arrayN(totface, sizeof(int), "original face set");
for (int i = 0; i < totface; i++) {
expand_cache->initial_face_sets[i] = ss->face_sets[i];
expand_cache->original_face_sets[i] = ss->face_sets[i];
}
if (expand_cache->target == SCULPT_EXPAND_TARGET_MASK) {
expand_cache->original_mask = MEM_malloc_arrayN(totvert, sizeof(float), "initial mask");
for (int i = 0; i < totvert; i++) {
expand_cache->original_mask[i] = SCULPT_vertex_mask_get(ss, i);
}
}
if (expand_cache->target == SCULPT_EXPAND_TARGET_COLORS) {
expand_cache->original_colors = MEM_malloc_arrayN(totvert, sizeof(float[4]), "initial colors");
for (int i = 0; i < totvert; i++) {
copy_v4_v4(expand_cache->original_colors[i], SCULPT_vertex_color_get(ss, i));
}
}
}
/**
* Restore the state of the Face Sets before a new update.
*/
static void sculpt_expand_face_sets_restore(SculptSession *ss, ExpandCache *expand_cache)
{
const int totfaces = ss->totfaces;
for (int i = 0; i < totfaces; i++) {
ss->face_sets[i] = expand_cache->initial_face_sets[i];
}
}
static void sculpt_expand_update_for_vertex(bContext *C, Object *ob, const int vertex)
{
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
ExpandCache *expand_cache = ss->expand_cache;
/* Update the active factor in the cache. */
if (vertex == SCULPT_EXPAND_VERTEX_NONE) {
/* This means that the cursor is not over the mesh, so a valid active falloff can't be
* determined. In this situations, don't evaluate enabled states and default all vertices in
* connected components to enabled. */
expand_cache->active_falloff = expand_cache->max_vert_falloff;
expand_cache->all_enabled = true;
}
else {
expand_cache->active_falloff = expand_cache->vert_falloff[vertex];
expand_cache->all_enabled = false;
}
if (expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS) {
/* Face sets needs to be restored their initial state on each iteration as the overwrite
* existing data. */
sculpt_expand_face_sets_restore(ss, expand_cache);
}
/* Update the mesh sculpt data. */
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.nodes = expand_cache->nodes,
};
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, expand_cache->totnode);
switch (expand_cache->target) {
case SCULPT_EXPAND_TARGET_MASK:
BLI_task_parallel_range(
0, expand_cache->totnode, &data, sculpt_expand_mask_update_task_cb, &settings);
break;
case SCULPT_EXPAND_TARGET_FACE_SETS:
sculpt_expand_face_sets_update(ss, expand_cache);
break;
case SCULPT_EXPAND_TARGET_COLORS:
BLI_task_parallel_range(
0, expand_cache->totnode, &data, sculpt_expand_colors_update_task_cb, &settings);
break;
}
sculpt_expand_flush_updates(C);
}
/**
* Updates the #SculptSession cursor data and gets the active vertex
* if the cursor is over the mesh.
*/
static int sculpt_expand_target_vertex_update_and_get(bContext *C,
Object *ob,
const float mouse[2])
{
SculptSession *ss = ob->sculpt;
SculptCursorGeometryInfo sgi;
if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) {
return SCULPT_active_vertex_get(ss);
}
return SCULPT_EXPAND_VERTEX_NONE;
}
/**
* Moves the sculpt pivot to the average point of the boundary enabled vertices of the current
* expand state. Take symmetry and active components into account.
*/
static void sculpt_expand_reposition_pivot(bContext *C, Object *ob, ExpandCache *expand_cache)
{
SculptSession *ss = ob->sculpt;
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
const int totvert = SCULPT_vertex_count_get(ss);
const bool initial_invert_state = expand_cache->invert;
expand_cache->invert = false;
BLI_bitmap *enabled_vertices = sculpt_expand_bitmap_from_enabled(ss, expand_cache);
/* For boundary topology, position the pivot using only the boundary of the enabled vertices,
* without taking mesh boundary into account. This allows to create deformations like bending the
* mesh from the boundary of the mask that was just created. */
const float use_mesh_boundary = expand_cache->falloff_type !=
SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY;
BLI_bitmap *boundary_vertices = sculpt_expand_boundary_from_enabled(
ss, enabled_vertices, use_mesh_boundary);
/* Ignore invert state, as this is the expected behavior in most cases and mask are created in
* inverted state by default. */
expand_cache->invert = initial_invert_state;
int total = 0;
float avg[3] = {0.0f};
const float *expand_init_co = SCULPT_vertex_co_get(ss, expand_cache->initial_active_vertex);
for (int i = 0; i < totvert; i++) {
if (!BLI_BITMAP_TEST(boundary_vertices, i)) {
continue;
}
if (!sculpt_expand_is_vert_in_active_component(ss, expand_cache, i)) {
continue;
}
const float *vertex_co = SCULPT_vertex_co_get(ss, i);
if (!SCULPT_check_vertex_pivot_symmetry(vertex_co, expand_init_co, symm)) {
continue;
}
add_v3_v3(avg, vertex_co);
total++;
}
MEM_freeN(enabled_vertices);
MEM_freeN(boundary_vertices);
if (total > 0) {
mul_v3_v3fl(ss->pivot_pos, avg, 1.0f / total);
}
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data);
}
static void sculpt_expand_finish(bContext *C)
{
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
SCULPT_undo_push_end();
/* Tag all nodes to redraw to avoid artifacts after the fast partial updates. */
PBVHNode **nodes;
int totnode;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
for (int n = 0; n < totnode; n++) {
BKE_pbvh_node_mark_update_mask(nodes[n]);
}
MEM_freeN(nodes);
switch (ss->expand_cache->target) {
case SCULPT_EXPAND_TARGET_MASK:
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
break;
case SCULPT_EXPAND_TARGET_FACE_SETS:
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
break;
case SCULPT_EXPAND_TARGET_COLORS:
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR);
break;
}
sculpt_expand_cache_free(ss);
ED_workspace_status_text(C, NULL);
}
/**
* Finds and stores in the #ExpandCache the sculpt connected component index for each symmetry pass
* needed for expand.
*/
static void sculpt_expand_find_active_connected_components_from_vert(Object *ob,
ExpandCache *expand_cache,
const int initial_vertex)
{
SculptSession *ss = ob->sculpt;
for (int i = 0; i < EXPAND_SYMM_AREAS; i++) {
expand_cache->active_connected_components[i] = EXPAND_ACTIVE_COMPONENT_NONE;
}
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
for (char symm_it = 0; symm_it <= symm; symm_it++) {
if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) {
continue;
}
const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(
ob, symm_it, initial_vertex);
expand_cache->active_connected_components[(int)symm_it] =
ss->vertex_info.connected_component[symm_vertex];
}
}
/**
* Stores the active vertex, Face Set and mouse coordinates in the #ExpandCache based on the
* current cursor position.
*/
static void sculpt_expand_set_initial_components_for_mouse(bContext *C,
Object *ob,
ExpandCache *expand_cache,
const float mouse[2])
{
SculptSession *ss = ob->sculpt;
int initial_vertex = sculpt_expand_target_vertex_update_and_get(C, ob, mouse);
if (initial_vertex == SCULPT_EXPAND_VERTEX_NONE) {
/* Cursor not over the mesh, for creating valid initial falloffs, fallback to the last active
* vertex in the sculpt session. */
initial_vertex = SCULPT_active_vertex_get(ss);
}
copy_v2_v2(ss->expand_cache->initial_mouse, mouse);
expand_cache->initial_active_vertex = initial_vertex;
expand_cache->initial_active_face_set = SCULPT_active_face_set_get(ss);
if (expand_cache->next_face_set == SCULPT_FACE_SET_NONE) {
/* Only set the next face set once, otherwise this ID will constantly update to a new one each
* time this function is called for using a new initial vertex from a different cursor
* position. */
if (expand_cache->modify_active_face_set) {
expand_cache->next_face_set = SCULPT_active_face_set_get(ss);
}
else {
expand_cache->next_face_set = ED_sculpt_face_sets_find_next_available_id(ob->data);
}
}
/* The new mouse position can be over a different connected component, so this needs to be
* updated. */
sculpt_expand_find_active_connected_components_from_vert(ob, expand_cache, initial_vertex);
}
/**
* Displaces the initial mouse coordinates using the new mouse position to get a new active vertex.
* After that, initializes a new falloff of the same type with the new active vertex.
*/
static void sculpt_expand_move_propagation_origin(bContext *C,
Object *ob,
const wmEvent *event,
ExpandCache *expand_cache)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
const float mouse[2] = {event->mval[0], event->mval[1]};
float move_disp[2];
sub_v2_v2v2(move_disp, mouse, expand_cache->initial_mouse_move);
float new_mouse[2];
add_v2_v2v2(new_mouse, move_disp, expand_cache->original_mouse_move);
sculpt_expand_set_initial_components_for_mouse(C, ob, expand_cache, new_mouse);
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
sd,
ob,
expand_cache->initial_active_vertex,
expand_cache->move_preview_falloff_type);
}
/**
* Ensures that the #SculptSession contains the required data needed for Expand.
*/
static void sculpt_expand_ensure_sculptsession_data(Object *ob)
{
SculptSession *ss = ob->sculpt;
SCULPT_vertex_random_access_ensure(ss);
SCULPT_connected_components_ensure(ob);
SCULPT_boundary_info_ensure(ob);
if (!ss->tex_pool) {
ss->tex_pool = BKE_image_pool_new();
}
}
/**
* Returns the active Face Sets ID from the enabled face or grid in the #SculptSession.
*/
static int sculpt_expand_active_face_set_id_get(SculptSession *ss, ExpandCache *expand_cache)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES:
return expand_cache->original_face_sets[ss->active_face_index];
case PBVH_GRIDS: {
const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg,
ss->active_grid_index);
return expand_cache->original_face_sets[face_index];
}
case PBVH_BMESH: {
/* Dyntopo does not support Face Set functionality. */
BLI_assert(false);
}
}
return SCULPT_FACE_SET_NONE;
}
static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
/* Skips INBETWEEN_MOUSEMOVE events and other events that may cause unnecessary updates. */
if (!ELEM(event->type, MOUSEMOVE, EVT_MODAL_MAP)) {
return OPERATOR_RUNNING_MODAL;
}
/* Update SculptSession data. */
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
sculpt_expand_ensure_sculptsession_data(ob);
/* Update and get the active vertex (and face) from the cursor. */
const float mouse[2] = {event->mval[0], event->mval[1]};
const int target_expand_vertex = sculpt_expand_target_vertex_update_and_get(C, ob, mouse);
/* Handle the modal keymap state changes. */
ExpandCache *expand_cache = ss->expand_cache;
if (event->type == EVT_MODAL_MAP) {
switch (event->val) {
case SCULPT_EXPAND_MODAL_CANCEL: {
sculpt_expand_cancel(C, op);
return OPERATOR_FINISHED;
}
case SCULPT_EXPAND_MODAL_INVERT: {
expand_cache->invert = !expand_cache->invert;
break;
}
case SCULPT_EXPAND_MODAL_PRESERVE_TOGGLE: {
expand_cache->preserve = !expand_cache->preserve;
break;
}
case SCULPT_EXPAND_MODAL_GRADIENT_TOGGLE: {
expand_cache->falloff_gradient = !expand_cache->falloff_gradient;
break;
}
case SCULPT_EXPAND_MODAL_BRUSH_GRADIENT_TOGGLE: {
expand_cache->brush_gradient = !expand_cache->brush_gradient;
if (expand_cache->brush_gradient) {
expand_cache->falloff_gradient = true;
}
break;
}
case SCULPT_EXPAND_MODAL_SNAP_TOGGLE: {
if (expand_cache->snap) {
expand_cache->snap = false;
if (expand_cache->snap_enabled_face_sets) {
BLI_gset_free(expand_cache->snap_enabled_face_sets, NULL);
expand_cache->snap_enabled_face_sets = NULL;
}
}
else {
expand_cache->snap = true;
if (!expand_cache->snap_enabled_face_sets) {
expand_cache->snap_enabled_face_sets = BLI_gset_int_new("snap face sets");
}
sculpt_expand_snap_initialize_from_enabled(ss, expand_cache);
}
} break;
case SCULPT_EXPAND_MODAL_MOVE_TOGGLE: {
if (expand_cache->move) {
expand_cache->move = false;
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
sd,
ob,
expand_cache->initial_active_vertex,
expand_cache->move_original_falloff_type);
break;
}
expand_cache->move = true;
expand_cache->move_original_falloff_type = expand_cache->falloff_type;
copy_v2_v2(expand_cache->initial_mouse_move, mouse);
copy_v2_v2(expand_cache->original_mouse_move, expand_cache->initial_mouse);
if (expand_cache->falloff_type == SCULPT_EXPAND_FALLOFF_GEODESIC &&
SCULPT_vertex_count_get(ss) > expand_cache->max_geodesic_move_preview) {
/* Set to spherical falloff for preview in high poly meshes as it is the fastest one.
* In most cases it should match closely the preview from geodesic. */
expand_cache->move_preview_falloff_type = SCULPT_EXPAND_FALLOFF_SPHERICAL;
}
else {
expand_cache->move_preview_falloff_type = expand_cache->falloff_type;
}
break;
}
case SCULPT_EXPAND_MODAL_RECURSION_STEP_GEODESIC: {
sculpt_expand_resursion_step_add(ob, expand_cache, SCULPT_EXPAND_RECURSION_GEODESICS);
break;
}
case SCULPT_EXPAND_MODAL_RECURSION_STEP_TOPOLOGY: {
sculpt_expand_resursion_step_add(ob, expand_cache, SCULPT_EXPAND_RECURSION_TOPOLOGY);
break;
}
case SCULPT_EXPAND_MODAL_CONFIRM: {
sculpt_expand_update_for_vertex(C, ob, target_expand_vertex);
if (expand_cache->reposition_pivot) {
sculpt_expand_reposition_pivot(C, ob, expand_cache);
}
sculpt_expand_finish(C);
return OPERATOR_FINISHED;
}
case SCULPT_EXPAND_MODAL_FALLOFF_GEODESIC: {
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
sd,
ob,
expand_cache->initial_active_vertex,
SCULPT_EXPAND_FALLOFF_GEODESIC);
break;
}
case SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY: {
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
sd,
ob,
expand_cache->initial_active_vertex,
SCULPT_EXPAND_FALLOFF_TOPOLOGY);
break;
}
case SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS: {
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
sd,
ob,
expand_cache->initial_active_vertex,
SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS);
break;
}
case SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL: {
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
expand_cache,
sd,
ob,
expand_cache->initial_active_vertex,
SCULPT_EXPAND_FALLOFF_SPHERICAL);
break;
}
case SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE: {
expand_cache->loop_count += 1;
break;
}
case SCULPT_EXPAND_MODAL_LOOP_COUNT_DECREASE: {
expand_cache->loop_count -= 1;
expand_cache->loop_count = max_ii(expand_cache->loop_count, 1);
break;
}
case SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_INCREASE: {
if (expand_cache->texture_distortion_strength == 0.0f) {
if (expand_cache->brush->mtex.tex == NULL) {
BKE_report(op->reports,
RPT_WARNING,
"Active brush does not contain any texture to distort the expand boundary");
break;
}
if (expand_cache->brush->mtex.brush_map_mode != MTEX_MAP_MODE_3D) {
BKE_report(op->reports,
RPT_WARNING,
"Texture mapping not set to 3D, results may be unpredictable");
}
}
expand_cache->texture_distortion_strength += SCULPT_EXPAND_TEXTURE_DISTORTION_STEP;
break;
}
case SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_DECREASE: {
expand_cache->texture_distortion_strength -= SCULPT_EXPAND_TEXTURE_DISTORTION_STEP;
expand_cache->texture_distortion_strength = max_ff(
expand_cache->texture_distortion_strength, 0.0f);
break;
}
}
}
/* Handle expand origin movement if enabled. */
if (expand_cache->move) {
sculpt_expand_move_propagation_origin(C, ob, event, expand_cache);
}
/* Add new Face Sets IDs to the snapping gset if enabled. */
if (expand_cache->snap) {
const int active_face_set_id = sculpt_expand_active_face_set_id_get(ss, expand_cache);
if (!BLI_gset_haskey(expand_cache->snap_enabled_face_sets,
POINTER_FROM_INT(active_face_set_id))) {
BLI_gset_add(expand_cache->snap_enabled_face_sets, POINTER_FROM_INT(active_face_set_id));
}
}
/* Update the sculpt data with the current state of the #ExpandCache. */
sculpt_expand_update_for_vertex(C, ob, target_expand_vertex);
return OPERATOR_RUNNING_MODAL;
}
/**
* Deletes the `delete_id` Face Set ID from the mesh Face Sets
* and stores the result in `r_face_set`.
* The faces that were using the `delete_id` Face Set are filled
* using the content from their neighbors.
*/
static void sculpt_expand_delete_face_set_id(int *r_face_sets,
SculptSession *ss,
ExpandCache *expand_cache,
Mesh *mesh,
const int delete_id)
{
const int totface = ss->totfaces;
MeshElemMap *pmap = ss->pmap;
/* Check that all the face sets IDs in the mesh are not equal to `delete_id`
* before attempting to delete it. */
bool all_same_id = true;
for (int i = 0; i < totface; i++) {
if (!sculpt_expand_is_face_in_active_component(ss, expand_cache, i)) {
continue;
}
if (r_face_sets[i] != delete_id) {
all_same_id = false;
break;
}
}
if (all_same_id) {
return;
}
BLI_LINKSTACK_DECLARE(queue, void *);
BLI_LINKSTACK_DECLARE(queue_next, void *);
BLI_LINKSTACK_INIT(queue);
BLI_LINKSTACK_INIT(queue_next);
for (int i = 0; i < totface; i++) {
if (r_face_sets[i] == delete_id) {
BLI_LINKSTACK_PUSH(queue, POINTER_FROM_INT(i));
}
}
while (BLI_LINKSTACK_SIZE(queue)) {
while (BLI_LINKSTACK_SIZE(queue)) {
const int f_index = POINTER_AS_INT(BLI_LINKSTACK_POP(queue));
int other_id = delete_id;
const MPoly *c_poly = &mesh->mpoly[f_index];
for (int l = 0; l < c_poly->totloop; l++) {
const MLoop *c_loop = &mesh->mloop[c_poly->loopstart + l];
const MeshElemMap *vert_map = &pmap[c_loop->v];
for (int i = 0; i < vert_map->count; i++) {
const int neighbor_face_index = vert_map->indices[i];
if (r_face_sets[neighbor_face_index] != delete_id) {
other_id = r_face_sets[neighbor_face_index];
}
}
}
if (other_id != delete_id) {
r_face_sets[f_index] = other_id;
}
else {
BLI_LINKSTACK_PUSH(queue_next, POINTER_FROM_INT(f_index));
}
}
BLI_LINKSTACK_SWAP(queue, queue_next);
}
BLI_LINKSTACK_FREE(queue);
BLI_LINKSTACK_FREE(queue_next);
}
static void sculpt_expand_cache_initial_config_set(bContext *C,
wmOperator *op,
ExpandCache *expand_cache)
{
/* RNA properties. */
expand_cache->invert = RNA_boolean_get(op->ptr, "invert");
expand_cache->preserve = RNA_boolean_get(op->ptr, "use_mask_preserve");
expand_cache->falloff_gradient = RNA_boolean_get(op->ptr, "use_falloff_gradient");
expand_cache->target = RNA_enum_get(op->ptr, "target");
expand_cache->modify_active_face_set = RNA_boolean_get(op->ptr, "use_modify_active");
expand_cache->reposition_pivot = RNA_boolean_get(op->ptr, "use_reposition_pivot");
expand_cache->max_geodesic_move_preview = RNA_int_get(op->ptr, "max_geodesic_move_preview");
/* These can be exposed in RNA if needed. */
expand_cache->loop_count = 1;
expand_cache->brush_gradient = false;
/* Texture and color data from the active Brush. */
Object *ob = CTX_data_active_object(C);
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
SculptSession *ss = ob->sculpt;
expand_cache->brush = BKE_paint_brush(&sd->paint);
BKE_curvemapping_init(expand_cache->brush->curve);
copy_v4_fl(expand_cache->fill_color, 1.0f);
copy_v3_v3(expand_cache->fill_color, BKE_brush_color_get(ss->scene, expand_cache->brush));
IMB_colormanagement_srgb_to_scene_linear_v3(expand_cache->fill_color);
expand_cache->scene = CTX_data_scene(C);
expand_cache->mtex = &expand_cache->brush->mtex;
expand_cache->texture_distortion_strength = 0.0f;
expand_cache->blend_mode = expand_cache->brush->blend;
}
/**
* Does the undo sculpt push for the affected target data of the #ExpandCache.
*/
static void sculpt_expand_undo_push(Object *ob, ExpandCache *expand_cache)
{
SculptSession *ss = ob->sculpt;
PBVHNode **nodes;
int totnode;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
switch (expand_cache->target) {
case SCULPT_EXPAND_TARGET_MASK:
for (int i = 0; i < totnode; i++) {
SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK);
}
break;
case SCULPT_EXPAND_TARGET_FACE_SETS:
SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS);
break;
case SCULPT_EXPAND_TARGET_COLORS:
for (int i = 0; i < totnode; i++) {
SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_COLOR);
}
break;
}
MEM_freeN(nodes);
}
static int sculpt_expand_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
/* Create and configure the Expand Cache. */
ss->expand_cache = MEM_callocN(sizeof(ExpandCache), "expand cache");
sculpt_expand_cache_initial_config_set(C, op, ss->expand_cache);
/* Update object. */
const bool needs_colors = ss->expand_cache->target == SCULPT_EXPAND_TARGET_COLORS;
if (needs_colors) {
/* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of
* earlier steps modifying the data. */
BKE_sculpt_color_layer_create_if_needed(ob);
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
}
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, needs_colors);
/* Do nothing when the mesh has 0 vertices. */
const int totvert = SCULPT_vertex_count_get(ss);
if (totvert == 0) {
sculpt_expand_cache_free(ss);
return OPERATOR_CANCELLED;
}
/* Face Set operations are not supported in dyntopo. */
if (ss->expand_cache->target == SCULPT_EXPAND_TARGET_FACE_SETS &&
BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
sculpt_expand_cache_free(ss);
return OPERATOR_CANCELLED;
}
sculpt_expand_ensure_sculptsession_data(ob);
/* Initialize undo. */
SCULPT_undo_push_begin(ob, "expand");
sculpt_expand_undo_push(ob, ss->expand_cache);
/* Set the initial element for expand from the event position. */
const float mouse[2] = {event->mval[0], event->mval[1]};
sculpt_expand_set_initial_components_for_mouse(C, ob, ss->expand_cache, mouse);
/* Cache PBVH nodes. */
BKE_pbvh_search_gather(
ss->pbvh, NULL, NULL, &ss->expand_cache->nodes, &ss->expand_cache->totnode);
/* Store initial state. */
sculpt_expand_original_state_store(ob, ss->expand_cache);
if (ss->expand_cache->modify_active_face_set) {
sculpt_expand_delete_face_set_id(ss->expand_cache->initial_face_sets,
ss,
ss->expand_cache,
ob->data,
ss->expand_cache->next_face_set);
}
/* Initialize the falloff. */
eSculptExpandFalloffType falloff_type = RNA_enum_get(op->ptr, "falloff_type");
/* When starting from a boundary vertex, set the initial falloff to boundary. */
if (SCULPT_vertex_is_boundary(ss, ss->expand_cache->initial_active_vertex)) {
falloff_type = SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY;
}
sculpt_expand_falloff_factors_from_vertex_and_symm_create(
ss->expand_cache, sd, ob, ss->expand_cache->initial_active_vertex, falloff_type);
/* Initial mesh data update, resets all target data in the sculpt mesh. */
sculpt_expand_update_for_vertex(C, ob, ss->expand_cache->initial_active_vertex);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
void sculpt_expand_modal_keymap(wmKeyConfig *keyconf)
{
static const EnumPropertyItem modal_items[] = {
{SCULPT_EXPAND_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
{SCULPT_EXPAND_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
{SCULPT_EXPAND_MODAL_INVERT, "INVERT", 0, "Invert", ""},
{SCULPT_EXPAND_MODAL_PRESERVE_TOGGLE, "PRESERVE", 0, "Toggle Preserve State", ""},
{SCULPT_EXPAND_MODAL_GRADIENT_TOGGLE, "GRADIENT", 0, "Toggle Gradient", ""},
{SCULPT_EXPAND_MODAL_RECURSION_STEP_GEODESIC,
"RECURSION_STEP_GEODESIC",
0,
"Geodesic recursion step",
""},
{SCULPT_EXPAND_MODAL_RECURSION_STEP_TOPOLOGY,
"RECURSION_STEP_TOPOLOGY",
0,
"Topology recursion Step",
""},
{SCULPT_EXPAND_MODAL_MOVE_TOGGLE, "MOVE_TOGGLE", 0, "Move Origin", ""},
{SCULPT_EXPAND_MODAL_FALLOFF_GEODESIC, "FALLOFF_GEODESICS", 0, "Geodesic Falloff", ""},
{SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY, "FALLOFF_TOPOLOGY", 0, "Topology Falloff", ""},
{SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS,
"FALLOFF_TOPOLOGY_DIAGONALS",
0,
"Diagonals Falloff",
""},
{SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL, "FALLOFF_SPHERICAL", 0, "Spherical Falloff", ""},
{SCULPT_EXPAND_MODAL_SNAP_TOGGLE, "SNAP_TOGGLE", 0, "Snap expand to Face Sets", ""},
{SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE,
"LOOP_COUNT_INCREASE",
0,
"Loop Count Increase",
""},
{SCULPT_EXPAND_MODAL_LOOP_COUNT_DECREASE,
"LOOP_COUNT_DECREASE",
0,
"Loop Count Decrease",
""},
{SCULPT_EXPAND_MODAL_BRUSH_GRADIENT_TOGGLE,
"BRUSH_GRADIENT_TOGGLE",
0,
"Toggle Brush Gradient",
""},
{SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_INCREASE,
"TEXTURE_DISTORTION_INCREASE",
0,
"Texture Distortion Increase",
""},
{SCULPT_EXPAND_MODAL_TEXTURE_DISTORTION_DECREASE,
"TEXTURE_DISTORTION_DECREASE",
0,
"Texture Distortion Decrease",
""},
{0, NULL, 0, NULL, NULL},
};
static const char *name = "Sculpt Expand Modal";
wmKeyMap *keymap = WM_modalkeymap_find(keyconf, name);
/* This function is called for each spacetype, only needs to add map once. */
if (keymap && keymap->modal_items) {
return;
}
keymap = WM_modalkeymap_ensure(keyconf, name, modal_items);
WM_modalkeymap_assign(keymap, "SCULPT_OT_expand");
}
void SCULPT_OT_expand(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Expand";
ot->idname = "SCULPT_OT_expand";
ot->description = "Generic sculpt expand operator";
/* API callbacks. */
ot->invoke = sculpt_expand_invoke;
ot->modal = sculpt_expand_modal;
ot->cancel = sculpt_expand_cancel;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
static EnumPropertyItem prop_sculpt_expand_falloff_type_items[] = {
{SCULPT_EXPAND_FALLOFF_GEODESIC, "GEODESIC", 0, "Geodesic", ""},
{SCULPT_EXPAND_FALLOFF_TOPOLOGY, "TOPOLOGY", 0, "Topology", ""},
{SCULPT_EXPAND_FALLOFF_TOPOLOGY_DIAGONALS,
"TOPOLOGY_DIAGONALS",
0,
"Topology Diagonals",
""},
{SCULPT_EXPAND_FALLOFF_NORMALS, "NORMALS", 0, "Normals", ""},
{SCULPT_EXPAND_FALLOFF_SPHERICAL, "SPHERICAL", 0, "Spherical", ""},
{SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY, "BOUNDARY_TOPOLOGY", 0, "Boundary Topology", ""},
{SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET, "BOUNDARY_FACE_SET", 0, "Boundary Face Set", ""},
{SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET, "ACTIVE_FACE_SET", 0, "Active Face Set", ""},
{0, NULL, 0, NULL, NULL},
};
static EnumPropertyItem prop_sculpt_expand_target_type_items[] = {
{SCULPT_EXPAND_TARGET_MASK, "MASK", 0, "Mask", ""},
{SCULPT_EXPAND_TARGET_FACE_SETS, "FACE_SETS", 0, "Face Sets", ""},
{SCULPT_EXPAND_TARGET_COLORS, "COLOR", 0, "Color", ""},
{0, NULL, 0, NULL, NULL},
};
RNA_def_enum(ot->srna,
"target",
prop_sculpt_expand_target_type_items,
SCULPT_EXPAND_TARGET_MASK,
"Data Target",
"Data that is going to be modified in the expand operation");
RNA_def_enum(ot->srna,
"falloff_type",
prop_sculpt_expand_falloff_type_items,
SCULPT_EXPAND_FALLOFF_GEODESIC,
"Falloff Type",
"Initial falloff of the expand operation");
ot->prop = RNA_def_boolean(
ot->srna, "invert", false, "Invert", "Invert the expand active elements");
ot->prop = RNA_def_boolean(ot->srna,
"use_mask_preserve",
false,
"Preserve Previous",
"Preserve the previous state of the target data");
ot->prop = RNA_def_boolean(ot->srna,
"use_falloff_gradient",
false,
"Falloff Gradient",
"Expand Using a linear falloff");
ot->prop = RNA_def_boolean(ot->srna,
"use_modify_active",
false,
"Modify Active",
"Modify the active Face Set instead of creating a new one");
ot->prop = RNA_def_boolean(
ot->srna,
"use_reposition_pivot",
true,
"Reposition Pivot",
"Reposition the sculpt transform pivot to the boundary of the expand active area");
ot->prop = RNA_def_int(ot->srna,
"max_geodesic_move_preview",
10000,
0,
INT_MAX,
"Max Vertex Count for Geodesic Move Preview",
"Maximum number of vertices in the mesh for using geodesic falloff when "
"moving the origin of expand. If the total number of vertices is greater "
"than this value, the falloff will be set to spherical when moving",
0,
1000000);
}