Sculpt: Cloth brush

This brush has a simple physics solver that helps when sculpting cloth.

- The mass and the damping properties of the simulation are properties of the brush.
- It has two additional radius control to limit the influence and falloff of the simulation.
- Masked vertices are pinned in the simulation, and it applies the sculpt gravity directly in the solver.
- The Cloth Brush has 7 deformation modes with 2 falloff types (radial and plane).

The brush can create the constraints only on the required PBVH nodes, so the simulation is isolated on high poly meshes. As long
as the brush size is not too big it should be possible to keep it real time.

Known issues:
- The way constraints are created is extremely basic and it creates repeated constraints. Maybe there is another way to create fewer constraints while keeping the simulation quality decent. This part can also be multithreaded. (As it is it works ok, but it could be better)

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D6715
This commit is contained in:
Pablo Dobarro 2020-02-28 14:40:40 +01:00
parent 793135e190
commit 4a373afa5f
14 changed files with 1144 additions and 35 deletions

View File

@ -626,6 +626,18 @@ def brush_settings(layout, context, brush, popover=False):
layout.prop(brush, "pose_ik_segments")
layout.prop(brush, "use_pose_ik_anchored")
layout.separator()
if brush.sculpt_tool == 'CLOTH':
layout.separator()
layout.prop(brush, "cloth_sim_limit")
layout.prop(brush, "cloth_sim_falloff")
layout.separator()
layout.prop(brush, "cloth_deform_type")
layout.prop(brush, "cloth_force_falloff_type")
layout.separator()
layout.prop(brush, "cloth_mass")
layout.prop(brush, "cloth_damping")
layout.separator()
if brush.sculpt_tool == 'SCRAPE':
row = layout.row()

View File

@ -245,6 +245,31 @@ typedef struct SculptPoseIKChain {
int tot_segments;
} SculptPoseIKChain;
/* Cloth Brush */
typedef struct SculptClothLengthConstraint {
int v1;
int v2;
float length;
} SculptClothLengthConstraint;
typedef struct SculptClothSimulation {
SculptClothLengthConstraint *length_constraints;
int tot_length_constraints;
int capacity_length_constraints;
float *length_constraint_tweak;
float mass;
float damping;
float (*acceleration)[3];
float (*pos)[3];
float (*init_pos)[3];
float (*prev_pos)[3];
} SculptClothSimulation;
/* Session data (mode-specific) */
typedef struct SculptSession {
@ -298,6 +323,7 @@ typedef struct SculptSession {
float cursor_radius;
float cursor_location[3];
float cursor_normal[3];
float cursor_sampled_normal[3];
float cursor_view_normal[3];
/* TODO(jbakker): Replace rv3d adn v3d with ViewContext */

View File

@ -1014,6 +1014,14 @@ void BKE_brush_sculpt_reset(Brush *br)
br->flag &= ~BRUSH_SPACE;
br->flag &= ~BRUSH_SPACE_ATTEN;
break;
case SCULPT_TOOL_CLOTH:
br->cloth_mass = 1.0f;
br->cloth_damping = 0.01f;
br->cloth_sim_limit = 2.5f;
br->cloth_sim_falloff = 0.75f;
br->cloth_deform_type = BRUSH_CLOTH_DEFORM_DRAG;
br->flag &= ~(BRUSH_ALPHA_PRESSURE | BRUSH_SIZE_PRESSURE);
break;
default:
break;
}
@ -1081,6 +1089,15 @@ void BKE_brush_sculpt_reset(Brush *br)
br->sub_col[1] = 0.750000;
br->sub_col[2] = 0.750000;
break;
case SCULPT_TOOL_CLOTH:
br->add_col[0] = 1.0f;
br->add_col[1] = 0.5f;
br->add_col[2] = 0.1f;
br->sub_col[0] = 1.0f;
br->sub_col[1] = 0.5f;
br->sub_col[2] = 0.1f;
break;
default:
break;
}

View File

@ -59,6 +59,7 @@ set(SRC
paint_vertex_weight_ops.c
paint_vertex_weight_utils.c
sculpt.c
sculpt_cloth.c
sculpt_undo.c
sculpt_uv.c

View File

@ -1526,11 +1526,21 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
immUniformColor3fvAlpha(outline_col, outline_alpha);
GPU_line_width(2.0f);
imm_draw_circle_wire_3d(pos, 0, 0, rds, 80);
GPU_line_width(1.0f);
immUniformColor3fvAlpha(outline_col, outline_alpha * 0.5f);
imm_draw_circle_wire_3d(pos, 0, 0, rds * clamp_f(brush->alpha, 0.0f, 1.0f), 80);
GPU_matrix_pop();
/* Cloth brush simulation areas. */
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
GPU_matrix_push();
const float white[3] = {1.0f, 1.0f, 1.0f};
SCULPT_cloth_simulation_limits_draw(
pos, brush, vc.obact->obmat, gi.location, gi.normal, rds, 1.0f, white, 0.25f);
GPU_matrix_pop();
}
/* Update and draw dynamic mesh preview lines. */
GPU_matrix_push();
GPU_matrix_mul(vc.obact->obmat);
@ -1620,6 +1630,48 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
GPU_matrix_pop_projection();
}
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH && !ss->cache->first_time) {
GPU_matrix_push_projection();
ED_view3d_draw_setup_view(CTX_wm_window(C),
CTX_data_depsgraph_pointer(C),
CTX_data_scene(C),
ar,
CTX_wm_view3d(C),
NULL,
NULL,
NULL);
/* Plane falloff preview */
if (brush->cloth_force_falloff_type == BRUSH_CLOTH_FORCE_FALLOFF_PLANE) {
GPU_matrix_push();
GPU_matrix_mul(vc.obact->obmat);
SCULPT_cloth_plane_falloff_preview_draw(pos, ss, outline_col, outline_alpha);
GPU_matrix_pop();
}
/* Display the simulation limits if sculpting outside them. */
/* This does not makes much sense of plane fallof as the fallof is infinte. */
else if (brush->cloth_force_falloff_type == BRUSH_CLOTH_FORCE_FALLOFF_RADIAL) {
if (len_v3v3(ss->cache->true_location, ss->cache->true_initial_location) >
ss->cache->radius * (1.0f + brush->cloth_sim_limit)) {
const float red[3] = {1.0f, 0.2f, 0.2f};
GPU_matrix_push();
SCULPT_cloth_simulation_limits_draw(pos,
brush,
vc.obact->obmat,
ss->cache->true_initial_location,
ss->cache->true_initial_normal,
ss->cache->radius,
2.0f,
red,
0.8f);
GPU_matrix_pop();
}
}
GPU_matrix_pop_projection();
}
wmWindowViewport(win);
}
}

