Sculpt: Dyntopo detail size edit operator

This introduces a new operator to edit the detail size of constant
detail mode in dyntopo. The way this operator works and the
functionality it provides is similar to the "Voxel size edit" operator
for the voxel remesher.

It also includes a sample mode. When pressing Ctrl, the detail size
will be sampled from the surface under the cursor, updating the
preview in real time. This allows quick resolution changes without
using the operator multiple times.

The operator is set to Shift + D, replacing the old way to change
the constant detail size of dyntopo. Shift + R will remain available to
be enabled when the voxel remesher works with dyntopo. Deciding
if both detail sizes can be unified needs a separate discussion as the
new dyntopo can work with detail sizes in parts of the mesh that can
easily crash the remesher.

The structure of these operators is similar, but the data they control,
ranges, drawing and setup functions are completely different, making it
hard to merge them into one.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D9355
This commit is contained in:
Pablo Dobarro 2020-10-29 22:32:22 +01:00
parent 39be996452
commit 99a7c917ea
Notes: blender-bot 2023-02-21 17:59:30 +01:00
Referenced by commit 7f9b6b1dc9, Sculpt: 'Unifiy' Dyntopo Detail Size operators
Referenced by issue #83828, Dyntopo Relative Detail Size not in keymap
4 changed files with 335 additions and 1 deletions

View File

@ -4400,7 +4400,7 @@ def km_sculpt(params):
{"properties": [("use_normals", True), ("keep_previous_mask", True), ("invert", False), ("smooth_iterations", 0), ("create_face_set", False)]}),
# Dynamic topology
("sculpt.dynamic_topology_toggle", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
("sculpt.set_detail_size", {"type": 'D', "value": 'PRESS', "shift": True}, None),
("sculpt.dyntopo_detail_size_edit", {"type": 'D', "value": 'PRESS', "shift": True}, None),
# Remesh
("object.voxel_remesh", {"type": 'R', "value": 'PRESS', "ctrl": True}, None),
("object.voxel_size_edit", {"type": 'R', "value": 'PRESS', "shift": True}, None),

View File

@ -505,6 +505,9 @@ typedef struct SculptSession {
int active_face_index;
int active_grid_index;
/* When active, the cursor draws with faded colors, indicating that there is an action enabled.
*/
bool draw_faded_cursor;
float cursor_radius;
float cursor_location[3];
float cursor_normal[3];

View File

@ -1310,6 +1310,11 @@ static bool paint_cursor_context_init(bContext *C,
Object *active_object = pcontext->vc.obact;
pcontext->ss = active_object ? active_object->sculpt : NULL;
if (pcontext->ss && pcontext->ss->draw_faded_cursor) {
pcontext->outline_alpha = 0.3f;
copy_v3_fl(pcontext->outline_col, 0.8f);
}
pcontext->is_stroke_active = pcontext->ups->stroke_active;
return true;

View File

@ -76,6 +76,11 @@
#include "IMB_colormanagement.h"
#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "GPU_matrix.h"
#include "GPU_state.h"
#include "WM_api.h"
#include "WM_message.h"
#include "WM_toolsystem.h"
@ -84,6 +89,7 @@
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_space_api.h"
#include "ED_view3d.h"
#include "paint_intern.h"
#include "sculpt_intern.h"
@ -9236,6 +9242,325 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot)
1.0f);
}
/* -------------------------------------------------------------------- */
/** \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);
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 *ar = CTX_wm_region(C);
DyntopoDetailSizeEditCustomData *cd = op->customdata;
ED_region_draw_cb_exit(ar->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 *ar = 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(ar);
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(ar->type, cd->draw_handle);
sd->constant_detail = cd->detail_size;
ss->draw_faded_cursor = false;
MEM_freeN(op->customdata);
ED_region_tag_redraw(ar);
ED_workspace_status_text(C, NULL);
return OPERATOR_FINISHED;
}
ED_region_tag_redraw(ar);
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)
{
ARegion *ar = CTX_wm_region(C);
Object *active_object = CTX_data_active_object(C);
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
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(
ar->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]);
rotation_between_vecs_to_quat(quat, z_axis, ss->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);
/* Initize 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(ar);
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;
}
static bool dyntopo_detail_size_edit_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);
}
static void SCULPT_OT_dyntopo_detail_size_edit(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Edit Dyntopo Detail Size";
ot->description = "Modify the constant detail size of dyntopo interactively";
ot->idname = "SCULPT_OT_dyntopo_detail_size_edit";
/* api callbacks */
ot->poll = dyntopo_detail_size_edit_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;
}
void ED_operatortypes_sculpt(void)
{
WM_operatortype_append(SCULPT_OT_brush_stroke);
@ -9269,4 +9594,5 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_vertex_to_loop_colors);
WM_operatortype_append(SCULPT_OT_color_filter);
WM_operatortype_append(SCULPT_OT_mask_by_color);
WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit);
}