Selecting UVs wasn't properly scaling based on the zoom or image aspect. This now matches vertex selection in the 3D view.
855 lines
27 KiB
C
855 lines
27 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) Blender Foundation, 2002-2009
|
|
* All rights reserved.
|
|
* UV Sculpt tools
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup edsculpt
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_brush_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "BKE_brush.h"
|
|
#include "BKE_colortools.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_editmesh.h"
|
|
#include "BKE_mesh_mapping.h"
|
|
#include "BKE_paint.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
|
|
#include "ED_image.h"
|
|
#include "ED_mesh.h"
|
|
#include "ED_screen.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "paint_intern.h"
|
|
#include "uvedit_intern.h"
|
|
|
|
#include "UI_view2d.h"
|
|
|
|
#define MARK_BOUNDARY 1
|
|
|
|
typedef struct UvAdjacencyElement {
|
|
/* pointer to original uvelement */
|
|
UvElement *element;
|
|
/* uv pointer for convenience. Caution, this points to the original UVs! */
|
|
float *uv;
|
|
/* general use flag (Used to check if Element is boundary here) */
|
|
char flag;
|
|
} UvAdjacencyElement;
|
|
|
|
typedef struct UvEdge {
|
|
uint uv1;
|
|
uint uv2;
|
|
/* general use flag
|
|
* (Used to check if edge is boundary here, and propagates to adjacency elements) */
|
|
char flag;
|
|
} UvEdge;
|
|
|
|
typedef struct UVInitialStrokeElement {
|
|
/* index to unique uv */
|
|
int uv;
|
|
|
|
/* strength of brush on initial position */
|
|
float strength;
|
|
|
|
/* initial uv position */
|
|
float initial_uv[2];
|
|
} UVInitialStrokeElement;
|
|
|
|
typedef struct UVInitialStroke {
|
|
/* Initial Selection,for grab brushes for instance */
|
|
UVInitialStrokeElement *initialSelection;
|
|
|
|
/* total initially selected UVs*/
|
|
int totalInitialSelected;
|
|
|
|
/* initial mouse coordinates */
|
|
float init_coord[2];
|
|
} UVInitialStroke;
|
|
|
|
/* custom data for uv smoothing brush */
|
|
typedef struct UvSculptData {
|
|
/* Contains the first of each set of coincident uvs.
|
|
* These will be used to perform smoothing on and propagate the changes
|
|
* to their coincident uvs */
|
|
UvAdjacencyElement *uv;
|
|
|
|
/* ...Is what it says */
|
|
int totalUniqueUvs;
|
|
|
|
/* Edges used for adjacency info, used with laplacian smoothing */
|
|
UvEdge *uvedges;
|
|
|
|
/* need I say more? */
|
|
int totalUvEdges;
|
|
|
|
/* data for initial stroke, used by tools like grab */
|
|
UVInitialStroke *initial_stroke;
|
|
|
|
/* timer to be used for airbrush-type brush */
|
|
wmTimer *timer;
|
|
|
|
/* to determine quickly adjacent uvs */
|
|
UvElementMap *elementMap;
|
|
|
|
/* uvsmooth Paint for fast reference */
|
|
Paint *uvsculpt;
|
|
|
|
/* tool to use. duplicating here to change if modifier keys are pressed */
|
|
char tool;
|
|
|
|
/* store invert flag here */
|
|
char invert;
|
|
} UvSculptData;
|
|
|
|
/*********** Improved Laplacian Relaxation Operator ************************/
|
|
/* original code by Raul Fernandez Hernandez "farsthary" *
|
|
* adapted to uv smoothing by Antony Riakiatakis *
|
|
***************************************************************************/
|
|
|
|
typedef struct Temp_UvData {
|
|
float sum_co[2], p[2], b[2], sum_b[2];
|
|
int ncounter;
|
|
} Temp_UVData;
|
|
|
|
static void HC_relaxation_iteration_uv(BMEditMesh *em,
|
|
UvSculptData *sculptdata,
|
|
const float mouse_coord[2],
|
|
float alpha,
|
|
float radius,
|
|
float aspectRatio)
|
|
{
|
|
Temp_UVData *tmp_uvdata;
|
|
float diff[2];
|
|
int i;
|
|
float radius_root = sqrtf(radius);
|
|
Brush *brush = BKE_paint_brush(sculptdata->uvsculpt);
|
|
|
|
tmp_uvdata = (Temp_UVData *)MEM_callocN(sculptdata->totalUniqueUvs * sizeof(Temp_UVData),
|
|
"Temporal data");
|
|
|
|
/* counting neighbors */
|
|
for (i = 0; i < sculptdata->totalUvEdges; i++) {
|
|
UvEdge *tmpedge = sculptdata->uvedges + i;
|
|
tmp_uvdata[tmpedge->uv1].ncounter++;
|
|
tmp_uvdata[tmpedge->uv2].ncounter++;
|
|
|
|
add_v2_v2(tmp_uvdata[tmpedge->uv2].sum_co, sculptdata->uv[tmpedge->uv1].uv);
|
|
add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_co, sculptdata->uv[tmpedge->uv2].uv);
|
|
}
|
|
|
|
for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
|
|
copy_v2_v2(diff, tmp_uvdata[i].sum_co);
|
|
mul_v2_fl(diff, 1.0f / tmp_uvdata[i].ncounter);
|
|
copy_v2_v2(tmp_uvdata[i].p, diff);
|
|
|
|
tmp_uvdata[i].b[0] = diff[0] - sculptdata->uv[i].uv[0];
|
|
tmp_uvdata[i].b[1] = diff[1] - sculptdata->uv[i].uv[1];
|
|
}
|
|
|
|
for (i = 0; i < sculptdata->totalUvEdges; i++) {
|
|
UvEdge *tmpedge = sculptdata->uvedges + i;
|
|
add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_b, tmp_uvdata[tmpedge->uv2].b);
|
|
add_v2_v2(tmp_uvdata[tmpedge->uv2].sum_b, tmp_uvdata[tmpedge->uv1].b);
|
|
}
|
|
|
|
for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
|
|
float dist;
|
|
/* This is supposed to happen only if "Pin Edges" is on,
|
|
* since we have initialization on stroke start.
|
|
* If ever uv brushes get their own mode we should check for toolsettings option too. */
|
|
if ((sculptdata->uv[i].flag & MARK_BOUNDARY)) {
|
|
continue;
|
|
}
|
|
|
|
sub_v2_v2v2(diff, sculptdata->uv[i].uv, mouse_coord);
|
|
diff[1] /= aspectRatio;
|
|
if ((dist = dot_v2v2(diff, diff)) <= radius) {
|
|
UvElement *element;
|
|
float strength;
|
|
strength = alpha * BKE_brush_curve_strength_clamped(brush, sqrtf(dist), radius_root);
|
|
|
|
sculptdata->uv[i].uv[0] = (1.0f - strength) * sculptdata->uv[i].uv[0] +
|
|
strength *
|
|
(tmp_uvdata[i].p[0] -
|
|
0.5f * (tmp_uvdata[i].b[0] +
|
|
tmp_uvdata[i].sum_b[0] / tmp_uvdata[i].ncounter));
|
|
sculptdata->uv[i].uv[1] = (1.0f - strength) * sculptdata->uv[i].uv[1] +
|
|
strength *
|
|
(tmp_uvdata[i].p[1] -
|
|
0.5f * (tmp_uvdata[i].b[1] +
|
|
tmp_uvdata[i].sum_b[1] / tmp_uvdata[i].ncounter));
|
|
|
|
for (element = sculptdata->uv[i].element; element; element = element->next) {
|
|
MLoopUV *luv;
|
|
BMLoop *l;
|
|
|
|
if (element->separate && element != sculptdata->uv[i].element) {
|
|
break;
|
|
}
|
|
|
|
l = element->l;
|
|
luv = CustomData_bmesh_get(&em->bm->ldata, l->head.data, CD_MLOOPUV);
|
|
copy_v2_v2(luv->uv, sculptdata->uv[i].uv);
|
|
}
|
|
}
|
|
}
|
|
|
|
MEM_freeN(tmp_uvdata);
|
|
}
|
|
|
|
static void laplacian_relaxation_iteration_uv(BMEditMesh *em,
|
|
UvSculptData *sculptdata,
|
|
const float mouse_coord[2],
|
|
float alpha,
|
|
float radius,
|
|
float aspectRatio)
|
|
{
|
|
Temp_UVData *tmp_uvdata;
|
|
float diff[2];
|
|
int i;
|
|
float radius_root = sqrtf(radius);
|
|
Brush *brush = BKE_paint_brush(sculptdata->uvsculpt);
|
|
|
|
tmp_uvdata = (Temp_UVData *)MEM_callocN(sculptdata->totalUniqueUvs * sizeof(Temp_UVData),
|
|
"Temporal data");
|
|
|
|
/* counting neighbors */
|
|
for (i = 0; i < sculptdata->totalUvEdges; i++) {
|
|
UvEdge *tmpedge = sculptdata->uvedges + i;
|
|
tmp_uvdata[tmpedge->uv1].ncounter++;
|
|
tmp_uvdata[tmpedge->uv2].ncounter++;
|
|
|
|
add_v2_v2(tmp_uvdata[tmpedge->uv2].sum_co, sculptdata->uv[tmpedge->uv1].uv);
|
|
add_v2_v2(tmp_uvdata[tmpedge->uv1].sum_co, sculptdata->uv[tmpedge->uv2].uv);
|
|
}
|
|
|
|
/* Original Lacplacian algorithm included removal of normal component of translation.
|
|
* here it is not needed since we translate along the UV plane always. */
|
|
for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
|
|
copy_v2_v2(tmp_uvdata[i].p, tmp_uvdata[i].sum_co);
|
|
mul_v2_fl(tmp_uvdata[i].p, 1.0f / tmp_uvdata[i].ncounter);
|
|
}
|
|
|
|
for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
|
|
float dist;
|
|
/* This is supposed to happen only if "Pin Edges" is on,
|
|
* since we have initialization on stroke start.
|
|
* If ever uv brushes get their own mode we should check for toolsettings option too. */
|
|
if ((sculptdata->uv[i].flag & MARK_BOUNDARY)) {
|
|
continue;
|
|
}
|
|
|
|
sub_v2_v2v2(diff, sculptdata->uv[i].uv, mouse_coord);
|
|
diff[1] /= aspectRatio;
|
|
if ((dist = dot_v2v2(diff, diff)) <= radius) {
|
|
UvElement *element;
|
|
float strength;
|
|
strength = alpha * BKE_brush_curve_strength_clamped(brush, sqrtf(dist), radius_root);
|
|
|
|
sculptdata->uv[i].uv[0] = (1.0f - strength) * sculptdata->uv[i].uv[0] +
|
|
strength * tmp_uvdata[i].p[0];
|
|
sculptdata->uv[i].uv[1] = (1.0f - strength) * sculptdata->uv[i].uv[1] +
|
|
strength * tmp_uvdata[i].p[1];
|
|
|
|
for (element = sculptdata->uv[i].element; element; element = element->next) {
|
|
MLoopUV *luv;
|
|
BMLoop *l;
|
|
|
|
if (element->separate && element != sculptdata->uv[i].element) {
|
|
break;
|
|
}
|
|
|
|
l = element->l;
|
|
luv = CustomData_bmesh_get(&em->bm->ldata, l->head.data, CD_MLOOPUV);
|
|
copy_v2_v2(luv->uv, sculptdata->uv[i].uv);
|
|
}
|
|
}
|
|
}
|
|
|
|
MEM_freeN(tmp_uvdata);
|
|
}
|
|
|
|
static void uv_sculpt_stroke_apply(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent *event,
|
|
Object *obedit)
|
|
{
|
|
float co[2], radius, radius_root;
|
|
Scene *scene = CTX_data_scene(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
BMEditMesh *em = BKE_editmesh_from_object(obedit);
|
|
uint tool;
|
|
UvSculptData *sculptdata = (UvSculptData *)op->customdata;
|
|
SpaceImage *sima;
|
|
int invert;
|
|
int width, height;
|
|
float aspectRatio;
|
|
float alpha, zoomx, zoomy;
|
|
Brush *brush = BKE_paint_brush(sculptdata->uvsculpt);
|
|
ToolSettings *toolsettings = CTX_data_tool_settings(C);
|
|
tool = sculptdata->tool;
|
|
invert = sculptdata->invert ? -1 : 1;
|
|
alpha = BKE_brush_alpha_get(scene, brush);
|
|
UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
|
|
|
|
sima = CTX_wm_space_image(C);
|
|
ED_space_image_get_size(sima, &width, &height);
|
|
ED_space_image_get_zoom(sima, region, &zoomx, &zoomy);
|
|
|
|
radius = BKE_brush_size_get(scene, brush) / (width * zoomx);
|
|
aspectRatio = width / (float)height;
|
|
|
|
/* We will compare squares to save some computation */
|
|
radius = radius * radius;
|
|
radius_root = sqrtf(radius);
|
|
|
|
/*
|
|
* Pinch Tool
|
|
*/
|
|
if (tool == UV_SCULPT_TOOL_PINCH) {
|
|
int i;
|
|
alpha *= invert;
|
|
for (i = 0; i < sculptdata->totalUniqueUvs; i++) {
|
|
float dist, diff[2];
|
|
/* This is supposed to happen only if "Lock Borders" is on,
|
|
* since we have initialization on stroke start.
|
|
* If ever uv brushes get their own mode we should check for toolsettings option too. */
|
|
if (sculptdata->uv[i].flag & MARK_BOUNDARY) {
|
|
continue;
|
|
}
|
|
|
|
sub_v2_v2v2(diff, sculptdata->uv[i].uv, co);
|
|
diff[1] /= aspectRatio;
|
|
if ((dist = dot_v2v2(diff, diff)) <= radius) {
|
|
UvElement *element;
|
|
float strength;
|
|
strength = alpha * BKE_brush_curve_strength_clamped(brush, sqrtf(dist), radius_root);
|
|
normalize_v2(diff);
|
|
|
|
sculptdata->uv[i].uv[0] -= strength * diff[0] * 0.001f;
|
|
sculptdata->uv[i].uv[1] -= strength * diff[1] * 0.001f;
|
|
|
|
for (element = sculptdata->uv[i].element; element; element = element->next) {
|
|
MLoopUV *luv;
|
|
BMLoop *l;
|
|
|
|
if (element->separate && element != sculptdata->uv[i].element) {
|
|
break;
|
|
}
|
|
|
|
l = element->l;
|
|
luv = CustomData_bmesh_get(&em->bm->ldata, l->head.data, CD_MLOOPUV);
|
|
copy_v2_v2(luv->uv, sculptdata->uv[i].uv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Smooth Tool
|
|
*/
|
|
else if (tool == UV_SCULPT_TOOL_RELAX) {
|
|
uint method = toolsettings->uv_relax_method;
|
|
if (method == UV_SCULPT_TOOL_RELAX_HC) {
|
|
HC_relaxation_iteration_uv(em, sculptdata, co, alpha, radius, aspectRatio);
|
|
}
|
|
else {
|
|
laplacian_relaxation_iteration_uv(em, sculptdata, co, alpha, radius, aspectRatio);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Grab Tool
|
|
*/
|
|
else if (tool == UV_SCULPT_TOOL_GRAB) {
|
|
int i;
|
|
float diff[2];
|
|
sub_v2_v2v2(diff, co, sculptdata->initial_stroke->init_coord);
|
|
|
|
for (i = 0; i < sculptdata->initial_stroke->totalInitialSelected; i++) {
|
|
UvElement *element;
|
|
int uvindex = sculptdata->initial_stroke->initialSelection[i].uv;
|
|
float strength = sculptdata->initial_stroke->initialSelection[i].strength;
|
|
sculptdata->uv[uvindex].uv[0] =
|
|
sculptdata->initial_stroke->initialSelection[i].initial_uv[0] + strength * diff[0];
|
|
sculptdata->uv[uvindex].uv[1] =
|
|
sculptdata->initial_stroke->initialSelection[i].initial_uv[1] + strength * diff[1];
|
|
|
|
for (element = sculptdata->uv[uvindex].element; element; element = element->next) {
|
|
MLoopUV *luv;
|
|
BMLoop *l;
|
|
|
|
if (element->separate && element != sculptdata->uv[uvindex].element) {
|
|
break;
|
|
}
|
|
|
|
l = element->l;
|
|
luv = CustomData_bmesh_get(&em->bm->ldata, l->head.data, CD_MLOOPUV);
|
|
copy_v2_v2(luv->uv, sculptdata->uv[uvindex].uv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void uv_sculpt_stroke_exit(bContext *C, wmOperator *op)
|
|
{
|
|
UvSculptData *data = op->customdata;
|
|
if (data->timer) {
|
|
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), data->timer);
|
|
}
|
|
if (data->elementMap) {
|
|
BM_uv_element_map_free(data->elementMap);
|
|
}
|
|
if (data->uv) {
|
|
MEM_freeN(data->uv);
|
|
}
|
|
if (data->uvedges) {
|
|
MEM_freeN(data->uvedges);
|
|
}
|
|
if (data->initial_stroke) {
|
|
if (data->initial_stroke->initialSelection) {
|
|
MEM_freeN(data->initial_stroke->initialSelection);
|
|
}
|
|
MEM_freeN(data->initial_stroke);
|
|
}
|
|
|
|
MEM_freeN(data);
|
|
op->customdata = NULL;
|
|
}
|
|
|
|
static int uv_element_offset_from_face_get(
|
|
UvElementMap *map, BMFace *efa, BMLoop *l, int island_index, const bool doIslands)
|
|
{
|
|
UvElement *element = BM_uv_element_get(map, efa, l);
|
|
if (!element || (doIslands && element->island != island_index)) {
|
|
return -1;
|
|
}
|
|
return element - map->buf;
|
|
}
|
|
|
|
static uint uv_edge_hash(const void *key)
|
|
{
|
|
const UvEdge *edge = key;
|
|
return (BLI_ghashutil_uinthash(edge->uv2) + BLI_ghashutil_uinthash(edge->uv1));
|
|
}
|
|
|
|
static bool uv_edge_compare(const void *a, const void *b)
|
|
{
|
|
const UvEdge *edge1 = a;
|
|
const UvEdge *edge2 = b;
|
|
|
|
if ((edge1->uv1 == edge2->uv1) && (edge1->uv2 == edge2->uv2)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static UvSculptData *uv_sculpt_stroke_init(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
ToolSettings *ts = scene->toolsettings;
|
|
UvSculptData *data = MEM_callocN(sizeof(*data), "UV Smooth Brush Data");
|
|
BMEditMesh *em = BKE_editmesh_from_object(obedit);
|
|
BMesh *bm = em->bm;
|
|
|
|
op->customdata = data;
|
|
|
|
BKE_curvemapping_init(ts->uvsculpt->paint.brush->curve);
|
|
|
|
if (data) {
|
|
int counter = 0, i;
|
|
ARegion *region = CTX_wm_region(C);
|
|
float co[2];
|
|
BMFace *efa;
|
|
MLoopUV *luv;
|
|
BMLoop *l;
|
|
BMIter iter, liter;
|
|
|
|
UvEdge *edges;
|
|
GHash *edgeHash;
|
|
GHashIterator gh_iter;
|
|
|
|
bool do_island_optimization = !(ts->uv_sculpt_settings & UV_SCULPT_ALL_ISLANDS);
|
|
int island_index = 0;
|
|
/* Holds, for each UvElement in elementMap, a pointer to its unique uv.*/
|
|
int *uniqueUv;
|
|
data->tool = (RNA_enum_get(op->ptr, "mode") == BRUSH_STROKE_SMOOTH) ?
|
|
UV_SCULPT_TOOL_RELAX :
|
|
ts->uvsculpt->paint.brush->uv_sculpt_tool;
|
|
data->invert = (RNA_enum_get(op->ptr, "mode") == BRUSH_STROKE_INVERT) ? 1 : 0;
|
|
|
|
data->uvsculpt = &ts->uvsculpt->paint;
|
|
|
|
if (do_island_optimization) {
|
|
/* We will need island information */
|
|
if (ts->uv_flag & UV_SYNC_SELECTION) {
|
|
data->elementMap = BM_uv_element_map_create(bm, scene, false, false, true, true);
|
|
}
|
|
else {
|
|
data->elementMap = BM_uv_element_map_create(bm, scene, true, false, true, true);
|
|
}
|
|
}
|
|
else {
|
|
if (ts->uv_flag & UV_SYNC_SELECTION) {
|
|
data->elementMap = BM_uv_element_map_create(bm, scene, false, false, true, false);
|
|
}
|
|
else {
|
|
data->elementMap = BM_uv_element_map_create(bm, scene, true, false, true, false);
|
|
}
|
|
}
|
|
|
|
if (!data->elementMap) {
|
|
uv_sculpt_stroke_exit(C, op);
|
|
return NULL;
|
|
}
|
|
|
|
/* Mouse coordinates, useful for some functions like grab and sculpt all islands */
|
|
UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
|
|
|
|
/* we need to find the active island here */
|
|
if (do_island_optimization) {
|
|
UvElement *element;
|
|
UvNearestHit hit = UV_NEAREST_HIT_INIT_MAX(®ion->v2d);
|
|
uv_find_nearest_vert(scene, obedit, co, 0.0f, &hit);
|
|
|
|
element = BM_uv_element_get(data->elementMap, hit.efa, hit.l);
|
|
island_index = element->island;
|
|
}
|
|
|
|
/* Count 'unique' uvs */
|
|
for (i = 0; i < data->elementMap->totalUVs; i++) {
|
|
if (data->elementMap->buf[i].separate &&
|
|
(!do_island_optimization || data->elementMap->buf[i].island == island_index)) {
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
/* Allocate the unique uv buffers */
|
|
data->uv = MEM_mallocN(sizeof(*data->uv) * counter, "uv_brush_unique_uvs");
|
|
uniqueUv = MEM_mallocN(sizeof(*uniqueUv) * data->elementMap->totalUVs,
|
|
"uv_brush_unique_uv_map");
|
|
edgeHash = BLI_ghash_new(uv_edge_hash, uv_edge_compare, "uv_brush_edge_hash");
|
|
/* we have at most totalUVs edges */
|
|
edges = MEM_mallocN(sizeof(*edges) * data->elementMap->totalUVs, "uv_brush_all_edges");
|
|
if (!data->uv || !uniqueUv || !edgeHash || !edges) {
|
|
if (edges) {
|
|
MEM_freeN(edges);
|
|
}
|
|
if (uniqueUv) {
|
|
MEM_freeN(uniqueUv);
|
|
}
|
|
if (edgeHash) {
|
|
BLI_ghash_free(edgeHash, NULL, NULL);
|
|
}
|
|
uv_sculpt_stroke_exit(C, op);
|
|
return NULL;
|
|
}
|
|
|
|
data->totalUniqueUvs = counter;
|
|
/* So that we can use this as index for the UvElements */
|
|
counter = -1;
|
|
/* initialize the unique UVs */
|
|
for (i = 0; i < bm->totvert; i++) {
|
|
UvElement *element = data->elementMap->vert[i];
|
|
for (; element; element = element->next) {
|
|
if (element->separate) {
|
|
if (do_island_optimization && (element->island != island_index)) {
|
|
/* skip this uv if not on the active island */
|
|
for (; element->next && !(element->next->separate); element = element->next) {
|
|
/* pass */
|
|
}
|
|
continue;
|
|
}
|
|
|
|
l = element->l;
|
|
luv = CustomData_bmesh_get(&em->bm->ldata, l->head.data, CD_MLOOPUV);
|
|
|
|
counter++;
|
|
data->uv[counter].element = element;
|
|
data->uv[counter].flag = 0;
|
|
data->uv[counter].uv = luv->uv;
|
|
}
|
|
/* pointer arithmetic to the rescue, as always :)*/
|
|
uniqueUv[element - data->elementMap->buf] = counter;
|
|
}
|
|
}
|
|
|
|
/* Now, on to generate our uv connectivity data */
|
|
counter = 0;
|
|
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
|
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
|
|
int offset1, itmp1 = uv_element_offset_from_face_get(
|
|
data->elementMap, efa, l, island_index, do_island_optimization);
|
|
int offset2, itmp2 = uv_element_offset_from_face_get(
|
|
data->elementMap, efa, l->next, island_index, do_island_optimization);
|
|
char *flag;
|
|
|
|
/* Skip edge if not found(unlikely) or not on valid island */
|
|
if (itmp1 == -1 || itmp2 == -1) {
|
|
continue;
|
|
}
|
|
|
|
offset1 = uniqueUv[itmp1];
|
|
offset2 = uniqueUv[itmp2];
|
|
|
|
edges[counter].flag = 0;
|
|
/* using an order policy, sort uvs according to address space. This avoids
|
|
* Having two different UvEdges with the same uvs on different positions */
|
|
if (offset1 < offset2) {
|
|
edges[counter].uv1 = offset1;
|
|
edges[counter].uv2 = offset2;
|
|
}
|
|
else {
|
|
edges[counter].uv1 = offset2;
|
|
edges[counter].uv2 = offset1;
|
|
}
|
|
/* Hack! Set the value of the key to its flag.
|
|
* Now we can set the flag when an edge exists twice :) */
|
|
flag = BLI_ghash_lookup(edgeHash, &edges[counter]);
|
|
if (flag) {
|
|
*flag = 1;
|
|
}
|
|
else {
|
|
/* Hack mentioned */
|
|
BLI_ghash_insert(edgeHash, &edges[counter], &edges[counter].flag);
|
|
}
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
MEM_freeN(uniqueUv);
|
|
|
|
/* Allocate connectivity data, we allocate edges once */
|
|
data->uvedges = MEM_mallocN(sizeof(*data->uvedges) * BLI_ghash_len(edgeHash),
|
|
"uv_brush_edge_connectivity_data");
|
|
if (!data->uvedges) {
|
|
BLI_ghash_free(edgeHash, NULL, NULL);
|
|
MEM_freeN(edges);
|
|
uv_sculpt_stroke_exit(C, op);
|
|
return NULL;
|
|
}
|
|
|
|
/* fill the edges with data */
|
|
i = 0;
|
|
GHASH_ITER (gh_iter, edgeHash) {
|
|
data->uvedges[i++] = *((UvEdge *)BLI_ghashIterator_getKey(&gh_iter));
|
|
}
|
|
data->totalUvEdges = BLI_ghash_len(edgeHash);
|
|
|
|
/* cleanup temporary stuff */
|
|
BLI_ghash_free(edgeHash, NULL, NULL);
|
|
MEM_freeN(edges);
|
|
|
|
/* transfer boundary edge property to uvs */
|
|
if (ts->uv_sculpt_settings & UV_SCULPT_LOCK_BORDERS) {
|
|
for (i = 0; i < data->totalUvEdges; i++) {
|
|
if (!data->uvedges[i].flag) {
|
|
data->uv[data->uvedges[i].uv1].flag |= MARK_BOUNDARY;
|
|
data->uv[data->uvedges[i].uv2].flag |= MARK_BOUNDARY;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Allocate initial selection for grab tool */
|
|
if (data->tool == UV_SCULPT_TOOL_GRAB) {
|
|
float radius, radius_root;
|
|
UvSculptData *sculptdata = (UvSculptData *)op->customdata;
|
|
SpaceImage *sima;
|
|
int width, height;
|
|
float aspectRatio;
|
|
float alpha, zoomx, zoomy;
|
|
Brush *brush = BKE_paint_brush(sculptdata->uvsculpt);
|
|
|
|
alpha = BKE_brush_alpha_get(scene, brush);
|
|
|
|
radius = BKE_brush_size_get(scene, brush);
|
|
sima = CTX_wm_space_image(C);
|
|
ED_space_image_get_size(sima, &width, &height);
|
|
ED_space_image_get_zoom(sima, region, &zoomx, &zoomy);
|
|
|
|
aspectRatio = width / (float)height;
|
|
radius /= (width * zoomx);
|
|
radius = radius * radius;
|
|
radius_root = sqrtf(radius);
|
|
|
|
/* Allocate selection stack */
|
|
data->initial_stroke = MEM_mallocN(sizeof(*data->initial_stroke),
|
|
"uv_sculpt_initial_stroke");
|
|
if (!data->initial_stroke) {
|
|
uv_sculpt_stroke_exit(C, op);
|
|
}
|
|
data->initial_stroke->initialSelection = MEM_mallocN(
|
|
sizeof(*data->initial_stroke->initialSelection) * data->totalUniqueUvs,
|
|
"uv_sculpt_initial_selection");
|
|
if (!data->initial_stroke->initialSelection) {
|
|
uv_sculpt_stroke_exit(C, op);
|
|
}
|
|
|
|
copy_v2_v2(data->initial_stroke->init_coord, co);
|
|
|
|
counter = 0;
|
|
|
|
for (i = 0; i < data->totalUniqueUvs; i++) {
|
|
float dist, diff[2];
|
|
if (data->uv[i].flag & MARK_BOUNDARY) {
|
|
continue;
|
|
}
|
|
|
|
sub_v2_v2v2(diff, data->uv[i].uv, co);
|
|
diff[1] /= aspectRatio;
|
|
if ((dist = dot_v2v2(diff, diff)) <= radius) {
|
|
float strength;
|
|
strength = alpha * BKE_brush_curve_strength_clamped(brush, sqrtf(dist), radius_root);
|
|
|
|
data->initial_stroke->initialSelection[counter].uv = i;
|
|
data->initial_stroke->initialSelection[counter].strength = strength;
|
|
copy_v2_v2(data->initial_stroke->initialSelection[counter].initial_uv, data->uv[i].uv);
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
data->initial_stroke->totalInitialSelected = counter;
|
|
}
|
|
}
|
|
|
|
return op->customdata;
|
|
}
|
|
|
|
static int uv_sculpt_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
UvSculptData *data;
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
|
|
if (!(data = uv_sculpt_stroke_init(C, op, event))) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
uv_sculpt_stroke_apply(C, op, event, obedit);
|
|
|
|
data->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.001f);
|
|
|
|
if (!data->timer) {
|
|
uv_sculpt_stroke_exit(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static int uv_sculpt_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
UvSculptData *data = (UvSculptData *)op->customdata;
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
|
|
switch (event->type) {
|
|
case LEFTMOUSE:
|
|
case MIDDLEMOUSE:
|
|
case RIGHTMOUSE:
|
|
uv_sculpt_stroke_exit(C, op);
|
|
return OPERATOR_FINISHED;
|
|
|
|
case MOUSEMOVE:
|
|
case INBETWEEN_MOUSEMOVE:
|
|
uv_sculpt_stroke_apply(C, op, event, obedit);
|
|
break;
|
|
case TIMER:
|
|
if (event->customdata == data->timer) {
|
|
uv_sculpt_stroke_apply(C, op, event, obedit);
|
|
}
|
|
break;
|
|
default:
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
ED_region_tag_redraw(CTX_wm_region(C));
|
|
WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
|
|
DEG_id_tag_update(obedit->data, 0);
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static bool uv_sculpt_stroke_poll(bContext *C)
|
|
{
|
|
if (ED_operator_uvedit_space_image(C)) {
|
|
/* While these values could be initialized on demand,
|
|
* the only case this would be useful is running from the operator search popup.
|
|
* This is such a corner case that it's simpler to check a brush has already been created
|
|
* (something the tool system ensures). */
|
|
Scene *scene = CTX_data_scene(C);
|
|
ToolSettings *ts = scene->toolsettings;
|
|
Brush *brush = BKE_paint_brush(&ts->uvsculpt->paint);
|
|
if (brush != NULL) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SCULPT_OT_uv_sculpt_stroke(wmOperatorType *ot)
|
|
{
|
|
static const EnumPropertyItem stroke_mode_items[] = {
|
|
{BRUSH_STROKE_NORMAL, "NORMAL", 0, "Regular", "Apply brush normally"},
|
|
{BRUSH_STROKE_INVERT,
|
|
"INVERT",
|
|
0,
|
|
"Invert",
|
|
"Invert action of brush for duration of stroke"},
|
|
{BRUSH_STROKE_SMOOTH,
|
|
"RELAX",
|
|
0,
|
|
"Relax",
|
|
"Switch brush to relax mode for duration of stroke"},
|
|
{0},
|
|
};
|
|
|
|
/* identifiers */
|
|
ot->name = "Sculpt UVs";
|
|
ot->description = "Sculpt UVs using a brush";
|
|
ot->idname = "SCULPT_OT_uv_sculpt_stroke";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = uv_sculpt_stroke_invoke;
|
|
ot->modal = uv_sculpt_stroke_modal;
|
|
ot->poll = uv_sculpt_stroke_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
/* props */
|
|
RNA_def_enum(ot->srna, "mode", stroke_mode_items, BRUSH_STROKE_NORMAL, "Mode", "Stroke Mode");
|
|
}
|