Reviewed By: JacquesLucke Differential Revision: https://developer.blender.org/D10707
563 lines
18 KiB
C
563 lines
18 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_hash.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_task.h"
|
|
|
|
#include "DNA_brush_types.h"
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
|
|
#include "BKE_brush.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_mesh_mapping.h"
|
|
#include "BKE_object.h"
|
|
#include "BKE_paint.h"
|
|
#include "BKE_pbvh.h"
|
|
#include "BKE_scene.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_message.h"
|
|
#include "WM_toolsystem.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "ED_object.h"
|
|
#include "ED_screen.h"
|
|
#include "ED_sculpt.h"
|
|
#include "paint_intern.h"
|
|
#include "sculpt_intern.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "bmesh.h"
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
|
|
void SCULPT_neighbor_coords_average_interior(SculptSession *ss, float result[3], int index)
|
|
{
|
|
float avg[3] = {0.0f, 0.0f, 0.0f};
|
|
int total = 0;
|
|
int neighbor_count = 0;
|
|
const bool is_boundary = SCULPT_vertex_is_boundary(ss, index);
|
|
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
|
|
neighbor_count++;
|
|
if (is_boundary) {
|
|
/* Boundary vertices use only other boundary vertices. */
|
|
if (SCULPT_vertex_is_boundary(ss, ni.index)) {
|
|
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index));
|
|
total++;
|
|
}
|
|
}
|
|
else {
|
|
/* Interior vertices use all neighbors. */
|
|
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index));
|
|
total++;
|
|
}
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
|
|
/* Do not modify corner vertices. */
|
|
if (neighbor_count <= 2) {
|
|
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
|
|
return;
|
|
}
|
|
|
|
/* Avoid division by 0 when there are no neighbors. */
|
|
if (total == 0) {
|
|
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
|
|
return;
|
|
}
|
|
|
|
mul_v3_v3fl(result, avg, 1.0f / total);
|
|
}
|
|
|
|
/* For bmesh: Average surrounding verts based on an orthogonality measure.
|
|
* Naturally converges to a quad-like structure. */
|
|
void SCULPT_bmesh_four_neighbor_average(float avg[3], float direction[3], BMVert *v)
|
|
{
|
|
|
|
float avg_co[3] = {0.0f, 0.0f, 0.0f};
|
|
float tot_co = 0.0f;
|
|
|
|
BMIter eiter;
|
|
BMEdge *e;
|
|
|
|
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
|
|
if (BM_edge_is_boundary(e)) {
|
|
copy_v3_v3(avg, v->co);
|
|
return;
|
|
}
|
|
BMVert *v_other = (e->v1 == v) ? e->v2 : e->v1;
|
|
float vec[3];
|
|
sub_v3_v3v3(vec, v_other->co, v->co);
|
|
madd_v3_v3fl(vec, v->no, -dot_v3v3(vec, v->no));
|
|
normalize_v3(vec);
|
|
|
|
/* fac is a measure of how orthogonal or parallel the edge is
|
|
* relative to the direction. */
|
|
float fac = dot_v3v3(vec, direction);
|
|
fac = fac * fac - 0.5f;
|
|
fac *= fac;
|
|
madd_v3_v3fl(avg_co, v_other->co, fac);
|
|
tot_co += fac;
|
|
}
|
|
|
|
/* In case vert has no Edge s. */
|
|
if (tot_co > 0.0f) {
|
|
mul_v3_v3fl(avg, avg_co, 1.0f / tot_co);
|
|
|
|
/* Preserve volume. */
|
|
float vec[3];
|
|
sub_v3_v3(avg, v->co);
|
|
mul_v3_v3fl(vec, v->no, dot_v3v3(avg, v->no));
|
|
sub_v3_v3(avg, vec);
|
|
add_v3_v3(avg, v->co);
|
|
}
|
|
else {
|
|
zero_v3(avg);
|
|
}
|
|
}
|
|
|
|
/* Generic functions for laplacian smoothing. These functions do not take boundary vertices into
|
|
* account. */
|
|
|
|
void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], int index)
|
|
{
|
|
float avg[3] = {0.0f, 0.0f, 0.0f};
|
|
int total = 0;
|
|
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
|
|
add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index));
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
|
|
if (total > 0) {
|
|
mul_v3_v3fl(result, avg, 1.0f / total);
|
|
}
|
|
else {
|
|
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
|
|
}
|
|
}
|
|
|
|
float SCULPT_neighbor_mask_average(SculptSession *ss, int index)
|
|
{
|
|
float avg = 0.0f;
|
|
int total = 0;
|
|
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
|
|
avg += SCULPT_vertex_mask_get(ss, ni.index);
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
|
|
if (total > 0) {
|
|
return avg / total;
|
|
}
|
|
return SCULPT_vertex_mask_get(ss, index);
|
|
}
|
|
|
|
void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], int index)
|
|
{
|
|
float avg[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
int total = 0;
|
|
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
|
|
add_v4_v4(avg, SCULPT_vertex_color_get(ss, ni.index));
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
|
|
if (total > 0) {
|
|
mul_v4_v4fl(result, avg, 1.0f / total);
|
|
}
|
|
else {
|
|
copy_v4_v4(result, SCULPT_vertex_color_get(ss, index));
|
|
}
|
|
}
|
|
|
|
static void do_enhance_details_brush_task_cb_ex(void *__restrict userdata,
|
|
const int n,
|
|
const TaskParallelTLS *__restrict tls)
|
|
{
|
|
SculptThreadedTaskData *data = userdata;
|
|
SculptSession *ss = data->ob->sculpt;
|
|
Sculpt *sd = data->sd;
|
|
const Brush *brush = data->brush;
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
float bstrength = ss->cache->bstrength;
|
|
CLAMP(bstrength, -1.0f, 1.0f);
|
|
|
|
SculptBrushTest test;
|
|
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
|
ss, &test, data->brush->falloff_shape);
|
|
|
|
const int thread_id = BLI_task_parallel_thread_id(tls);
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask ? *vd.mask : 0.0f,
|
|
vd.index,
|
|
thread_id);
|
|
|
|
float disp[3];
|
|
madd_v3_v3v3fl(disp, vd.co, ss->cache->detail_directions[vd.index], fade);
|
|
SCULPT_clip(sd, ss, vd.co, disp);
|
|
|
|
if (vd.mvert) {
|
|
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
|
}
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
static void SCULPT_enhance_details_brush(Sculpt *sd,
|
|
Object *ob,
|
|
PBVHNode **nodes,
|
|
const int totnode)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
SCULPT_vertex_random_access_ensure(ss);
|
|
SCULPT_boundary_info_ensure(ob);
|
|
|
|
if (SCULPT_stroke_is_first_brush_step(ss->cache)) {
|
|
const int totvert = SCULPT_vertex_count_get(ss);
|
|
ss->cache->detail_directions = MEM_malloc_arrayN(
|
|
totvert, 3 * sizeof(float), "details directions");
|
|
|
|
for (int i = 0; i < totvert; i++) {
|
|
float avg[3];
|
|
SCULPT_neighbor_coords_average(ss, avg, i);
|
|
sub_v3_v3v3(ss->cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, i));
|
|
}
|
|
}
|
|
|
|
SculptThreadedTaskData data = {
|
|
.sd = sd,
|
|
.ob = ob,
|
|
.brush = brush,
|
|
.nodes = nodes,
|
|
};
|
|
|
|
TaskParallelSettings settings;
|
|
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
|
BLI_task_parallel_range(0, totnode, &data, do_enhance_details_brush_task_cb_ex, &settings);
|
|
}
|
|
|
|
static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
|
|
const int n,
|
|
const TaskParallelTLS *__restrict tls)
|
|
{
|
|
SculptThreadedTaskData *data = userdata;
|
|
SculptSession *ss = data->ob->sculpt;
|
|
Sculpt *sd = data->sd;
|
|
const Brush *brush = data->brush;
|
|
const bool smooth_mask = data->smooth_mask;
|
|
float bstrength = data->strength;
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
CLAMP(bstrength, 0.0f, 1.0f);
|
|
|
|
SculptBrushTest test;
|
|
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
|
ss, &test, data->brush->falloff_shape);
|
|
|
|
const int thread_id = BLI_task_parallel_thread_id(tls);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(
|
|
ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
smooth_mask ? 0.0f : (vd.mask ? *vd.mask : 0.0f),
|
|
vd.index,
|
|
thread_id);
|
|
if (smooth_mask) {
|
|
float val = SCULPT_neighbor_mask_average(ss, vd.index) - *vd.mask;
|
|
val *= fade * bstrength;
|
|
*vd.mask += val;
|
|
CLAMP(*vd.mask, 0.0f, 1.0f);
|
|
}
|
|
else {
|
|
float avg[3], val[3];
|
|
SCULPT_neighbor_coords_average_interior(ss, avg, vd.index);
|
|
sub_v3_v3v3(val, avg, vd.co);
|
|
madd_v3_v3v3fl(val, vd.co, val, fade);
|
|
SCULPT_clip(sd, ss, vd.co, val);
|
|
}
|
|
if (vd.mvert) {
|
|
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
|
}
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
void SCULPT_smooth(Sculpt *sd,
|
|
Object *ob,
|
|
PBVHNode **nodes,
|
|
const int totnode,
|
|
float bstrength,
|
|
const bool smooth_mask)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
const int max_iterations = 4;
|
|
const float fract = 1.0f / max_iterations;
|
|
PBVHType type = BKE_pbvh_type(ss->pbvh);
|
|
int iteration, count;
|
|
float last;
|
|
|
|
CLAMP(bstrength, 0.0f, 1.0f);
|
|
|
|
count = (int)(bstrength * max_iterations);
|
|
last = max_iterations * (bstrength - count * fract);
|
|
|
|
if (type == PBVH_FACES && !ss->pmap) {
|
|
BLI_assert(!"sculpt smooth: pmap missing");
|
|
return;
|
|
}
|
|
|
|
SCULPT_vertex_random_access_ensure(ss);
|
|
SCULPT_boundary_info_ensure(ob);
|
|
|
|
for (iteration = 0; iteration <= count; iteration++) {
|
|
const float strength = (iteration != count) ? 1.0f : last;
|
|
|
|
SculptThreadedTaskData data = {
|
|
.sd = sd,
|
|
.ob = ob,
|
|
.brush = brush,
|
|
.nodes = nodes,
|
|
.smooth_mask = smooth_mask,
|
|
.strength = strength,
|
|
};
|
|
|
|
TaskParallelSettings settings;
|
|
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
|
BLI_task_parallel_range(0, totnode, &data, do_smooth_brush_task_cb_ex, &settings);
|
|
}
|
|
}
|
|
|
|
void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
if (ss->cache->bstrength <= 0.0f) {
|
|
/* Invert mode, intensify details. */
|
|
SCULPT_enhance_details_brush(sd, ob, nodes, totnode);
|
|
}
|
|
else {
|
|
/* Regular mode, smooth. */
|
|
SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, false);
|
|
}
|
|
}
|
|
|
|
/* HC Smooth Algorithm. */
|
|
/* From: Improved Laplacian Smoothing of Noisy Surface Meshes */
|
|
|
|
void SCULPT_surface_smooth_laplacian_step(SculptSession *ss,
|
|
float *disp,
|
|
const float co[3],
|
|
float (*laplacian_disp)[3],
|
|
const int v_index,
|
|
const float origco[3],
|
|
const float alpha)
|
|
{
|
|
float laplacian_smooth_co[3];
|
|
float weigthed_o[3], weigthed_q[3], d[3];
|
|
SCULPT_neighbor_coords_average(ss, laplacian_smooth_co, v_index);
|
|
|
|
mul_v3_v3fl(weigthed_o, origco, alpha);
|
|
mul_v3_v3fl(weigthed_q, co, 1.0f - alpha);
|
|
add_v3_v3v3(d, weigthed_o, weigthed_q);
|
|
sub_v3_v3v3(laplacian_disp[v_index], laplacian_smooth_co, d);
|
|
|
|
sub_v3_v3v3(disp, laplacian_smooth_co, co);
|
|
}
|
|
|
|
void SCULPT_surface_smooth_displace_step(SculptSession *ss,
|
|
float *co,
|
|
float (*laplacian_disp)[3],
|
|
const int v_index,
|
|
const float beta,
|
|
const float fade)
|
|
{
|
|
float b_avg[3] = {0.0f, 0.0f, 0.0f};
|
|
float b_current_vertex[3];
|
|
int total = 0;
|
|
SculptVertexNeighborIter ni;
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v_index, ni) {
|
|
add_v3_v3(b_avg, laplacian_disp[ni.index]);
|
|
total++;
|
|
}
|
|
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
|
|
if (total > 0) {
|
|
mul_v3_v3fl(b_current_vertex, b_avg, (1.0f - beta) / total);
|
|
madd_v3_v3fl(b_current_vertex, laplacian_disp[v_index], beta);
|
|
mul_v3_fl(b_current_vertex, clamp_f(fade, 0.0f, 1.0f));
|
|
sub_v3_v3(co, b_current_vertex);
|
|
}
|
|
}
|
|
|
|
static void SCULPT_do_surface_smooth_brush_laplacian_task_cb_ex(
|
|
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls)
|
|
{
|
|
SculptThreadedTaskData *data = userdata;
|
|
SculptSession *ss = data->ob->sculpt;
|
|
const Brush *brush = data->brush;
|
|
const float bstrength = ss->cache->bstrength;
|
|
float alpha = brush->surface_smooth_shape_preservation;
|
|
|
|
PBVHVertexIter vd;
|
|
SculptOrigVertData orig_data;
|
|
|
|
SculptBrushTest test;
|
|
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
|
ss, &test, data->brush->falloff_shape);
|
|
const int thread_id = BLI_task_parallel_thread_id(tls);
|
|
|
|
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
|
SCULPT_orig_vert_data_update(&orig_data, &vd);
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask ? *vd.mask : 0.0f,
|
|
vd.index,
|
|
thread_id);
|
|
|
|
float disp[3];
|
|
SCULPT_surface_smooth_laplacian_step(
|
|
ss, disp, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.index, orig_data.co, alpha);
|
|
madd_v3_v3fl(vd.co, disp, clamp_f(fade, 0.0f, 1.0f));
|
|
if (vd.mvert) {
|
|
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
|
|
}
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
static void SCULPT_do_surface_smooth_brush_displace_task_cb_ex(
|
|
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls)
|
|
{
|
|
SculptThreadedTaskData *data = userdata;
|
|
SculptSession *ss = data->ob->sculpt;
|
|
const Brush *brush = data->brush;
|
|
const float bstrength = ss->cache->bstrength;
|
|
const float beta = brush->surface_smooth_current_vertex;
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
SculptBrushTest test;
|
|
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
|
ss, &test, data->brush->falloff_shape);
|
|
const int thread_id = BLI_task_parallel_thread_id(tls);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask ? *vd.mask : 0.0f,
|
|
vd.index,
|
|
thread_id);
|
|
SCULPT_surface_smooth_displace_step(
|
|
ss, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.index, beta, fade);
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
void SCULPT_do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
|
{
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
if (SCULPT_stroke_is_first_brush_step(ss->cache)) {
|
|
BLI_assert(ss->cache->surface_smooth_laplacian_disp == NULL);
|
|
ss->cache->surface_smooth_laplacian_disp = MEM_callocN(
|
|
sizeof(float[3]) * SCULPT_vertex_count_get(ss), "HC smooth laplacian b");
|
|
}
|
|
|
|
/* Threaded loop over nodes. */
|
|
SculptThreadedTaskData data = {
|
|
.sd = sd,
|
|
.ob = ob,
|
|
.brush = brush,
|
|
.nodes = nodes,
|
|
};
|
|
|
|
TaskParallelSettings settings;
|
|
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
|
for (int i = 0; i < brush->surface_smooth_iterations; i++) {
|
|
BLI_task_parallel_range(
|
|
0, totnode, &data, SCULPT_do_surface_smooth_brush_laplacian_task_cb_ex, &settings);
|
|
BLI_task_parallel_range(
|
|
0, totnode, &data, SCULPT_do_surface_smooth_brush_displace_task_cb_ex, &settings);
|
|
}
|
|
}
|