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
2021-03-10 15:47:50 +11:00

762 lines
24 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) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup edsculpt
*/
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BLT_translation.h"
#include "DNA_mesh_types.h"
#include "BKE_context.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
#include "BKE_screen.h"
#include "DEG_depsgraph.h"
#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "GPU_matrix.h"
#include "GPU_state.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_space_api.h"
#include "ED_view3d.h"
#include "sculpt_intern.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include <math.h>
#include <stdlib.h>
typedef struct {
const float *ray_start;
bool hit;
float depth;
float edge_length;
struct IsectRayPrecalc isect_precalc;
} SculptDetailRaycastData;
static bool sculpt_and_constant_or_manual_detail_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
return SCULPT_mode_poll(C) && ob->sculpt->bm &&
(sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL));
}
static bool sculpt_and_dynamic_topology_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
return SCULPT_mode_poll(C) && ob->sculpt->bm;
}
static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *UNUSED(op))
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
float size;
float bb_min[3], bb_max[3], center[3], dim[3];
int totnodes;
PBVHNode **nodes;
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnodes);
if (!totnodes) {
return OPERATOR_CANCELLED;
}
for (int i = 0; i < totnodes; i++) {
BKE_pbvh_node_mark_topology_update(nodes[i]);
}
/* Get the bounding box, its center and size. */
BKE_pbvh_bounding_box(ob->sculpt->pbvh, bb_min, bb_max);
add_v3_v3v3(center, bb_min, bb_max);
mul_v3_fl(center, 0.5f);
sub_v3_v3v3(dim, bb_max, bb_min);
size = max_fff(dim[0], dim[1], dim[2]);
/* Update topology size. */
float object_space_constant_detail = 1.0f / (sd->constant_detail * mat4_to_scale(ob->obmat));
BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail);
SCULPT_undo_push_begin(ob, "Dynamic topology flood fill");
SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_COORDS);
while (BKE_pbvh_bmesh_update_topology(
ss->pbvh, PBVH_Collapse | PBVH_Subdivide, center, NULL, size, false, false)) {
for (int i = 0; i < totnodes; i++) {
BKE_pbvh_node_mark_topology_update(nodes[i]);
}
}
MEM_SAFE_FREE(nodes);
SCULPT_undo_push_end();
/* Force rebuild of PBVH for better BB placement. */
SCULPT_pbvh_clear(ob);
/* Redraw. */
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
return OPERATOR_FINISHED;
}
void SCULPT_OT_detail_flood_fill(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Detail Flood Fill";
ot->idname = "SCULPT_OT_detail_flood_fill";
ot->description = "Flood fill the mesh with the selected detail setting";
/* API callbacks. */
ot->exec = sculpt_detail_flood_fill_exec;
ot->poll = sculpt_and_constant_or_manual_detail_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
typedef enum eSculptSampleDetailModeTypes {
SAMPLE_DETAIL_DYNTOPO = 0,
SAMPLE_DETAIL_VOXEL = 1,
} eSculptSampleDetailModeTypes;
static EnumPropertyItem prop_sculpt_sample_detail_mode_types[] = {
{SAMPLE_DETAIL_DYNTOPO, "DYNTOPO", 0, "Dyntopo", "Sample dyntopo detail"},
{SAMPLE_DETAIL_VOXEL, "VOXEL", 0, "Voxel", "Sample mesh voxel size"},
{0, NULL, 0, NULL, NULL},
};
static void sample_detail_voxel(bContext *C, ViewContext *vc, int mx, int my)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Object *ob = vc->obact;
Mesh *mesh = ob->data;
SculptSession *ss = ob->sculpt;
SculptCursorGeometryInfo sgi;
SCULPT_vertex_random_access_ensure(ss);
/* Update the active vertex. */
const float mouse[2] = {mx, my};
SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
/* Average the edge length of the connected edges to the active vertex. */
int active_vertex = SCULPT_active_vertex_get(ss);
const float *active_vertex_co = SCULPT_active_vertex_co_get(ss);
float edge_length = 0.0f;
int tot = 0;
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, active_vertex, ni) {
edge_length += len_v3v3(active_vertex_co, SCULPT_vertex_co_get(ss, ni.index));
tot += 1;
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
if (tot > 0) {
mesh->remesh_voxel_size = edge_length / (float)tot;
}
}
static void sculpt_raycast_detail_cb(PBVHNode *node, void *data_v, float *tmin)
{
if (BKE_pbvh_node_get_tmin(node) < *tmin) {
SculptDetailRaycastData *srd = data_v;
if (BKE_pbvh_bmesh_node_raycast_detail(
node, srd->ray_start, &srd->isect_precalc, &srd->depth, &srd->edge_length)) {
srd->hit = true;
*tmin = srd->depth;
}
}
}
static void sample_detail_dyntopo(bContext *C, ViewContext *vc, ARegion *region, int mx, int my)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
Object *ob = vc->obact;
Brush *brush = BKE_paint_brush(&sd->paint);
SCULPT_stroke_modifiers_check(C, ob, brush);
const float mouse[2] = {mx - region->winrct.xmin, my - region->winrct.ymin};
float ray_start[3], ray_end[3], ray_normal[3];
float depth = SCULPT_raycast_init(vc, mouse, ray_start, ray_end, ray_normal, false);
SculptDetailRaycastData srd;
srd.hit = 0;
srd.ray_start = ray_start;
srd.depth = depth;
srd.edge_length = 0.0f;
isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal);
BKE_pbvh_raycast(ob->sculpt->pbvh, sculpt_raycast_detail_cb, &srd, ray_start, ray_normal, false);
if (srd.hit && srd.edge_length > 0.0f) {
/* Convert edge length to world space detail resolution. */
sd->constant_detail = 1 / (srd.edge_length * mat4_to_scale(ob->obmat));
}
}
static int sample_detail(bContext *C, int mx, int my, int mode)
{
/* Find 3D view to pick from. */
bScreen *screen = CTX_wm_screen(C);
ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_VIEW3D, mx, my);
ARegion *region = (area) ? BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mx, my) : NULL;
if (region == NULL) {
return OPERATOR_CANCELLED;
}
/* Set context to 3D view. */
ScrArea *prev_area = CTX_wm_area(C);
ARegion *prev_region = CTX_wm_region(C);
CTX_wm_area_set(C, area);
CTX_wm_region_set(C, region);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc;
ED_view3d_viewcontext_init(C, &vc, depsgraph);
Object *ob = vc.obact;
if (ob == NULL) {
return OPERATOR_CANCELLED;
}
SculptSession *ss = ob->sculpt;
if (!ss->pbvh) {
return OPERATOR_CANCELLED;
}
/* Pick sample detail. */
switch (mode) {
case SAMPLE_DETAIL_DYNTOPO:
if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) {
CTX_wm_area_set(C, prev_area);
CTX_wm_region_set(C, prev_region);
return OPERATOR_CANCELLED;
}
sample_detail_dyntopo(C, &vc, region, mx, my);
break;
case SAMPLE_DETAIL_VOXEL:
if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {
CTX_wm_area_set(C, prev_area);
CTX_wm_region_set(C, prev_region);
return OPERATOR_CANCELLED;
}
sample_detail_voxel(C, &vc, mx, my);
break;
}
/* Restore context. */
CTX_wm_area_set(C, prev_area);
CTX_wm_region_set(C, prev_region);
return OPERATOR_FINISHED;
}
static int sculpt_sample_detail_size_exec(bContext *C, wmOperator *op)
{
int ss_co[2];
RNA_int_get_array(op->ptr, "location", ss_co);
int mode = RNA_enum_get(op->ptr, "mode");
return sample_detail(C, ss_co[0], ss_co[1], mode);
}
static int sculpt_sample_detail_size_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(e))
{
ED_workspace_status_text(C, TIP_("Click on the mesh to set the detail"));
WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_EYEDROPPER);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int sculpt_sample_detail_size_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
switch (event->type) {
case LEFTMOUSE:
if (event->val == KM_PRESS) {
const int ss_co[2] = {event->x, event->y};
int mode = RNA_enum_get(op->ptr, "mode");
sample_detail(C, ss_co[0], ss_co[1], mode);
RNA_int_set_array(op->ptr, "location", ss_co);
WM_cursor_modal_restore(CTX_wm_window(C));
ED_workspace_status_text(C, NULL);
WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL);
return OPERATOR_FINISHED;
}
break;
case EVT_ESCKEY:
case RIGHTMOUSE: {
WM_cursor_modal_restore(CTX_wm_window(C));
ED_workspace_status_text(C, NULL);
return OPERATOR_CANCELLED;
}
}
return OPERATOR_RUNNING_MODAL;
}
void SCULPT_OT_sample_detail_size(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Sample Detail Size";
ot->idname = "SCULPT_OT_sample_detail_size";
ot->description = "Sample the mesh detail on clicked point";
/* API callbacks. */
ot->invoke = sculpt_sample_detail_size_invoke;
ot->exec = sculpt_sample_detail_size_exec;
ot->modal = sculpt_sample_detail_size_modal;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int_array(ot->srna,
"location",
2,
NULL,
0,
SHRT_MAX,
"Location",
"Screen coordinates of sampling",
0,
SHRT_MAX);
RNA_def_enum(ot->srna,
"mode",
prop_sculpt_sample_detail_mode_types,
SAMPLE_DETAIL_DYNTOPO,
"Detail Mode",
"Target sculpting workflow that is going to use the sampled size");
}
/* Dynamic-topology detail size.
*
* Currently, there are two operators editing the detail size:
* - SCULPT_OT_set_detail_size uses radial control for all methods
* - SCULPT_OT_dyntopo_detail_size_edit shows a triangle grid representation of the detail
* resolution (for constant detail method, falls back to radial control for the remaining methods).
*/
static void set_brush_rc_props(PointerRNA *ptr, const char *prop)
{
char *path = BLI_sprintfN("tool_settings.sculpt.brush.%s", prop);
RNA_string_set(ptr, "data_path_primary", path);
MEM_freeN(path);
}
static void sculpt_detail_size_set_radial_control(bContext *C)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
PointerRNA props_ptr;
wmOperatorType *ot = WM_operatortype_find("WM_OT_radial_control", true);
WM_operator_properties_create_ptr(&props_ptr, ot);
if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) {
set_brush_rc_props(&props_ptr, "constant_detail_resolution");
RNA_string_set(
&props_ptr, "data_path_primary", "tool_settings.sculpt.constant_detail_resolution");
}
else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) {
set_brush_rc_props(&props_ptr, "constant_detail_resolution");
RNA_string_set(&props_ptr, "data_path_primary", "tool_settings.sculpt.detail_percent");
}
else {
set_brush_rc_props(&props_ptr, "detail_size");
RNA_string_set(&props_ptr, "data_path_primary", "tool_settings.sculpt.detail_size");
}
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr);
WM_operator_properties_free(&props_ptr);
}
static int sculpt_set_detail_size_exec(bContext *C, wmOperator *UNUSED(op))
{
sculpt_detail_size_set_radial_control(C);
return OPERATOR_FINISHED;
}
void SCULPT_OT_set_detail_size(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Set Detail Size";
ot->idname = "SCULPT_OT_set_detail_size";
ot->description =
"Set the mesh detail (either relative or constant one, depending on current dyntopo mode)";
/* API callbacks. */
ot->exec = sculpt_set_detail_size_exec;
ot->poll = sculpt_and_dynamic_topology_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* -------------------------------------------------------------------- */
/** \name Dyntopo Detail Size Edit Operator
* \{ */
/* Defines how much the mouse movement will modify the detail size value. */
#define DETAIL_SIZE_DELTA_SPEED 0.08f
#define DETAIL_SIZE_DELTA_ACCURATE_SPEED 0.004f
typedef struct DyntopoDetailSizeEditCustomData {
void *draw_handle;
Object *active_object;
float init_mval[2];
float accurate_mval[2];
float outline_col[4];
bool accurate_mode;
bool sample_mode;
float init_detail_size;
float accurate_detail_size;
float detail_size;
float radius;
float preview_tri[3][3];
float gizmo_mat[4][4];
} DyntopoDetailSizeEditCustomData;
static void dyntopo_detail_size_parallel_lines_draw(uint pos3d,
DyntopoDetailSizeEditCustomData *cd,
const float start_co[3],
const float end_co[3],
bool flip,
const float angle)
{
float object_space_constant_detail = 1.0f /
(cd->detail_size * mat4_to_scale(cd->active_object->obmat));
/* The constant detail represents the maximum edge length allowed before subdividing it. If the
* triangle grid preview is created with this value it will represent an ideal mesh density where
* all edges have the exact maximum length, which never happens in practice. As the minimum edge
* length for dyntopo is 0.4 * max_edge_length, this adjust the detail size to the average
* between max and min edge length so the preview is more accurate. */
object_space_constant_detail *= 0.7f;
const float total_len = len_v3v3(cd->preview_tri[0], cd->preview_tri[1]);
const int tot_lines = (int)(total_len / object_space_constant_detail) + 1;
const float tot_lines_fl = total_len / object_space_constant_detail;
float spacing_disp[3];
sub_v3_v3v3(spacing_disp, end_co, start_co);
normalize_v3(spacing_disp);
float line_disp[3];
rotate_v2_v2fl(line_disp, spacing_disp, DEG2RAD(angle));
mul_v3_fl(spacing_disp, total_len / tot_lines_fl);
immBegin(GPU_PRIM_LINES, (uint)tot_lines * 2);
for (int i = 0; i < tot_lines; i++) {
float line_length;
if (flip) {
line_length = total_len * ((float)i / (float)tot_lines_fl);
}
else {
line_length = total_len * (1.0f - ((float)i / (float)tot_lines_fl));
}
float line_start[3];
copy_v3_v3(line_start, start_co);
madd_v3_v3v3fl(line_start, line_start, spacing_disp, i);
float line_end[3];
madd_v3_v3v3fl(line_end, line_start, line_disp, line_length);
immVertex3fv(pos3d, line_start);
immVertex3fv(pos3d, line_end);
}
immEnd();
}
static void dyntopo_detail_size_edit_draw(const bContext *UNUSED(C),
ARegion *UNUSED(ar),
void *arg)
{
DyntopoDetailSizeEditCustomData *cd = arg;
GPU_blend(GPU_BLEND_ALPHA);
GPU_line_smooth(true);
uint pos3d = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
GPU_matrix_push();
GPU_matrix_mul(cd->gizmo_mat);
/* Draw Cursor */
immUniformColor4fv(cd->outline_col);
GPU_line_width(3.0f);
imm_draw_circle_wire_3d(pos3d, 0, 0, cd->radius, 80);
/* Draw Triangle. */
immUniformColor4f(0.9f, 0.9f, 0.9f, 0.8f);
immBegin(GPU_PRIM_LINES, 6);
immVertex3fv(pos3d, cd->preview_tri[0]);
immVertex3fv(pos3d, cd->preview_tri[1]);
immVertex3fv(pos3d, cd->preview_tri[1]);
immVertex3fv(pos3d, cd->preview_tri[2]);
immVertex3fv(pos3d, cd->preview_tri[2]);
immVertex3fv(pos3d, cd->preview_tri[0]);
immEnd();
/* Draw Grid */
GPU_line_width(1.0f);
dyntopo_detail_size_parallel_lines_draw(
pos3d, cd, cd->preview_tri[0], cd->preview_tri[1], false, 60.0f);
dyntopo_detail_size_parallel_lines_draw(
pos3d, cd, cd->preview_tri[0], cd->preview_tri[1], true, 120.0f);
dyntopo_detail_size_parallel_lines_draw(
pos3d, cd, cd->preview_tri[0], cd->preview_tri[2], false, -60.0f);
immUnbindProgram();
GPU_matrix_pop();
GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
}
static void dyntopo_detail_size_edit_cancel(bContext *C, wmOperator *op)
{
Object *active_object = CTX_data_active_object(C);
SculptSession *ss = active_object->sculpt;
ARegion *region = CTX_wm_region(C);
DyntopoDetailSizeEditCustomData *cd = op->customdata;
ED_region_draw_cb_exit(region->type, cd->draw_handle);
ss->draw_faded_cursor = false;
MEM_freeN(op->customdata);
ED_workspace_status_text(C, NULL);
}
static void dyntopo_detail_size_sample_from_surface(Object *ob,
DyntopoDetailSizeEditCustomData *cd)
{
SculptSession *ss = ob->sculpt;
const int active_vertex = SCULPT_active_vertex_get(ss);
float len_accum = 0;
int num_neighbors = 0;
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, active_vertex, ni) {
len_accum += len_v3v3(SCULPT_vertex_co_get(ss, active_vertex),
SCULPT_vertex_co_get(ss, ni.index));
num_neighbors++;
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
if (num_neighbors > 0) {
const float avg_edge_len = len_accum / num_neighbors;
/* Use 0.7 as the average of min and max dyntopo edge length. */
const float detail_size = 0.7f / (avg_edge_len * mat4_to_scale(cd->active_object->obmat));
cd->detail_size = clamp_f(detail_size, 1.0f, 500.0f);
}
}
static void dyntopo_detail_size_update_from_mouse_delta(DyntopoDetailSizeEditCustomData *cd,
const wmEvent *event)
{
const float mval[2] = {event->mval[0], event->mval[1]};
float detail_size_delta;
if (cd->accurate_mode) {
detail_size_delta = mval[0] - cd->accurate_mval[0];
cd->detail_size = cd->accurate_detail_size +
detail_size_delta * DETAIL_SIZE_DELTA_ACCURATE_SPEED;
}
else {
detail_size_delta = mval[0] - cd->init_mval[0];
cd->detail_size = cd->init_detail_size + detail_size_delta * DETAIL_SIZE_DELTA_SPEED;
}
if (event->type == EVT_LEFTSHIFTKEY && event->val == KM_PRESS) {
cd->accurate_mode = true;
copy_v2_v2(cd->accurate_mval, mval);
cd->accurate_detail_size = cd->detail_size;
}
if (event->type == EVT_LEFTSHIFTKEY && event->val == KM_RELEASE) {
cd->accurate_mode = false;
cd->accurate_detail_size = 0.0f;
}
cd->detail_size = clamp_f(cd->detail_size, 1.0f, 500.0f);
}
static int dyntopo_detail_size_edit_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
Object *active_object = CTX_data_active_object(C);
SculptSession *ss = active_object->sculpt;
ARegion *region = CTX_wm_region(C);
DyntopoDetailSizeEditCustomData *cd = op->customdata;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
/* Cancel modal operator */
if ((event->type == EVT_ESCKEY && event->val == KM_PRESS) ||
(event->type == RIGHTMOUSE && event->val == KM_PRESS)) {
dyntopo_detail_size_edit_cancel(C, op);
ED_region_tag_redraw(region);
return OPERATOR_FINISHED;
}
/* Finish modal operator */
if ((event->type == LEFTMOUSE && event->val == KM_RELEASE) ||
(event->type == EVT_RETKEY && event->val == KM_PRESS) ||
(event->type == EVT_PADENTER && event->val == KM_PRESS)) {
ED_region_draw_cb_exit(region->type, cd->draw_handle);
sd->constant_detail = cd->detail_size;
ss->draw_faded_cursor = false;
MEM_freeN(op->customdata);
ED_region_tag_redraw(region);
ED_workspace_status_text(C, NULL);
return OPERATOR_FINISHED;
}
ED_region_tag_redraw(region);
if (event->type == EVT_LEFTCTRLKEY && event->val == KM_PRESS) {
cd->sample_mode = true;
}
if (event->type == EVT_LEFTCTRLKEY && event->val == KM_RELEASE) {
cd->sample_mode = false;
}
/* Sample mode sets the detail size sampling the average edge length under the surface. */
if (cd->sample_mode) {
dyntopo_detail_size_sample_from_surface(active_object, cd);
return OPERATOR_RUNNING_MODAL;
}
/* Regular mode, changes the detail size by moving the cursor. */
dyntopo_detail_size_update_from_mouse_delta(cd, event);
return OPERATOR_RUNNING_MODAL;
}
static int dyntopo_detail_size_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
/* Fallback to radial control for modes other than SCULPT_DYNTOPO_DETAIL_CONSTANT [same as in
* SCULPT_OT_set_detail_size]. */
if (!(sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL))) {
sculpt_detail_size_set_radial_control(C);
return OPERATOR_FINISHED;
}
/* Special method for SCULPT_DYNTOPO_DETAIL_CONSTANT. */
ARegion *region = CTX_wm_region(C);
Object *active_object = CTX_data_active_object(C);
Brush *brush = BKE_paint_brush(&sd->paint);
DyntopoDetailSizeEditCustomData *cd = MEM_callocN(sizeof(DyntopoDetailSizeEditCustomData),
"Dyntopo Detail Size Edit OP Custom Data");
/* Initial operator Custom Data setup. */
cd->draw_handle = ED_region_draw_cb_activate(
region->type, dyntopo_detail_size_edit_draw, cd, REGION_DRAW_POST_VIEW);
cd->active_object = active_object;
cd->init_mval[0] = event->mval[0];
cd->init_mval[1] = event->mval[1];
cd->detail_size = sd->constant_detail;
cd->init_detail_size = sd->constant_detail;
copy_v4_v4(cd->outline_col, brush->add_col);
op->customdata = cd;
SculptSession *ss = active_object->sculpt;
cd->radius = ss->cursor_radius;
/* Generates the matrix to position the gizmo in the surface of the mesh using the same location
* and orientation as the brush cursor. */
float cursor_trans[4][4], cursor_rot[4][4];
const float z_axis[4] = {0.0f, 0.0f, 1.0f, 0.0f};
float quat[4];
copy_m4_m4(cursor_trans, active_object->obmat);
translate_m4(
cursor_trans, ss->cursor_location[0], ss->cursor_location[1], ss->cursor_location[2]);
float cursor_normal[3];
if (!is_zero_v3(ss->cursor_sampled_normal)) {
copy_v3_v3(cursor_normal, ss->cursor_sampled_normal);
}
else {
copy_v3_v3(cursor_normal, ss->cursor_normal);
}
rotation_between_vecs_to_quat(quat, z_axis, cursor_normal);
quat_to_mat4(cursor_rot, quat);
copy_m4_m4(cd->gizmo_mat, cursor_trans);
mul_m4_m4_post(cd->gizmo_mat, cursor_rot);
/* Initialize the position of the triangle vertices. */
const float y_axis[3] = {0.0f, cd->radius, 0.0f};
for (int i = 0; i < 3; i++) {
zero_v3(cd->preview_tri[i]);
rotate_v2_v2fl(cd->preview_tri[i], y_axis, DEG2RAD(120.0f * i));
}
SCULPT_vertex_random_access_ensure(ss);
WM_event_add_modal_handler(C, op);
ED_region_tag_redraw(region);
ss->draw_faded_cursor = true;
const char *status_str = TIP_(
"Move the mouse to change the dyntopo detail size. LMB: confirm size, ESC/RMB: cancel");
ED_workspace_status_text(C, status_str);
return OPERATOR_RUNNING_MODAL;
}
void SCULPT_OT_dyntopo_detail_size_edit(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Edit Dyntopo Detail Size";
ot->description = "Modify the detail size of dyntopo interactively";
ot->idname = "SCULPT_OT_dyntopo_detail_size_edit";
/* api callbacks */
ot->poll = sculpt_and_dynamic_topology_poll;
ot->invoke = dyntopo_detail_size_edit_invoke;
ot->modal = dyntopo_detail_size_edit_modal;
ot->cancel = dyntopo_detail_size_edit_cancel;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}