View File

@ -228,6 +228,10 @@ static bool paint_tool_require_location(Brush *brush, ePaintMode mode)
SCULPT_TOOL_THUMB)) {
return false;
}
else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH &&
brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
return false;
}
else {
return true;
}
@ -259,6 +263,7 @@ static bool paint_tool_require_inbetween_mouse_events(Brush *brush, ePaintMode m
SCULPT_TOOL_THUMB,
SCULPT_TOOL_SNAKE_HOOK,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_CLOTH,
SCULPT_TOOL_POSE)) {
return false;
}
@ -999,6 +1004,10 @@ bool paint_space_stroke_enabled(Brush *br, ePaintMode mode)
static bool sculpt_is_grab_tool(Brush *br)
{
if (br->sculpt_tool == SCULPT_TOOL_CLOTH && br->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
return true;
}
return ELEM(br->sculpt_tool,
SCULPT_TOOL_GRAB,
SCULPT_TOOL_ELASTIC_DEFORM,

View File

@ -112,7 +112,7 @@ static void sculpt_vertex_random_access_init(SculptSession *ss)
}
}
static int sculpt_vertex_count_get(SculptSession *ss)
int sculpt_vertex_count_get(SculptSession *ss)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES:
@ -171,7 +171,7 @@ static void sculpt_vertex_normal_get(SculptSession *ss, int index, float no[3])
}
}
static float sculpt_vertex_mask_get(SculptSession *ss, int index)
float sculpt_vertex_mask_get(SculptSession *ss, int index)
{
BMVert *v;
float *mask;
@ -220,22 +220,6 @@ static void sculpt_active_vertex_normal_get(SculptSession *ss, float normal[3])
#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
typedef struct SculptVertexNeighborIter {
/* Storage */
int *neighbors;
int size;
int capacity;
int neighbors_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY];
/* Internal iterator. */
int num_duplicates;
int i;
/* Public */
int index;
bool is_duplicate;
} SculptVertexNeighborIter;
static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, int neighbor_index)
{
for (int i = 0; i < iter->size; i++) {
@ -342,10 +326,10 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss,
}
}
static void sculpt_vertex_neighbors_get(SculptSession *ss,
const int index,
const bool include_duplicates,
SculptVertexNeighborIter *iter)
void sculpt_vertex_neighbors_get(SculptSession *ss,
const int index,
const bool include_duplicates,
SculptVertexNeighborIter *iter)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES:
@ -630,7 +614,8 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
static bool sculpt_tool_is_proxy_used(const char sculpt_tool)
{
return ELEM(sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_POSE);
return ELEM(
sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_POSE, SCULPT_TOOL_CLOTH);
}
static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush)
@ -652,6 +637,7 @@ static int sculpt_brush_needs_normal(const SculptSession *ss, const Brush *brush
SCULPT_TOOL_CREASE,
SCULPT_TOOL_DRAW,
SCULPT_TOOL_DRAW_SHARP,
SCULPT_TOOL_CLOTH,
SCULPT_TOOL_LAYER,
SCULPT_TOOL_NUDGE,
SCULPT_TOOL_ROTATE,
@ -1820,6 +1806,17 @@ static float brush_strength(const Sculpt *sd,
case SCULPT_TOOL_DRAW_SHARP:
case SCULPT_TOOL_LAYER:
return alpha * flip * pressure * overlap * feather;
case SCULPT_TOOL_CLOTH:
/* Ex/pand is more sensible to strength as it keeps expanding the cloth when sculpting over
* the same vertices. */
if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) {
return 0.1f * alpha * flip * pressure * overlap * feather;
}
else {
/* Multiply by 10 by default to get a larger range of strength depending on the size of the
* brush and object. */
return 10.0f * alpha * flip * pressure * overlap * feather;
}
case SCULPT_TOOL_SLIDE_RELAX:
return alpha * pressure * overlap * feather * 2.0f;
case SCULPT_TOOL_CLAY_STRIPS:
@ -4416,6 +4413,94 @@ static void sculpt_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Br
MEM_SAFE_FREE(nodes);
}
void SCULPT_calc_brush_plane(
Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3])
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
zero_v3(r_area_co);
zero_v3(r_area_no);
if (ss->cache->mirror_symmetry_pass == 0 && ss->cache->radial_symmetry_pass == 0 &&
ss->cache->tile_pass == 0 &&
(ss->cache->first_time || !(brush->flag & BRUSH_ORIGINAL_PLANE) ||
!(brush->flag & BRUSH_ORIGINAL_NORMAL))) {
switch (brush->sculpt_plane) {
case SCULPT_DISP_DIR_VIEW:
copy_v3_v3(r_area_no, ss->cache->true_view_normal);
break;
case SCULPT_DISP_DIR_X:
ARRAY_SET_ITEMS(r_area_no, 1.0f, 0.0f, 0.0f);
break;
case SCULPT_DISP_DIR_Y:
ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f);
break;
case SCULPT_DISP_DIR_Z:
ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f);
break;
case SCULPT_DISP_DIR_AREA:
calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co);
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal);
normalize_v3(r_area_no);
}
break;
default:
break;
}
/* For flatten center. */
/* fFlatten center has not been calculated yet if we are not using the area normal. */
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) {
calc_area_center(sd, ob, nodes, totnode, r_area_co);
}
/* For area normal. */
if ((!ss->cache->first_time) && (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
copy_v3_v3(r_area_no, ss->cache->sculpt_normal);
}
else {
copy_v3_v3(ss->cache->sculpt_normal, r_area_no);
}
/* For flatten center. */
if ((!ss->cache->first_time) && (brush->flag & BRUSH_ORIGINAL_PLANE)) {
copy_v3_v3(r_area_co, ss->cache->last_center);
}
else {
copy_v3_v3(ss->cache->last_center, r_area_co);
}
}
else {
/* For area normal. */
copy_v3_v3(r_area_no, ss->cache->sculpt_normal);
/* For flatten center. */
copy_v3_v3(r_area_co, ss->cache->last_center);
/* For area normal. */
flip_v3(r_area_no, ss->cache->mirror_symmetry_pass);
/* For flatten center. */
flip_v3(r_area_co, ss->cache->mirror_symmetry_pass);
/* For area normal. */
mul_m4_v3(ss->cache->symm_rot_mat, r_area_no);
/* For flatten center. */
mul_m4_v3(ss->cache->symm_rot_mat, r_area_co);
/* Shift the plane for the current tile. */
add_v3_v3(r_area_co, ss->cache->plane_offset);
}
}
static void do_nudge_brush_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
@ -5016,7 +5101,7 @@ static void do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totno
float displace;
float temp[3];
calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
displace = radius * offset;
@ -5175,7 +5260,7 @@ static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
float area_co[3];
float temp[3];
calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
SculptThreadedTaskData sample_data = {
.sd = NULL,
@ -5414,7 +5499,7 @@ static void do_multiplane_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes,
float temp[3];
float mat[4][4];
calc_sculpt_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
calc_area_normal(sd, ob, nodes, totnode, area_no);
@ -5645,7 +5730,7 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t
float scale[4][4];
float tmat[4][4];
calc_sculpt_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
calc_area_normal(sd, ob, nodes, totnode, area_no);
@ -5769,7 +5854,7 @@ static void do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
float temp[3];
calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
displace = radius * offset;
@ -5861,7 +5946,7 @@ static void do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
float temp[3];
calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co);
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
displace = -radius * offset;
@ -5996,7 +6081,7 @@ static void do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int to
float scale[4][4];
float tmat[4][4];
calc_sculpt_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
calc_area_normal(sd, ob, nodes, totnode, area_no);
@ -6275,6 +6360,17 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
if (ELEM(brush->sculpt_tool, SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_POSE)) {
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
}
else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
SculptSearchSphereData data = {
.ss = ss,
.sd = sd,
.radius_squared = SQUARE(ss->cache->radius * (1.0 + brush->cloth_sim_limit)),
.original = false,
.ignore_fully_masked = false,
.center = ss->cache->initial_location,
};
BKE_pbvh_search_gather(ss->pbvh, sculpt_search_sphere_cb, &data, &nodes, &totnode);
}
else {
const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true :
ss->cache->original;
@ -6407,6 +6503,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
case SCULPT_TOOL_SLIDE_RELAX:
do_slide_relax_brush(sd, ob, nodes, totnode);
break;
case SCULPT_TOOL_CLOTH:
SCULPT_do_cloth_brush(sd, ob, nodes, totnode);
break;
}
if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) &&
@ -6428,7 +6527,8 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor);
}
if (ss->cache->supports_gravity) {
/* The cloth brush adds the gravity as a regular force and it is processed in the solver. */
if (ss->cache->supports_gravity && brush->sculpt_tool != SCULPT_TOOL_CLOTH) {
do_gravity(sd, ob, nodes, totnode, sd->gravity_factor);
}
@ -6663,6 +6763,9 @@ void sculpt_cache_calc_brushdata_symm(StrokeCache *cache,
flip_v3_v3(cache->grab_delta_symmetry, cache->grab_delta, symm);
flip_v3_v3(cache->view_normal, cache->true_view_normal, symm);
flip_v3_v3(cache->initial_location, cache->true_initial_location, symm);
flip_v3_v3(cache->initial_normal, cache->true_initial_normal, symm);
/* XXX This reduces the length of the grab delta if it approaches the line of symmetry
* XXX However, a different approach appears to be needed. */
#if 0
@ -6925,6 +7028,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
return "Multi-plane Scrape Brush";
case SCULPT_TOOL_SLIDE_RELAX:
return "Slide/Relax Brush";
case SCULPT_TOOL_CLOTH:
return "Cloth Brush";
}
return "Sculpting";
@ -6942,6 +7047,11 @@ void sculpt_cache_free(StrokeCache *cache)
if (cache->pose_ik_chain) {
sculpt_pose_ik_chain_free(cache->pose_ik_chain);
}
if (cache->cloth_sim) {
SCULPT_cloth_simulation_free(cache->cloth_sim);
}
MEM_freeN(cache);
}
@ -7018,6 +7128,12 @@ static void sculpt_update_cache_invariants(
zero_v2(cache->initial_mouse);
}
copy_v3_v3(cache->initial_location, ss->cursor_location);
copy_v3_v3(cache->true_initial_location, ss->cursor_location);
copy_v3_v3(cache->initial_normal, ss->cursor_normal);
copy_v3_v3(cache->true_initial_normal, ss->cursor_normal);
mode = RNA_enum_get(op->ptr, "mode");
cache->invert = mode == BRUSH_STROKE_INVERT;
cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH;
@ -7193,6 +7309,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
if (ELEM(tool,
SCULPT_TOOL_GRAB,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_CLOTH,
SCULPT_TOOL_NUDGE,
SCULPT_TOOL_CLAY_STRIPS,
SCULPT_TOOL_PINCH,
@ -7234,6 +7351,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
break;
case SCULPT_TOOL_CLAY_STRIPS:
case SCULPT_TOOL_PINCH:
case SCULPT_TOOL_CLOTH:
case SCULPT_TOOL_MULTIPLANE_SCRAPE:
case SCULPT_TOOL_CLAY_THUMB:
case SCULPT_TOOL_NUDGE:
@ -7448,7 +7566,8 @@ static bool sculpt_needs_connectivity_info(const Brush *brush, SculptSession *ss
(brush->sculpt_tool == SCULPT_TOOL_SMOOTH) || (brush->autosmooth_factor > 0) ||
((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) ||
(brush->sculpt_tool == SCULPT_TOOL_POSE) ||
(brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX));
(brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) ||
(brush->sculpt_tool == SCULPT_TOOL_CLOTH));
}
static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush)
@ -7687,6 +7806,7 @@ bool sculpt_cursor_geometry_info_update(bContext *C,
/* Calculate the sampled normal. */
if (sculpt_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, sampled_normal)) {
copy_v3_v3(out->normal, sampled_normal);
copy_v3_v3(ss->cursor_sampled_normal, sampled_normal);
}
else {
/* Use face normal when there are no vertices to sample inside the cursor radius. */
@ -7806,6 +7926,10 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op)
need_mask = true;
}
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
need_mask = true;
}
view3d_operator_needs_opengl(C);
sculpt_brush_init_tex(scene, sd, ss);
@ -7820,7 +7944,8 @@ static void sculpt_restore_mesh(Sculpt *sd, Object *ob)
/* Restore the mesh before continuing with anchored stroke. */
if ((brush->flag & BRUSH_ANCHORED) ||
((brush->sculpt_tool == SCULPT_TOOL_GRAB ||
brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) &&
brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM ||
brush->sculpt_tool == SCULPT_TOOL_CLOTH) &&
BKE_brush_use_size_pressure(brush)) ||
(brush->flag & BRUSH_DRAG_DOT)) {
paint_mesh_restore_co(sd, ob);

View File

@ -0,0 +1,679 @@
/*
* 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_math.h"
#include "BLI_blenlib.h"
#include "BLI_dial_2d.h"
#include "BLI_gsqueue.h"
#include "BLI_ghash.h"
#include "BLI_hash.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "DNA_customdata_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_brush_types.h"
#include "BKE_brush.h"
#include "BKE_ccg.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_image.h"
#include "BKE_kelvinlet.h"
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_mesh_mirror.h"
#include "BKE_modifier.h"
#include "BKE_multires.h"
#include "BKE_node.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_particle.h"
#include "BKE_pbvh.h"
#include "BKE_pointcache.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_screen.h"
#include "BKE_subdiv_ccg.h"
#include "BKE_subsurf.h"
#include "DEG_depsgraph.h"
#include "WM_api.h"
#include "WM_types.h"
#include "WM_message.h"
#include "WM_toolsystem.h"
#include "ED_sculpt.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_view3d.h"
#include "paint_intern.h"
#include "sculpt_intern.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "GPU_draw.h"
#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "GPU_matrix.h"
#include "GPU_state.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "bmesh.h"
#include "bmesh_tools.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#define CLOTH_LENGTH_CONSTRAINTS_BLOCK 100000
#define CLOTH_SIMULATION_ITERATIONS 5
#define CLOTH_MAX_CONSTRAINTS_PER_VERTEX 1024
#define CLOTH_SIMULATION_TIME_STEP 0.01f
static void cloth_brush_add_length_constraint(SculptSession *ss, const int v1, const int v2)
{
SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
cloth_sim->length_constraints[cloth_sim->tot_length_constraints].v1 = v1;
cloth_sim->length_constraints[cloth_sim->tot_length_constraints].v2 = v2;
cloth_sim->length_constraints[cloth_sim->tot_length_constraints].length = len_v3v3(
sculpt_vertex_co_get(ss, v1), sculpt_vertex_co_get(ss, v2));
cloth_sim->tot_length_constraints++;
/* Reallocation if the array capacity is exceeded. */
if (cloth_sim->tot_length_constraints >= cloth_sim->capacity_length_constraints) {
cloth_sim->capacity_length_constraints += CLOTH_LENGTH_CONSTRAINTS_BLOCK;
cloth_sim->length_constraints = MEM_reallocN_id(cloth_sim->length_constraints,
cloth_sim->capacity_length_constraints *
sizeof(SculptClothLengthConstraint),
"length constraints");
}
}
static void do_cloth_brush_build_constraints_task_cb_ex(
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
PBVHVertexIter vd;
const float radius = ss->cache->initial_radius;
const float limit = radius + (radius * data->brush->cloth_sim_limit);
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
if (len_squared_v3v3(vd.co, ss->cache->initial_location) < limit * limit) {
SculptVertexNeighborIter ni;
int build_indices[CLOTH_MAX_CONSTRAINTS_PER_VERTEX];
int tot_indices = 0;
build_indices[tot_indices] = vd.index;
tot_indices++;
sculpt_vertex_neighbors_iter_begin(ss, vd.index, ni)
{
build_indices[tot_indices] = ni.index;
tot_indices++;
}
sculpt_vertex_neighbors_iter_end(ni);
/* As we don't know the order of the neighbor vertices, we create all possible combinations
* between the neighbor and the original vertex as length constraints. */
/* This results on a pattern that contains structural, shear and bending constraints for all
* vertices, but constraints are repeated taking more memory than necessary. */
for (int c_i = 0; c_i < tot_indices; c_i++) {
for (int c_j = 0; c_j < tot_indices; c_j++) {
if (c_i != c_j) {
cloth_brush_add_length_constraint(ss, build_indices[c_i], build_indices[c_j]);
}
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
static float cloth_brush_simulation_falloff_get(const Brush *brush,
const float radius,
const float location[3],
const float co[3])
{
const float distance = len_v3v3(location, co);
const float limit = radius + (radius * brush->cloth_sim_limit);
const float falloff = radius + (radius * brush->cloth_sim_limit * brush->cloth_sim_falloff);
if (distance > limit) {
/* Outiside the limits. */
return 0.0f;
}
else if (distance < falloff) {
/* Before the falloff area. */
return 1.0f;
}
else {
/* Do a smoothstep transition inside the falloff area. */
float p = 1.0f - ((distance - falloff) / (limit - falloff));
return 3.0f * p * p - 2.0f * p * p * p;
}
}
static void cloth_brush_apply_force_to_vertex(SculptSession *ss,
const float force[3],
const int vertex_index)
{
SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
madd_v3_v3fl(cloth_sim->acceleration[vertex_index], force, 1.0f / cloth_sim->mass);
}
static void do_cloth_brush_apply_forces_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;
SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
const float *offset = data->offset;
const float *grab_delta = data->grab_delta;
float(*imat)[4] = data->mat;
const bool use_falloff_plane = brush->cloth_force_falloff_type ==
BRUSH_CLOTH_FORCE_FALLOFF_PLANE;
PBVHVertexIter vd;
const float bstrength = ss->cache->bstrength;
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = sculpt_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
/* For Pich Perpendicular Deform Type. */
float x_object_space[3];
float z_object_space[3];
if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR) {
normalize_v3_v3(x_object_space, imat[0]);
normalize_v3_v3(z_object_space, imat[2]);
}
/* For Plane Force Falloff. */
float deform_plane[4];
float plane_normal[3];
if (use_falloff_plane) {
normalize_v3_v3(plane_normal, grab_delta);
plane_from_point_normal_v3(deform_plane, data->area_co, plane_normal);
}
/* Gravity */
float gravity[3] = {0.0f};
if (ss->cache->supports_gravity) {
madd_v3_v3fl(
gravity, ss->cache->gravity_direction, -ss->cache->radius * data->sd->gravity_factor);
}
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
float force[3];
const float sim_factor = cloth_brush_simulation_falloff_get(
brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[vd.index]);
/* When using the plane falloff mode the falloff is not constrained by the brush radius. */
if (sculpt_brush_test_sq_fn(&test, vd.co) || use_falloff_plane) {
float dist = sqrtf(test.dist);
if (use_falloff_plane) {
dist = dist_to_plane_v3(vd.co, deform_plane);
}
const float fade = sim_factor * bstrength *
tex_strength(ss,
brush,
vd.co,
dist,
vd.no,
vd.fno,
vd.mask ? *vd.mask : 0.0f,
vd.index,
tls->thread_id);
float brush_disp[3];
float normal[3];
if (vd.no) {
normal_short_to_float_v3(normal, vd.no);
}
else {
copy_v3_v3(normal, vd.fno);
}
switch (brush->cloth_deform_type) {
case BRUSH_CLOTH_DEFORM_DRAG:
sub_v3_v3v3(brush_disp, ss->cache->location, ss->cache->last_location);
normalize_v3(brush_disp);
mul_v3_v3fl(force, brush_disp, fade);
break;
case BRUSH_CLOTH_DEFORM_PUSH:
/* Invert the fade to push inwards. */
mul_v3_v3fl(force, offset, -fade);
break;
case BRUSH_CLOTH_DEFORM_GRAB:
mul_v3_v3fl(force, grab_delta, fade);
break;
case BRUSH_CLOTH_DEFORM_PINCH_POINT:
if (use_falloff_plane) {
float distance = dist_signed_to_plane_v3(vd.co, deform_plane);
copy_v3_v3(brush_disp, plane_normal);
mul_v3_fl(brush_disp, -distance);
}
else {
sub_v3_v3v3(brush_disp, ss->cache->location, vd.co);
}
normalize_v3(brush_disp);
mul_v3_v3fl(force, brush_disp, fade);
break;
case BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR: {
float disp_center[3];
float x_disp[3];
float z_disp[3];
sub_v3_v3v3(disp_center, ss->cache->location, vd.co);
normalize_v3(disp_center);
mul_v3_v3fl(x_disp, x_object_space, dot_v3v3(disp_center, x_object_space));
mul_v3_v3fl(z_disp, z_object_space, dot_v3v3(disp_center, z_object_space));
add_v3_v3v3(disp_center, x_disp, z_disp);
mul_v3_v3fl(force, disp_center, fade);
} break;
case BRUSH_CLOTH_DEFORM_INFLATE:
mul_v3_v3fl(force, normal, fade);
break;
case BRUSH_CLOTH_DEFORM_EXPAND:
cloth_sim->length_constraint_tweak[vd.index] += fade * 0.1f;
zero_v3(force);
break;
}
madd_v3_v3fl(force, gravity, fade);
cloth_brush_apply_force_to_vertex(ss, force, vd.index);
}
}
BKE_pbvh_vertex_iter_end;
}
static SculptClothSimulation *cloth_brush_simulation_create(SculptSession *ss, Brush *brush)
{
const int totverts = sculpt_vertex_count_get(ss);
SculptClothSimulation *cloth_sim;
cloth_sim = MEM_callocN(sizeof(SculptClothSimulation), "cloth constraints");
cloth_sim->length_constraints = MEM_callocN(sizeof(SculptClothLengthConstraint) *
CLOTH_LENGTH_CONSTRAINTS_BLOCK,
"cloth length constraints");
cloth_sim->capacity_length_constraints = CLOTH_LENGTH_CONSTRAINTS_BLOCK;
cloth_sim->acceleration = MEM_callocN(sizeof(float) * 3 * totverts, "cloth sim acceleration");
cloth_sim->pos = MEM_callocN(sizeof(float) * 3 * totverts, "cloth sim pos");
cloth_sim->prev_pos = MEM_callocN(sizeof(float) * 3 * totverts, "cloth sim prev pos");
cloth_sim->init_pos = MEM_callocN(sizeof(float) * 3 * totverts, "cloth sim init pos");
cloth_sim->length_constraint_tweak = MEM_callocN(sizeof(float) * totverts,
"cloth sim length tweak");
cloth_sim->mass = brush->cloth_mass;
cloth_sim->damping = brush->cloth_damping;
return cloth_sim;
}
static void do_cloth_brush_solve_simulation_task_cb_ex(
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const Brush *brush = data->brush;
PBVHVertexIter vd;
SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
const float time_step = data->cloth_time_step;
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
const float sim_factor = cloth_brush_simulation_falloff_get(
brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[vd.index]);
if (sim_factor > 0.0f) {
int i = vd.index;
float temp[3];
copy_v3_v3(temp, cloth_sim->pos[i]);
mul_v3_fl(cloth_sim->acceleration[i], time_step);
float pos_diff[3];
sub_v3_v3v3(pos_diff, cloth_sim->pos[i], cloth_sim->prev_pos[i]);
mul_v3_fl(pos_diff, (1.0f - cloth_sim->damping));
const float mask_v = (1.0f - (vd.mask ? *vd.mask : 0.0f));
madd_v3_v3fl(cloth_sim->pos[i], pos_diff, mask_v);
madd_v3_v3fl(cloth_sim->pos[i], cloth_sim->acceleration[i], mask_v);
copy_v3_v3(cloth_sim->prev_pos[i], temp);
copy_v3_fl(cloth_sim->acceleration[i], 0.0f);
copy_v3_v3(vd.co, ss->cache->cloth_sim->pos[vd.index]);
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void cloth_brush_build_nodes_constraints(Sculpt *sd,
Object *ob,
PBVHNode **nodes,
int totnode)
{
Brush *brush = BKE_paint_brush(&sd->paint);
/* TODO: Multithreaded needs to be disabled for this task until implementing the optimization of
* storing the constraints per node. */
/* Currently all constrains are added to the same global array which can't be accesed from
* different threads. */
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, false, totnode);
SculptThreadedTaskData build_constraints_data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
};
BKE_pbvh_parallel_range(
0, totnode, &build_constraints_data, do_cloth_brush_build_constraints_task_cb_ex, &settings);
}
static void cloth_brush_satisfy_constraints(SculptSession *ss,
Brush *brush,
SculptClothSimulation *cloth_sim)
{
for (int constraint_it = 0; constraint_it < CLOTH_SIMULATION_ITERATIONS; constraint_it++) {
for (int i = 0; i < cloth_sim->tot_length_constraints; i++) {
const SculptClothLengthConstraint *constraint = &cloth_sim->length_constraints[i];
const int v1 = constraint->v1;
const int v2 = constraint->v2;
float v1_to_v2[3];
sub_v3_v3v3(v1_to_v2, cloth_sim->pos[v2], cloth_sim->pos[v1]);
const float current_distance = len_v3(v1_to_v2);
float correction_vector[3];
float correction_vector_half[3];
const float constraint_distance = constraint->length +
(cloth_sim->length_constraint_tweak[v1] * 0.5f) +
(cloth_sim->length_constraint_tweak[v2] * 0.5f);
mul_v3_v3fl(correction_vector, v1_to_v2, 1.0f - (constraint_distance / current_distance));
mul_v3_v3fl(correction_vector_half, correction_vector, 0.5f);
const float mask_v1 = (1.0f - sculpt_vertex_mask_get(ss, v1));
const float mask_v2 = (1.0f - sculpt_vertex_mask_get(ss, v2));
const float sim_factor_v1 = cloth_brush_simulation_falloff_get(
brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[v1]);
const float sim_factor_v2 = cloth_brush_simulation_falloff_get(
brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[v2]);
madd_v3_v3fl(cloth_sim->pos[v1], correction_vector_half, 1.0f * mask_v1 * sim_factor_v1);
madd_v3_v3fl(cloth_sim->pos[v2], correction_vector_half, -1.0f * mask_v2 * sim_factor_v2);
}
}
}
static void cloth_brush_do_simulation_step(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
SculptClothSimulation *cloth_sim = ss->cache->cloth_sim;
/* Update the constraints. */
cloth_brush_satisfy_constraints(ss, brush, cloth_sim);
/* Solve the simulation and write the final step to the mesh. */
SculptThreadedTaskData solve_simulation_data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.cloth_time_step = CLOTH_SIMULATION_TIME_STEP,
};
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
BKE_pbvh_parallel_range(
0, totnode, &solve_simulation_data, do_cloth_brush_solve_simulation_task_cb_ex, &settings);
}
static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
float grab_delta[3];
float mat[4][4];
float area_no[3];
float area_co[3];
float imat[4][4];
float offset[3];
SculptThreadedTaskData apply_forces_data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.area_no = area_no,
.area_co = area_co,
.mat = imat,
};
BKE_curvemapping_initialize(brush->curve);
/* Init the grab delta. */
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
normalize_v3(grab_delta);
apply_forces_data.grab_delta = grab_delta;
if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
return;
}
/* Calcuate push offset. */
if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_PUSH) {
mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius);
mul_v3_v3(offset, ss->cache->scale);
mul_v3_fl(offset, 2.0f);
apply_forces_data.offset = offset;
}
if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR ||
brush->cloth_force_falloff_type == BRUSH_CLOTH_FORCE_FALLOFF_PLANE) {
SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co);
/* Init stroke local space matrix. */
cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
mat[0][3] = 0.0f;
cross_v3_v3v3(mat[1], area_no, mat[0]);
mat[1][3] = 0.0f;
copy_v3_v3(mat[2], area_no);
mat[2][3] = 0.0f;
copy_v3_v3(mat[3], ss->cache->location);
mat[3][3] = 1.0f;
normalize_m4(mat);
apply_forces_data.area_co = area_co;
apply_forces_data.area_no = area_no;
apply_forces_data.mat = mat;
/* Update matrix for the cursor preview. */
if (ss->cache->mirror_symmetry_pass == 0) {
copy_m4_m4(ss->cache->stroke_local_mat, mat);
}
}
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
BKE_pbvh_parallel_range(
0, totnode, &apply_forces_data, do_cloth_brush_apply_forces_task_cb_ex, &settings);
}
/* Public functions. */
/* Main Brush Function. */
void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
const int totverts = sculpt_vertex_count_get(ss);
/* In the first brush step of each symmetry pass, build the constraints for the vertices in all
* nodes inside the simulation's limits. */
/* Brush stroke types that restore the mesh on each brush step also need the cloth sim data to be
* created on each step. */
if (ss->cache->first_time || !ss->cache->cloth_sim) {
/* The simulation structure only needs to be created on the first symmetry pass. */
if (ss->cache->mirror_symmetry_pass == 0) {
ss->cache->cloth_sim = cloth_brush_simulation_create(ss, brush);
for (int i = 0; i < totverts; i++) {
copy_v3_v3(ss->cache->cloth_sim->prev_pos[i], sculpt_vertex_co_get(ss, i));
copy_v3_v3(ss->cache->cloth_sim->init_pos[i], sculpt_vertex_co_get(ss, i));
}
}
/* Build the constraints. */
cloth_brush_build_nodes_constraints(sd, ob, nodes, totnode);
return;
}
/* Store the initial state in the simulation. */
for (int i = 0; i < totverts; i++) {
copy_v3_v3(ss->cache->cloth_sim->pos[i], sculpt_vertex_co_get(ss, i));
}
/* Apply forces to the vertices. */
cloth_brush_apply_brush_foces(sd, ob, nodes, totnode);
/* Update and write the simulation to the nodes. */
cloth_brush_do_simulation_step(sd, ob, nodes, totnode);
return;
}
void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim)
{
MEM_SAFE_FREE(cloth_sim->pos);
MEM_SAFE_FREE(cloth_sim->prev_pos);
MEM_SAFE_FREE(cloth_sim->acceleration);
MEM_SAFE_FREE(cloth_sim->length_constraints);
MEM_SAFE_FREE(cloth_sim->length_constraint_tweak);
MEM_SAFE_FREE(cloth_sim->init_pos);
MEM_SAFE_FREE(cloth_sim);
}
/* Cursor drawing function. */
void SCULPT_cloth_simulation_limits_draw(const uint gpuattr,
const Brush *brush,
const float obmat[4][4],
const float location[3],
const float normal[3],
const float rds,
const float line_width,
const float outline_col[3],
const float alpha)
{
float cursor_trans[4][4], cursor_rot[4][4];
float z_axis[4] = {0.0f, 0.0f, 1.0f, 0.0f};
float quat[4];
copy_m4_m4(cursor_trans, obmat);
translate_m4(cursor_trans, location[0], location[1], location[2]);
rotation_between_vecs_to_quat(quat, z_axis, normal);
quat_to_mat4(cursor_rot, quat);
GPU_matrix_mul(cursor_trans);
GPU_matrix_mul(cursor_rot);
GPU_line_width(line_width);
immUniformColor3fvAlpha(outline_col, alpha * 0.5f);
imm_draw_circle_dashed_3d(
gpuattr, 0, 0, rds + (rds * brush->cloth_sim_limit * brush->cloth_sim_falloff), 320);
immUniformColor3fvAlpha(outline_col, alpha * 0.7f);
imm_draw_circle_wire_3d(gpuattr, 0, 0, rds + rds * brush->cloth_sim_limit, 80);
}
void SCULPT_cloth_plane_falloff_preview_draw(const uint gpuattr,
SculptSession *ss,
const float outline_col[3],
float outline_alpha)
{
float local_mat_inv[4][4];
invert_m4_m4(local_mat_inv, ss->cache->stroke_local_mat);
GPU_matrix_mul(ss->cache->stroke_local_mat);
const float dist = ss->cache->radius;
const float arrow_x = ss->cache->radius * 0.2f;
const float arrow_y = ss->cache->radius * 0.1f;
immUniformColor3fvAlpha(outline_col, outline_alpha);
GPU_line_width(2.0f);
immBegin(GPU_PRIM_LINES, 2);
immVertex3f(gpuattr, dist, 0.0f, 0.0f);
immVertex3f(gpuattr, -dist, 0.0f, 0.0f);
immEnd();
immBegin(GPU_PRIM_TRIS, 6);
immVertex3f(gpuattr, dist, 0.0f, 0.0f);
immVertex3f(gpuattr, dist - arrow_x, arrow_y, 0.0f);
immVertex3f(gpuattr, dist - arrow_x, -arrow_y, 0.0f);
immVertex3f(gpuattr, -dist, 0.0f, 0.0f);
immVertex3f(gpuattr, -dist + arrow_x, arrow_y, 0.0f);
immVertex3f(gpuattr, -dist + arrow_x, -arrow_y, 0.0f);
immEnd();
}

