Files
test/source/blender/editors/sculpt_paint/sculpt_cloth.c
Campbell Barton 2f149ebbe9 Cleanup: uppercase macros for sculpt iterators, add to clang-format
Also use sculpt prefix for SCULPT_CLAY_STABILIZER_LEN.
2020-03-27 11:27:10 +11:00

689 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_dial_2d.h"
#include "BLI_ghash.h"
#include "BLI_gsqueue.h"
#include "BLI_hash.h"
#include "BLI_math.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "DNA_brush_types.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 "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_message.h"
#include "WM_toolsystem.h"
#include "WM_types.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.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 *
SCULPT_brush_strength_factor(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)) *
SCULPT_automasking_factor_get(ss, vd.index);
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: Multi-threaded 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 accessed 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);
if (current_distance > 0.0f) {
mul_v3_v3fl(correction_vector, v1_to_v2, 1.0f - (constraint_distance / current_distance));
}
else {
copy_v3_v3(correction_vector, v1_to_v2);
}
mul_v3_v3fl(correction_vector_half, correction_vector, 0.5f);
const float mask_v1 = (1.0f - SCULPT_vertex_mask_get(ss, v1)) *
SCULPT_automasking_factor_get(ss, v1);
const float mask_v2 = (1.0f - SCULPT_vertex_mask_get(ss, v2)) *
SCULPT_automasking_factor_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();
}