762 lines
24 KiB
C
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;
|
|
}
|