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/paint_mask.c
Campbell Barton b8e8c0e325 Cleanup: comment line length (editors)
Prevents clang-format wrapping text before comments.
2019-01-15 23:30:31 +11:00

547 lines
14 KiB
C

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* 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) 2012 by Nicholas Bishop
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s):
*
* ***** END GPL LICENSE BLOCK *****
*
*/
/** \file blender/editors/sculpt_paint/paint_mask.c
* \ingroup edsculpt
*/
#include "MEM_guardedalloc.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "BLI_bitmap_draw_2d.h"
#include "BLI_math_matrix.h"
#include "BLI_math_geom.h"
#include "BLI_utildefines.h"
#include "BLI_lasso_2d.h"
#include "BLI_task.h"
#include "BKE_pbvh.h"
#include "BKE_ccg.h"
#include "BKE_context.h"
#include "BKE_multires.h"
#include "BKE_paint.h"
#include "BKE_subsurf.h"
#include "DEG_depsgraph.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_view3d.h"
#include "bmesh.h"
#include "paint_intern.h"
#include "sculpt_intern.h" /* for undo push */
#include <stdlib.h>
static const EnumPropertyItem mode_items[] = {
{PAINT_MASK_FLOOD_VALUE, "VALUE", 0, "Value", "Set mask to the level specified by the 'value' property"},
{PAINT_MASK_FLOOD_VALUE_INVERSE, "VALUE_INVERSE", 0, "Value Inverted", "Set mask to the level specified by the inverted 'value' property"},
{PAINT_MASK_INVERT, "INVERT", 0, "Invert", "Invert the mask"},
{0}};
static void mask_flood_fill_set_elem(
float *elem,
PaintMaskFloodMode mode,
float value)
{
switch (mode) {
case PAINT_MASK_FLOOD_VALUE:
(*elem) = value;
break;
case PAINT_MASK_FLOOD_VALUE_INVERSE:
(*elem) = 1.0f - value;
break;
case PAINT_MASK_INVERT:
(*elem) = 1.0f - (*elem);
break;
}
}
typedef struct MaskTaskData {
Object *ob;
PBVH *pbvh;
PBVHNode **nodes;
bool multires;
PaintMaskFloodMode mode;
float value;
float (*clip_planes_final)[4];
} MaskTaskData;
static void mask_flood_fill_task_cb(
void *__restrict userdata,
const int i,
const ParallelRangeTLS *__restrict UNUSED(tls))
{
MaskTaskData *data = userdata;
PBVHNode *node = data->nodes[i];
const PaintMaskFloodMode mode = data->mode;
const float value = data->value;
PBVHVertexIter vi;
sculpt_undo_push_node(data->ob, node, SCULPT_UNDO_MASK);
BKE_pbvh_vertex_iter_begin(data->pbvh, node, vi, PBVH_ITER_UNIQUE) {
mask_flood_fill_set_elem(vi.mask, mode, value);
} BKE_pbvh_vertex_iter_end;
BKE_pbvh_node_mark_redraw(node);
if (data->multires)
BKE_pbvh_node_mark_normals_update(node);
}
static int mask_flood_fill_exec(bContext *C, wmOperator *op)
{
ARegion *ar = CTX_wm_region(C);
struct Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
Depsgraph *depsgraph = CTX_data_depsgraph(C);
PaintMaskFloodMode mode;
float value;
PBVH *pbvh;
PBVHNode **nodes;
int totnode;
bool multires;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
mode = RNA_enum_get(op->ptr, "mode");
value = RNA_float_get(op->ptr, "value");
BKE_sculpt_update_mesh_elements(depsgraph, scene, sd, ob, false, true);
pbvh = ob->sculpt->pbvh;
multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS);
BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
sculpt_undo_push_begin("Mask flood fill");
MaskTaskData data = {
.ob = ob, .pbvh = pbvh, .nodes = nodes, .multires = multires,
.mode = mode, .value = value,
};
ParallelRangeSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT);
BLI_task_parallel_range(
0, totnode, &data, mask_flood_fill_task_cb,
&settings);
if (multires)
multires_mark_as_modified(ob, MULTIRES_COORDS_MODIFIED);
sculpt_undo_push_end();
if (nodes)
MEM_freeN(nodes);
ED_region_tag_redraw(ar);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
return OPERATOR_FINISHED;
}
void PAINT_OT_mask_flood_fill(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Mask Flood Fill";
ot->idname = "PAINT_OT_mask_flood_fill";
ot->description = "Fill the whole mask with a given value, or invert its values";
/* api callbacks */
ot->exec = mask_flood_fill_exec;
ot->poll = sculpt_mode_poll;
ot->flag = OPTYPE_REGISTER;
/* rna */
RNA_def_enum(ot->srna, "mode", mode_items, PAINT_MASK_FLOOD_VALUE, "Mode", NULL);
RNA_def_float(ot->srna, "value", 0, 0, 1, "Value",
"Mask level to use when mode is 'Value'; zero means no masking and one is fully masked", 0, 1);
}
/* Box select, operator is VIEW3D_OT_select_box, defined in view3d_select.c */
static bool is_effected(float planes[4][4], const float co[3])
{
return isect_point_planes_v3(planes, 4, co);
}
static void flip_plane(float out[4], const float in[4], const char symm)
{
if (symm & PAINT_SYMM_X)
out[0] = -in[0];
else
out[0] = in[0];
if (symm & PAINT_SYMM_Y)
out[1] = -in[1];
else
out[1] = in[1];
if (symm & PAINT_SYMM_Z)
out[2] = -in[2];
else
out[2] = in[2];
out[3] = in[3];
}
static void mask_box_select_task_cb(
void *__restrict userdata,
const int i,
const ParallelRangeTLS *__restrict UNUSED(tls))
{
MaskTaskData *data = userdata;
PBVHNode *node = data->nodes[i];
const PaintMaskFloodMode mode = data->mode;
const float value = data->value;
float (*clip_planes_final)[4] = data->clip_planes_final;
PBVHVertexIter vi;
bool any_masked = false;
BKE_pbvh_vertex_iter_begin(data->pbvh, node, vi, PBVH_ITER_UNIQUE) {
if (is_effected(clip_planes_final, vi.co)) {
if (!any_masked) {
any_masked = true;
sculpt_undo_push_node(data->ob, node, SCULPT_UNDO_MASK);
BKE_pbvh_node_mark_redraw(node);
if (data->multires)
BKE_pbvh_node_mark_normals_update(node);
}
mask_flood_fill_set_elem(vi.mask, mode, value);
}
} BKE_pbvh_vertex_iter_end;
}
int ED_sculpt_mask_box_select(struct bContext *C, ViewContext *vc, const rcti *rect, bool select)
{
Depsgraph *depsgraph = CTX_data_depsgraph(C);
Sculpt *sd = vc->scene->toolsettings->sculpt;
BoundBox bb;
float clip_planes[4][4];
float clip_planes_final[4][4];
ARegion *ar = vc->ar;
struct Scene *scene = vc->scene;
Object *ob = vc->obact;
PaintMaskFloodMode mode;
float value;
bool multires;
PBVH *pbvh;
PBVHNode **nodes;
int totnode, symmpass;
int symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
mode = PAINT_MASK_FLOOD_VALUE;
value = select ? 1.0 : 0.0;
/* transform the clip planes in object space */
ED_view3d_clipping_calc(&bb, clip_planes, vc->ar, vc->obact, rect);
negate_m4(clip_planes);
BKE_sculpt_update_mesh_elements(depsgraph, scene, sd, ob, false, true);
pbvh = ob->sculpt->pbvh;
multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS);
sculpt_undo_push_begin("Mask box fill");
for (symmpass = 0; symmpass <= symm; ++symmpass) {
if (symmpass == 0 ||
(symm & symmpass &&
(symm != 5 || symmpass != 3) &&
(symm != 6 || (symmpass != 3 && symmpass != 5))))
{
int j = 0;
/* flip the planes symmetrically as needed */
for (; j < 4; j++) {
flip_plane(clip_planes_final[j], clip_planes[j], symmpass);
}
BKE_pbvh_search_gather(pbvh, BKE_pbvh_node_planes_contain_AABB, clip_planes_final, &nodes, &totnode);
MaskTaskData data = {
.ob = ob, .pbvh = pbvh, .nodes = nodes, .multires = multires,
.mode = mode, .value = value, .clip_planes_final = clip_planes_final,
};
ParallelRangeSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT);
BLI_task_parallel_range(
0, totnode, &data, mask_box_select_task_cb,
&settings);
if (nodes)
MEM_freeN(nodes);
}
}
if (multires)
multires_mark_as_modified(ob, MULTIRES_COORDS_MODIFIED);
sculpt_undo_push_end();
ED_region_tag_redraw(ar);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
return OPERATOR_FINISHED;
}
typedef struct LassoMaskData {
struct ViewContext *vc;
float projviewobjmat[4][4];
BLI_bitmap *px;
int width;
rcti rect; /* bounding box for scanfilling */
int symmpass;
MaskTaskData task_data;
} LassoMaskData;
/* Lasso select. This could be defined as part of VIEW3D_OT_select_lasso, still the shortcuts conflict,
* so we will use a separate operator */
static bool is_effected_lasso(LassoMaskData *data, float co[3])
{
float scr_co_f[2];
int scr_co_s[2];
float co_final[3];
flip_v3_v3(co_final, co, data->symmpass);
/* first project point to 2d space */
ED_view3d_project_float_v2_m4(data->vc->ar, co_final, scr_co_f, data->projviewobjmat);
scr_co_s[0] = scr_co_f[0];
scr_co_s[1] = scr_co_f[1];
/* clip against screen, because lasso is limited to screen only */
if ((scr_co_s[0] < data->rect.xmin) ||
(scr_co_s[1] < data->rect.ymin) ||
(scr_co_s[0] >= data->rect.xmax) ||
(scr_co_s[1] >= data->rect.ymax))
{
return false;
}
scr_co_s[0] -= data->rect.xmin;
scr_co_s[1] -= data->rect.ymin;
return BLI_BITMAP_TEST_BOOL(data->px, scr_co_s[1] * data->width + scr_co_s[0]);
}
static void mask_lasso_px_cb(int x, int x_end, int y, void *user_data)
{
LassoMaskData *data = user_data;
int index = (y * data->width) + x;
int index_end = (y * data->width) + x_end;
do {
BLI_BITMAP_ENABLE(data->px, index);
} while (++index != index_end);
}
static void mask_gesture_lasso_task_cb(
void *__restrict userdata,
const int i,
const ParallelRangeTLS *__restrict UNUSED(tls))
{
LassoMaskData *lasso_data = userdata;
MaskTaskData *data = &lasso_data->task_data;
PBVHNode *node = data->nodes[i];
const PaintMaskFloodMode mode = data->mode;
const float value = data->value;
PBVHVertexIter vi;
bool any_masked = false;
BKE_pbvh_vertex_iter_begin(data->pbvh, node, vi, PBVH_ITER_UNIQUE) {
if (is_effected_lasso(lasso_data, vi.co)) {
if (!any_masked) {
any_masked = true;
sculpt_undo_push_node(data->ob, node, SCULPT_UNDO_MASK);
BKE_pbvh_node_mark_redraw(node);
if (data->multires)
BKE_pbvh_node_mark_normals_update(node);
}
mask_flood_fill_set_elem(vi.mask, mode, value);
}
} BKE_pbvh_vertex_iter_end;
}
static int paint_mask_gesture_lasso_exec(bContext *C, wmOperator *op)
{
int mcords_tot;
const int (*mcords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcords_tot);
if (mcords) {
Depsgraph *depsgraph = CTX_data_depsgraph(C);
float clip_planes[4][4], clip_planes_final[4][4];
BoundBox bb;
Object *ob;
ViewContext vc;
LassoMaskData data;
struct Scene *scene = CTX_data_scene(C);
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
int symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
PBVH *pbvh;
PBVHNode **nodes;
int totnode, symmpass;
bool multires;
PaintMaskFloodMode mode = RNA_enum_get(op->ptr, "mode");
float value = RNA_float_get(op->ptr, "value");
/* Calculations of individual vertices are done in 2D screen space to diminish the amount of
* calculations done. Bounding box PBVH collision is not computed against enclosing rectangle
* of lasso */
ED_view3d_viewcontext_init(C, &vc);
/* lasso data calculations */
data.vc = &vc;
ob = vc.obact;
ED_view3d_ob_project_mat_get(vc.rv3d, ob, data.projviewobjmat);
BLI_lasso_boundbox(&data.rect, mcords, mcords_tot);
data.width = data.rect.xmax - data.rect.xmin;
data.px = BLI_BITMAP_NEW(data.width * (data.rect.ymax - data.rect.ymin), __func__);
BLI_bitmap_draw_2d_poly_v2i_n(
data.rect.xmin, data.rect.ymin, data.rect.xmax, data.rect.ymax,
mcords, mcords_tot,
mask_lasso_px_cb, &data);
ED_view3d_clipping_calc(&bb, clip_planes, vc.ar, vc.obact, &data.rect);
negate_m4(clip_planes);
BKE_sculpt_update_mesh_elements(depsgraph, scene, sd, ob, false, true);
pbvh = ob->sculpt->pbvh;
multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS);
sculpt_undo_push_begin("Mask lasso fill");
for (symmpass = 0; symmpass <= symm; ++symmpass) {
if ((symmpass == 0) ||
(symm & symmpass &&
(symm != 5 || symmpass != 3) &&
(symm != 6 || (symmpass != 3 && symmpass != 5))))
{
int j = 0;
/* flip the planes symmetrically as needed */
for (; j < 4; j++) {
flip_plane(clip_planes_final[j], clip_planes[j], symmpass);
}
data.symmpass = symmpass;
/* gather nodes inside lasso's enclosing rectangle
* (should greatly help with bigger meshes) */
BKE_pbvh_search_gather(pbvh, BKE_pbvh_node_planes_contain_AABB, clip_planes_final, &nodes, &totnode);
data.task_data.ob = ob;
data.task_data.pbvh = pbvh;
data.task_data.nodes = nodes;
data.task_data.multires = multires;
data.task_data.mode = mode;
data.task_data.value = value;
ParallelRangeSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && (totnode > SCULPT_THREADED_LIMIT));
BLI_task_parallel_range(
0, totnode, &data, mask_gesture_lasso_task_cb,
&settings);
if (nodes)
MEM_freeN(nodes);
}
}
if (multires)
multires_mark_as_modified(ob, MULTIRES_COORDS_MODIFIED);
sculpt_undo_push_end();
ED_region_tag_redraw(vc.ar);
MEM_freeN((void *)mcords);
MEM_freeN(data.px);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
return OPERATOR_FINISHED;
}
return OPERATOR_PASS_THROUGH;
}
void PAINT_OT_mask_lasso_gesture(wmOperatorType *ot)
{
ot->name = "Mask Lasso Gesture";
ot->idname = "PAINT_OT_mask_lasso_gesture";
ot->description = "Add mask within the lasso as you move the brush";
ot->invoke = WM_gesture_lasso_invoke;
ot->modal = WM_gesture_lasso_modal;
ot->exec = paint_mask_gesture_lasso_exec;
ot->poll = sculpt_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
WM_operator_properties_gesture_lasso(ot);
RNA_def_enum(ot->srna, "mode", mode_items, PAINT_MASK_FLOOD_VALUE, "Mode", NULL);
RNA_def_float(ot->srna, "value", 1.0, 0, 1.0, "Value",
"Mask level to use when mode is 'Value'; zero means no masking and one is fully masked", 0, 1);
}