View File

@ -85,13 +85,92 @@ struct SculptPoseIKChain *sculpt_pose_ik_chain_init(struct Sculpt *sd,
void sculpt_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain);
/* Sculpt PBVH abstraction API */
int sculpt_vertex_count_get(struct SculptSession *ss);
const float *sculpt_vertex_co_get(struct SculptSession *ss, int index);
float sculpt_vertex_mask_get(struct SculptSession *ss, int index);
#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
typedef struct SculptVertexNeighborIter {
/* Storage */
int *neighbors;
int size;
int capacity;
int neighbors_fixed[SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY];
/* Internal iterator. */
int num_duplicates;
int i;
/* Public */
int index;
bool is_duplicate;
} SculptVertexNeighborIter;
void sculpt_vertex_neighbors_get(struct SculptSession *ss,
const int index,
const bool include_duplicates,
SculptVertexNeighborIter *iter);
/* Iterator over neighboring vertices. */
#define sculpt_vertex_neighbors_iter_begin(ss, v_index, neighbor_iterator) \
sculpt_vertex_neighbors_get(ss, v_index, false, &neighbor_iterator); \
for (neighbor_iterator.i = 0; neighbor_iterator.i < neighbor_iterator.size; \
neighbor_iterator.i++) { \
neighbor_iterator.index = ni.neighbors[ni.i];
/* Iterate over neighboring and duplicate vertices (for PBVH_GRIDS). Duplicates come
* first since they are nearest for floodfill. */
#define sculpt_vertex_duplicates_and_neighbors_iter_begin(ss, v_index, neighbor_iterator) \
sculpt_vertex_neighbors_get(ss, v_index, true, &neighbor_iterator); \
for (neighbor_iterator.i = neighbor_iterator.size - 1; neighbor_iterator.i >= 0; \
neighbor_iterator.i--) { \
neighbor_iterator.index = ni.neighbors[ni.i]; \
neighbor_iterator.is_duplicate = (ni.i >= \
neighbor_iterator.size - neighbor_iterator.num_duplicates);
#define sculpt_vertex_neighbors_iter_end(neighbor_iterator) \
} \
if (neighbor_iterator.neighbors != neighbor_iterator.neighbors_fixed) { \
MEM_freeN(neighbor_iterator.neighbors); \
} \
((void)0)
/* Dynamic topology */
void sculpt_pbvh_clear(Object *ob);
void sculpt_dyntopo_node_layers_add(struct SculptSession *ss);
void sculpt_dynamic_topology_disable(bContext *C, struct SculptUndoNode *unode);
/* Utils. */
void SCULPT_calc_brush_plane(struct Sculpt *sd,
struct Object *ob,
struct PBVHNode **nodes,
int totnode,
float r_area_no[3],
float r_area_co[3]);
/* Brushes. */
/* Cloth Brush. */
void SCULPT_do_cloth_brush(struct Sculpt *sd,
struct Object *ob,
struct PBVHNode **nodes,
int totnode);
void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim);
void SCULPT_cloth_simulation_limits_draw(const uint gpuattr,
const struct Brush *brush,
const float obmat[4][4],
const float location[3],
const float normal[3],
const float rds,
const float line_width,
const float outline_col[3],
const float alpha);
void SCULPT_cloth_plane_falloff_preview_draw(const uint gpuattr,
struct SculptSession *ss,
const float outline_col[3],
float outline_alpha);
/* Undo */
typedef enum {
@ -242,6 +321,8 @@ typedef struct SculptThreadedTaskData {
float transform_mats[8][4][4];
float cloth_time_step;
float dirty_mask_min;
float dirty_mask_max;
bool dirty_mask_dirty_only;
@ -416,6 +497,13 @@ typedef struct StrokeCache {
float clay_pressure_stabilizer[CLAY_STABILIZER_LEN];
int clay_pressure_stabilizer_index;
/* Cloth brush */
struct SculptClothSimulation *cloth_sim;
float initial_location[3];
float true_initial_location[3];
float initial_normal[3];
float true_initial_normal[3];
float vertex_rotation; /* amount to rotate the vertices when using rotate brush */
struct Dial *dial;

View File

@ -47,6 +47,7 @@ void imm_draw_circle_fill_aspect_2d(
/* use this version when GPUVertFormat has a vec3 position */
void imm_draw_circle_wire_3d(uint pos, float x, float y, float radius, int nsegments);
void imm_draw_circle_dashed_3d(uint pos, float x, float y, float radius, int nsegments);
void imm_draw_circle_fill_3d(uint pos, float x, float y, float radius, int nsegments);
/* same as 'imm_draw_disk_partial_fill_2d', except it draws a wire arc. */

View File

@ -316,6 +316,11 @@ void imm_draw_circle_wire_3d(uint pos, float x, float y, float rad, int nsegment
imm_draw_circle_3D(GPU_PRIM_LINE_LOOP, pos, x, y, rad, nsegments);
}
void imm_draw_circle_dashed_3d(uint pos, float x, float y, float rad, int nsegments)
{
imm_draw_circle_3D(GPU_PRIM_LINES, pos, x, y, rad, nsegments / 2);
}
void imm_draw_circle_fill_3d(uint pos, float x, float y, float rad, int nsegments)
{
imm_draw_circle_3D(GPU_PRIM_TRI_FAN, pos, x, y, rad, nsegments);

View File

@ -48,6 +48,10 @@
.normal_radius_factor = 0.5f, \
.area_radius_factor = 0.5f, \
.sculpt_plane = SCULPT_DISP_DIR_AREA, \
.cloth_damping = 0.01, \
.cloth_mass = 1, \
.cloth_sim_limit = 2.5f, \
.cloth_sim_falloff = 0.75f, \
/* How far above or below the plane that is found by averaging the faces. */ \
.plane_offset = 0.0f, \
.plane_trim = 0.5f, \

View File

@ -210,6 +210,21 @@ typedef enum eBrushElasticDeformType {
BRUSH_ELASTIC_DEFORM_TWIST = 4,
} eBrushElasticDeformType;
typedef enum eBrushClothDeformType {
BRUSH_CLOTH_DEFORM_DRAG = 0,
BRUSH_CLOTH_DEFORM_PUSH = 1,
BRUSH_CLOTH_DEFORM_GRAB = 2,
BRUSH_CLOTH_DEFORM_PINCH_POINT = 3,
BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR = 4,
BRUSH_CLOTH_DEFORM_INFLATE = 5,
BRUSH_CLOTH_DEFORM_EXPAND = 6,
} eBrushClothDeformType;
typedef enum eBrushClothForceFalloffType {
BRUSH_CLOTH_FORCE_FALLOFF_RADIAL = 0,
BRUSH_CLOTH_FORCE_FALLOFF_PLANE = 1,
} eBrushClothForceFalloffType;
typedef enum eAutomasking_flag {
BRUSH_AUTOMASKING_TOPOLOGY = (1 << 0),
} eAutomasking_flag;
@ -291,7 +306,7 @@ typedef struct Brush {
/** Source for fill tool color gradient application. */
char gradient_fill_mode;
char _pad0;
char _pad0[5];
/** Projection shape (sphere, circle). */
char falloff_shape;
@ -311,7 +326,7 @@ typedef struct Brush {
char mask_tool;
/** Active grease pencil tool. */
char gpencil_tool;
char _pad1[5];
char _pad1[1];
float autosmooth_factor;
@ -343,6 +358,16 @@ typedef struct Brush {
int pose_smooth_iterations;
int pose_ik_segments;
/* cloth */
int cloth_deform_type;
int cloth_force_falloff_type;
float cloth_mass;
float cloth_damping;
float cloth_sim_limit;
float cloth_sim_falloff;
/* multiplane scrape */
float multiplane_scrape_angle;
@ -512,6 +537,7 @@ typedef enum eBrushSculptTool {
SCULPT_TOOL_MULTIPLANE_SCRAPE = 23,
SCULPT_TOOL_SLIDE_RELAX = 24,
SCULPT_TOOL_CLAY_THUMB = 25,
SCULPT_TOOL_CLOTH = 26,
} eBrushSculptTool;
/* Brush.uv_sculpt_tool */
@ -547,6 +573,7 @@ typedef enum eBrushUVSculptTool {
(ELEM(t, /* These brushes, as currently coded, cannot support dynamic topology */ \
SCULPT_TOOL_GRAB, \
SCULPT_TOOL_ROTATE, \
SCULPT_TOOL_CLOTH, \
SCULPT_TOOL_THUMB, \
SCULPT_TOOL_LAYER, \
SCULPT_TOOL_DRAW_SHARP, \

View File

@ -96,6 +96,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_ROTATE, "ROTATE", ICON_BRUSH_ROTATE, "Rotate", ""},
{SCULPT_TOOL_SLIDE_RELAX, "TOPOLOGY", ICON_BRUSH_GRAB, "Slide Relax", ""},
{0, "", 0, NULL, NULL},
{SCULPT_TOOL_CLOTH, "CLOTH", ICON_BRUSH_SCULPT_DRAW, "Cloth", ""},
{SCULPT_TOOL_SIMPLIFY, "SIMPLIFY", ICON_BRUSH_DATA, "Simplify", ""},
{SCULPT_TOOL_MASK, "MASK", ICON_BRUSH_MASK, "Mask", ""},
{0, NULL, 0, NULL, NULL},
@ -1641,6 +1642,27 @@ static void rna_def_brush(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_cloth_deform_type_items[] = {
{BRUSH_CLOTH_DEFORM_DRAG, "DRAG", 0, "Drag", ""},
{BRUSH_CLOTH_DEFORM_PUSH, "PUSH", 0, "Push", ""},
{BRUSH_CLOTH_DEFORM_PINCH_POINT, "PINCH_POINT", 0, "Pinch Point", ""},
{BRUSH_CLOTH_DEFORM_PINCH_PERPENDICULAR,
"PINCH_PERPENDICULAR",
0,
"Pinch Perpendicular",
""},
{BRUSH_CLOTH_DEFORM_INFLATE, "INFLATE", 0, "Inflate", ""},
{BRUSH_CLOTH_DEFORM_GRAB, "GRAB", 0, "Grab", ""},
{BRUSH_CLOTH_DEFORM_EXPAND, "EXPAND", 0, "Expand", ""},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_cloth_force_falloff_type_items[] = {
{BRUSH_CLOTH_FORCE_FALLOFF_RADIAL, "RADIAL", 0, "Radial", ""},
{BRUSH_CLOTH_FORCE_FALLOFF_PLANE, "PLANE", 0, "Plane", ""},
{0, NULL, 0, NULL, NULL},
};
srna = RNA_def_struct(brna, "Brush", "ID");
RNA_def_struct_ui_text(
srna, "Brush", "Brush data-block for storing brush settings for painting and sculpting");
@ -1726,6 +1748,17 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "cloth_deform_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_cloth_deform_type_items);
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "cloth_force_falloff_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_cloth_force_falloff_type_items);
RNA_def_property_ui_text(
prop, "Force Falloff", "Shape used in the brush to apply force to the cloth");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "jitter_unit", PROP_ENUM, PROP_NONE); /* as an enum */
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, brush_jitter_unit_items);
@ -1949,6 +1982,36 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Tip Roundness", "Roundness of the brush tip");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "cloth_mass", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "cloth_mass");
RNA_def_property_range(prop, 0.01f, 2.0f);
RNA_def_property_ui_text(prop, "Cloth mass", "Mass of each simulation particle");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "cloth_damping", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "cloth_damping");
RNA_def_property_range(prop, 0.01f, 1.0f);
RNA_def_property_ui_text(
prop, "Cloth Damping", "How much the applied forces are propagated through the cloth");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "cloth_sim_limit", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "cloth_sim_limit");
RNA_def_property_range(prop, 0.1f, 10.0f);
RNA_def_property_ui_text(
prop,
"Simulation Limit",
"Factor added relative to the size of the radius to limit the cloth simulation effects");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "cloth_sim_falloff", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "cloth_sim_falloff");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_text(prop,
"Simulation Falloff",
"Area to apply deformation falloff to the effects of the simulation");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "auto_smooth_factor", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "autosmooth_factor");
RNA_def_property_float_default(prop, 0);