Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/sculpt_paint/sculpt_dyntopo.c')
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_dyntopo.c1632
1 files changed, 1549 insertions, 83 deletions
diff --git a/source/blender/editors/sculpt_paint/sculpt_dyntopo.c b/source/blender/editors/sculpt_paint/sculpt_dyntopo.c
index ae6dcbdbff4..2d5d428e676 100644
--- a/source/blender/editors/sculpt_paint/sculpt_dyntopo.c
+++ b/source/blender/editors/sculpt_paint/sculpt_dyntopo.c
@@ -23,9 +23,15 @@
#include "MEM_guardedalloc.h"
+#include "BLI_alloca.h"
+#include "BLI_array.h"
#include "BLI_blenlib.h"
+#include "BLI_compiler_attrs.h"
#include "BLI_hash.h"
+#include "BLI_linklist.h"
#include "BLI_math.h"
+#include "BLI_memarena.h"
+#include "BLI_polyfill_2d.h"
#include "BLI_task.h"
#include "BLT_translation.h"
@@ -35,6 +41,7 @@
#include "DNA_modifier_types.h"
#include "BKE_brush.h"
+#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_main.h"
@@ -76,14 +83,417 @@
#include <math.h>
#include <stdlib.h>
-void SCULPT_dynamic_topology_triangulate(BMesh *bm)
+BMesh *SCULPT_dyntopo_empty_bmesh()
{
- if (bm->totloop != bm->totface * 3) {
- BM_mesh_triangulate(
- bm, MOD_TRIANGULATE_QUAD_BEAUTY, MOD_TRIANGULATE_NGON_EARCLIP, 4, false, NULL, NULL, NULL);
+ const BMAllocTemplate allocsize = {
+ .totvert = 2048 * 16, .totface = 2048 * 16, .totloop = 4196 * 16, .totedge = 2048 * 16};
+
+ BMesh *bm = BM_mesh_create(
+ &allocsize,
+ &((struct BMeshCreateParams){.use_toolflags = false,
+ .create_unique_ids = true,
+ .id_elem_mask = BM_VERT | BM_EDGE | BM_FACE,
+ .id_map = true,
+ .temporary_ids = false,
+ .no_reuse_ids = false}));
+
+ return bm;
+}
+// TODO: check if (mathematically speaking) is it really necassary
+// to sort the edge lists around verts
+
+// from http://rodolphe-vaillant.fr/?e=20
+static float tri_voronoi_area(float p[3], float q[3], float r[3])
+{
+ float pr[3];
+ float pq[3];
+
+ sub_v3_v3v3(pr, p, r);
+ sub_v3_v3v3(pq, p, q);
+
+ float angles[3];
+
+ angle_tri_v3(angles, p, q, r);
+
+ if (angles[0] > (float)M_PI * 0.5f) {
+ return area_tri_v3(p, q, r) / 2.0f;
+ }
+ else if (angles[1] > (float)M_PI * 0.5f || angles[2] > (float)M_PI * 0.5f) {
+ return area_tri_v3(p, q, r) / 4.0f;
+ }
+ else {
+
+ float dpr = dot_v3v3(pr, pr);
+ float dpq = dot_v3v3(pq, pq);
+
+ float area = (1.0f / 8.0f) *
+ (dpr * cotangent_tri_weight_v3(q, p, r) + dpq * cotangent_tri_weight_v3(r, q, p));
+
+ return area;
+ }
+}
+
+static float cotangent_tri_weight_v3_proj(const float n[3],
+ const float v1[3],
+ const float v2[3],
+ const float v3[3])
+{
+ float a[3], b[3], c[3], c_len;
+
+ sub_v3_v3v3(a, v2, v1);
+ sub_v3_v3v3(b, v3, v1);
+
+ madd_v3_v3fl(a, n, -dot_v3v3(n, a));
+ madd_v3_v3fl(b, n, -dot_v3v3(n, b));
+
+ cross_v3_v3v3(c, a, b);
+
+ c_len = len_v3(c);
+
+ if (c_len > FLT_EPSILON) {
+ return dot_v3v3(a, b) / c_len;
+ }
+
+ return 0.0f;
+}
+
+void SCULPT_dyntopo_get_cotangents(SculptSession *ss,
+ SculptVertRef vertex,
+ float *r_ws,
+ float *r_cot1,
+ float *r_cot2,
+ float *r_area,
+ float *r_totarea)
+{
+ SCULPT_dyntopo_check_disk_sort(ss, vertex);
+
+ BMVert *v = (BMVert *)vertex.i;
+ BMEdge *e = v->e;
+
+ if (!e) {
+ return;
+ }
+
+ int i = 0;
+ float totarea = 0.0;
+ float totw = 0.0;
+
+ do {
+ BMEdge *eprev = v == e->v1 ? e->v1_disk_link.prev : e->v2_disk_link.prev;
+ BMEdge *enext = v == e->v1 ? e->v1_disk_link.next : e->v2_disk_link.next;
+
+ BMVert *v1 = BM_edge_other_vert(eprev, v);
+ BMVert *v2 = BM_edge_other_vert(e, v);
+ BMVert *v3 = BM_edge_other_vert(enext, v);
+
+ float cot1 = cotangent_tri_weight_v3(v1->co, v->co, v2->co);
+ float cot2 = cotangent_tri_weight_v3(v3->co, v2->co, v->co);
+
+ float area = tri_voronoi_area(v->co, v1->co, v2->co);
+
+ r_ws[i] = (cot1 + cot2);
+ totw += r_ws[i];
+
+ totarea += area;
+
+ if (r_cot1) {
+ r_cot1[i] = cot1;
+ }
+
+ if (r_cot2) {
+ r_cot2[i] = cot2;
+ }
+
+ if (r_area) {
+ r_area[i] = area;
+ }
+
+ i++;
+ e = enext;
+ } while (e != v->e);
+
+ if (r_totarea) {
+ *r_totarea = totarea;
+ }
+
+ int count = i;
+
+ float mul = 1.0f / (totarea * 2.0);
+
+ for (i = 0; i < count; i++) {
+ r_ws[i] *= mul;
}
}
+void SCULPT_faces_get_cotangents(SculptSession *ss,
+ SculptVertRef vertex,
+ float *r_ws,
+ float *r_cot1,
+ float *r_cot2,
+ float *r_area,
+ float *r_totarea)
+{
+ // sculpt vemap should always be sorted in disk cycle order
+
+ float totarea = 0.0;
+ float totw = 0.0;
+
+ MeshElemMap *elem = ss->vemap + vertex.i;
+ for (int i = 0; i < elem->count; i++) {
+ int i1 = (i + elem->count - 1) % elem->count;
+ int i2 = i;
+ int i3 = (i + 1) % elem->count;
+
+ MVert *v = ss->mvert + vertex.i;
+ MEdge *e1 = ss->medge + elem->indices[i1];
+ MEdge *e2 = ss->medge + elem->indices[i2];
+ MEdge *e3 = ss->medge + elem->indices[i3];
+
+ MVert *v1 = (unsigned int)vertex.i == e1->v1 ? ss->mvert + e1->v2 : ss->mvert + e1->v1;
+ MVert *v2 = (unsigned int)vertex.i == e2->v1 ? ss->mvert + e2->v2 : ss->mvert + e2->v1;
+ MVert *v3 = (unsigned int)vertex.i == e3->v1 ? ss->mvert + e3->v2 : ss->mvert + e3->v1;
+
+ float cot1 = cotangent_tri_weight_v3(v1->co, v->co, v2->co);
+ float cot2 = cotangent_tri_weight_v3(v3->co, v2->co, v->co);
+
+ float area = tri_voronoi_area(v->co, v1->co, v2->co);
+
+ r_ws[i] = (cot1 + cot2);
+ totw += r_ws[i];
+
+ totarea += area;
+
+ if (r_cot1) {
+ r_cot1[i] = cot1;
+ }
+
+ if (r_cot2) {
+ r_cot2[i] = cot2;
+ }
+
+ if (r_area) {
+ r_area[i] = area;
+ }
+ }
+
+ if (r_totarea) {
+ *r_totarea = totarea;
+ }
+
+ float mul = 1.0f / (totarea * 2.0);
+
+ for (int i = 0; i < elem->count; i++) {
+ r_ws[i] *= mul;
+ }
+}
+
+void SCULPT_cotangents_begin(Object *ob, SculptSession *ss)
+{
+ SCULPT_vertex_random_access_ensure(ss);
+ int totvert = SCULPT_vertex_count_get(ss);
+
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_BMESH: {
+ for (int i = 0; i < totvert; i++) {
+ SculptVertRef vertex = BKE_pbvh_table_index_to_vertex(ss->pbvh, i);
+ SCULPT_dyntopo_check_disk_sort(ss, vertex);
+ }
+ break;
+ }
+ case PBVH_FACES: {
+ Mesh *mesh = BKE_object_get_original_mesh(ob);
+
+ if (!ss->vemap) {
+ BKE_mesh_vert_edge_map_create(&ss->vemap,
+ &ss->vemap_mem,
+ mesh->mvert,
+ mesh->medge,
+ mesh->totvert,
+ mesh->totedge,
+ true);
+ }
+
+ break;
+ }
+ case PBVH_GRIDS: // not supported yet
+ break;
+ }
+}
+
+void SCULPT_get_cotangents(SculptSession *ss,
+ SculptVertRef vertex,
+ float *r_ws,
+ float *r_cot1,
+ float *r_cot2,
+ float *r_area,
+ float *r_totarea)
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_BMESH:
+ SCULPT_dyntopo_get_cotangents(ss, vertex, r_ws, r_cot1, r_cot2, r_area, r_totarea);
+ break;
+ case PBVH_FACES:
+ SCULPT_faces_get_cotangents(ss, vertex, r_ws, r_cot1, r_cot2, r_area, r_totarea);
+ break;
+ case PBVH_GRIDS: {
+ {
+ // not supported, return uniform weights;
+
+ int val = SCULPT_vertex_valence_get(ss, vertex);
+
+ for (int i = 0; i < val; i++) {
+ r_ws[i] = 1.0f;
+ }
+ }
+ break;
+ }
+ }
+}
+
+void SCULT_dyntopo_flag_all_disk_sort(SculptSession *ss)
+{
+ BKE_pbvh_bmesh_flag_all_disk_sort(ss->pbvh);
+}
+
+// returns true if edge disk list around vertex was sorted
+bool SCULPT_dyntopo_check_disk_sort(SculptSession *ss, SculptVertRef vertex)
+{
+ BMVert *v = (BMVert *)vertex.i;
+ MDynTopoVert *mv = BKE_PBVH_DYNVERT(ss->cd_dyn_vert, v);
+
+ if (mv->flag & DYNVERT_NEED_DISK_SORT) {
+ mv->flag &= ~DYNVERT_NEED_DISK_SORT;
+
+ BM_sort_disk_cycle(v);
+
+ return true;
+ }
+
+ return false;
+}
+
+/*
+Copies the bmesh, but orders the elements
+according to PBVH node to improve memory locality
+*/
+void SCULPT_reorder_bmesh(SculptSession *ss)
+{
+#if 0
+ SCULPT_face_random_access_ensure(ss);
+ SCULPT_vertex_random_access_ensure(ss);
+
+ int actv = ss->active_vertex_index.i ?
+ BKE_pbvh_vertex_index_to_table(ss->pbvh, ss->active_vertex_index) :
+ -1;
+ int actf = ss->active_face_index.i ?
+ BKE_pbvh_face_index_to_table(ss->pbvh, ss->active_face_index) :
+ -1;
+
+ if (ss->bm_log) {
+ BM_log_full_mesh(ss->bm, ss->bm_log);
+ }
+
+ ss->bm = BKE_pbvh_reorder_bmesh(ss->pbvh);
+
+ SCULPT_face_random_access_ensure(ss);
+ SCULPT_vertex_random_access_ensure(ss);
+
+ if (actv >= 0) {
+ ss->active_vertex_index = BKE_pbvh_table_index_to_vertex(ss->pbvh, actv);
+ }
+ if (actf >= 0) {
+ ss->active_face_index = BKE_pbvh_table_index_to_face(ss->pbvh, actf);
+ }
+
+ SCULPT_dyntopo_node_layers_update_offsets(ss);
+
+ if (ss->bm_log) {
+ BM_log_set_bm(ss->bm, ss->bm_log);
+ }
+#endif
+}
+
+void SCULPT_dynamic_topology_triangulate(SculptSession *ss, BMesh *bm)
+{
+ if (bm->totloop == bm->totface * 3) {
+ ss->totfaces = ss->totpoly = ss->bm->totface;
+ ss->totvert = ss->bm->totvert;
+
+ return;
+ }
+
+ BMIter iter;
+ BMFace *f;
+
+ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
+ BM_elem_flag_enable(f, BM_ELEM_TAG);
+ }
+
+ MemArena *pf_arena = BLI_memarena_new(BLI_POLYFILL_ARENA_SIZE, __func__);
+ LinkNode *f_double = NULL;
+
+ BMFace **faces_array = NULL;
+ BLI_array_declare(faces_array);
+
+ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
+ if (f->len <= 3) {
+ continue;
+ }
+
+ bool sel = BM_elem_flag_test(f, BM_ELEM_SELECT);
+
+ int faces_array_tot = f->len;
+ BLI_array_clear(faces_array);
+ BLI_array_grow_items(faces_array, faces_array_tot);
+ // BMFace **faces_array = BLI_array_alloca(faces_array, faces_array_tot);
+
+ BM_face_triangulate(bm,
+ f,
+ faces_array,
+ &faces_array_tot,
+ NULL,
+ NULL,
+ &f_double,
+ MOD_TRIANGULATE_QUAD_BEAUTY,
+ MOD_TRIANGULATE_NGON_EARCLIP,
+ true,
+ pf_arena,
+ NULL);
+
+ for (int i = 0; i < faces_array_tot; i++) {
+ BMFace *f2 = faces_array[i];
+
+ // forcibly copy selection state
+ if (sel) {
+ BM_face_select_set(bm, f2, true);
+
+ // restore original face selection state too, triangulate code unset it
+ BM_face_select_set(bm, f, true);
+ }
+
+ // paranoia check that tag flag wasn't copied over
+ BM_elem_flag_disable(f2, BM_ELEM_TAG);
+ }
+ }
+
+ while (f_double) {
+ LinkNode *next = f_double->next;
+ BM_face_kill(bm, f_double->link);
+ MEM_freeN(f_double);
+ f_double = next;
+ }
+
+ BLI_memarena_free(pf_arena);
+ MEM_SAFE_FREE(faces_array);
+
+ ss->totfaces = ss->totpoly = ss->bm->totface;
+ ss->totvert = ss->bm->totvert;
+
+ // BM_mesh_triangulate(
+ // bm, MOD_TRIANGULATE_QUAD_BEAUTY, MOD_TRIANGULATE_NGON_EARCLIP, 4, false, NULL, NULL,
+ // NULL);
+}
+
void SCULPT_pbvh_clear(Object *ob)
{
SculptSession *ss = ob->sculpt;
@@ -104,18 +514,103 @@ void SCULPT_pbvh_clear(Object *ob)
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
-void SCULPT_dyntopo_node_layers_add(SculptSession *ss)
+void SCULPT_dyntopo_save_origverts(SculptSession *ss)
+{
+ BMIter iter;
+ BMVert *v;
+
+ BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) {
+ MDynTopoVert *mv = BKE_PBVH_DYNVERT(ss->cd_dyn_vert, v);
+
+ copy_v3_v3(mv->origco, v->co);
+ copy_v3_v3(mv->origno, v->no);
+
+ if (ss->cd_vcol_offset >= 0) {
+ MPropCol *mp = (MPropCol *)BM_ELEM_CD_GET_VOID_P(v, ss->cd_vcol_offset);
+ copy_v4_v4(mv->origcolor, mp->color);
+ }
+ }
+}
+
+char dyntopop_node_idx_layer_id[] = "_dyntopo_node_id";
+
+void SCULPT_dyntopo_node_layers_update_offsets(SculptSession *ss)
+{
+ SCULPT_dyntopo_node_layers_add(ss);
+ if (ss->pbvh) {
+ BKE_pbvh_update_offsets(ss->pbvh,
+ ss->cd_vert_node_offset,
+ ss->cd_face_node_offset,
+ ss->cd_dyn_vert,
+ ss->cd_face_areas);
+ }
+ if (ss->bm_log) {
+ BM_log_set_cd_offsets(ss->bm_log, ss->cd_dyn_vert);
+ }
+}
+
+bool SCULPT_dyntopo_has_templayer(SculptSession *ss, int type, const char *name)
{
- int cd_node_layer_index;
+ return CustomData_get_named_layer_index(&ss->bm->vdata, type, name) >= 0;
+}
- char layer_id[] = "_dyntopo_node_id";
+void SCULPT_dyntopo_ensure_templayer(SculptSession *ss, int type, const char *name)
+{
+ int li = CustomData_get_named_layer_index(&ss->bm->vdata, type, name);
- cd_node_layer_index = CustomData_get_named_layer_index(&ss->bm->vdata, CD_PROP_INT32, layer_id);
- if (cd_node_layer_index == -1) {
- BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_PROP_INT32, layer_id);
- cd_node_layer_index = CustomData_get_named_layer_index(
- &ss->bm->vdata, CD_PROP_INT32, layer_id);
+ if (li < 0) {
+ BM_data_layer_add_named(ss->bm, &ss->bm->vdata, type, name);
+ SCULPT_dyntopo_node_layers_update_offsets(ss);
+
+ li = CustomData_get_named_layer_index(&ss->bm->vdata, type, name);
+ ss->bm->vdata.layers[li].flag |= CD_FLAG_TEMPORARY;
}
+}
+
+int SCULPT_dyntopo_get_templayer(SculptSession *ss, int type, const char *name)
+{
+ int li = CustomData_get_named_layer_index(&ss->bm->vdata, type, name);
+
+ if (li < 0) {
+ return -1;
+ }
+
+ return CustomData_get_n_offset(
+ &ss->bm->vdata, type, li - CustomData_get_layer_index(&ss->bm->vdata, type));
+}
+
+char dyntopop_faces_areas_layer_id[] = "__dyntopo_face_areas";
+
+void SCULPT_dyntopo_node_layers_add(SculptSession *ss)
+{
+ int cd_node_layer_index, cd_face_node_layer_index;
+
+ int cd_origco_index, cd_origno_index, cd_origvcol_index = -1;
+ bool have_vcol = CustomData_has_layer(&ss->bm->vdata, CD_PROP_COLOR);
+
+ BMCustomLayerReq vlayers[] = {{CD_PAINT_MASK, NULL, 0},
+ {CD_DYNTOPO_VERT, NULL, CD_FLAG_TEMPORARY},
+ {CD_PROP_INT32, dyntopop_node_idx_layer_id, CD_FLAG_TEMPORARY}};
+
+ BM_data_layers_ensure(ss->bm, &ss->bm->vdata, vlayers, 3);
+
+ BMCustomLayerReq flayers[] = {
+ {CD_PROP_INT32, dyntopop_node_idx_layer_id, CD_FLAG_TEMPORARY},
+ {CD_PROP_FLOAT, dyntopop_faces_areas_layer_id, CD_FLAG_TEMPORARY},
+ };
+ BM_data_layers_ensure(ss->bm, &ss->bm->pdata, flayers, 2);
+
+ // get indices again, as they might have changed after adding new layers
+ cd_node_layer_index = CustomData_get_named_layer_index(
+ &ss->bm->vdata, CD_PROP_INT32, dyntopop_node_idx_layer_id);
+ cd_face_node_layer_index = CustomData_get_named_layer_index(
+ &ss->bm->pdata, CD_PROP_INT32, dyntopop_node_idx_layer_id);
+
+ ss->cd_origvcol_offset = -1;
+
+ ss->cd_dyn_vert = CustomData_get_offset(&ss->bm->vdata, CD_DYNTOPO_VERT);
+
+ ss->cd_vcol_offset = CustomData_get_offset(&ss->bm->vdata, CD_PROP_COLOR);
ss->cd_vert_node_offset = CustomData_get_n_offset(
&ss->bm->vdata,
@@ -124,51 +619,272 @@ void SCULPT_dyntopo_node_layers_add(SculptSession *ss)
ss->bm->vdata.layers[cd_node_layer_index].flag |= CD_FLAG_TEMPORARY;
- cd_node_layer_index = CustomData_get_named_layer_index(&ss->bm->pdata, CD_PROP_INT32, layer_id);
- if (cd_node_layer_index == -1) {
- BM_data_layer_add_named(ss->bm, &ss->bm->pdata, CD_PROP_INT32, layer_id);
- cd_node_layer_index = CustomData_get_named_layer_index(
- &ss->bm->pdata, CD_PROP_INT32, layer_id);
- }
-
ss->cd_face_node_offset = CustomData_get_n_offset(
&ss->bm->pdata,
CD_PROP_INT32,
- cd_node_layer_index - CustomData_get_layer_index(&ss->bm->pdata, CD_PROP_INT32));
+ cd_face_node_layer_index - CustomData_get_layer_index(&ss->bm->pdata, CD_PROP_INT32));
- ss->bm->pdata.layers[cd_node_layer_index].flag |= CD_FLAG_TEMPORARY;
+ ss->bm->pdata.layers[cd_face_node_layer_index].flag |= CD_FLAG_TEMPORARY;
+ ss->cd_faceset_offset = CustomData_get_offset(&ss->bm->pdata, CD_SCULPT_FACE_SETS);
+
+ ss->cd_face_areas = CustomData_get_named_layer(
+ &ss->bm->pdata, CD_PROP_FLOAT, dyntopop_faces_areas_layer_id);
+ ss->cd_face_areas = ss->bm->pdata.layers[ss->cd_face_areas].offset;
}
+/**
+ Syncs customdata layers with internal bmesh, but ignores deleted layers.
+*/
+void SCULPT_dynamic_topology_sync_layers(Object *ob, Mesh *me)
+{
+ SculptSession *ss = ob->sculpt;
+
+ if (!ss || !ss->bm) {
+ return;
+ }
+
+ bool modified = false;
+ BMesh *bm = ss->bm;
+
+ CustomData *cd1[4] = {&me->vdata, &me->edata, &me->ldata, &me->pdata};
+ CustomData *cd2[4] = {&bm->vdata, &bm->edata, &bm->ldata, &bm->pdata};
+ int types[4] = {BM_VERT, BM_EDGE, BM_LOOP, BM_FACE};
+ int badmask = CD_MASK_MLOOP | CD_MASK_MVERT | CD_MASK_MEDGE | CD_MASK_MPOLY | CD_MASK_ORIGINDEX |
+ CD_MASK_ORIGSPACE | CD_MASK_MFACE;
+
+ for (int i = 0; i < 4; i++) {
+ CustomDataLayer **newlayers = NULL;
+ BLI_array_declare(newlayers);
+
+ CustomData *data1 = cd1[i];
+ CustomData *data2 = cd2[i];
+
+ if (!data1->layers) {
+ modified |= data2->layers != NULL;
+ continue;
+ }
+
+ for (int j = 0; j < data1->totlayer; j++) {
+ CustomDataLayer *cl1 = data1->layers + j;
+
+ if ((1 << cl1->type) & badmask) {
+ continue;
+ }
+
+ int idx = CustomData_get_named_layer_index(data2, cl1->type, cl1->name);
+ if (idx < 0) {
+ BLI_array_append(newlayers, cl1);
+ }
+ }
+
+ for (int j = 0; j < BLI_array_len(newlayers); j++) {
+ BM_data_layer_add_named(bm, data2, newlayers[j]->type, newlayers[j]->name);
+ modified = true;
+ }
+
+ bool typemap[CD_NUMTYPES] = {0};
+
+ for (int j = 0; j < data1->totlayer; j++) {
+ CustomDataLayer *cl1 = data1->layers + j;
+
+ if ((1 << cl1->type) & badmask) {
+ continue;
+ }
+
+ if (typemap[cl1->type]) {
+ continue;
+ }
+
+ typemap[cl1->type] = true;
+
+ // find first layer
+ int baseidx = CustomData_get_layer_index(data2, cl1->type);
+
+ if (baseidx < 0) {
+ modified |= true;
+ continue;
+ }
+
+ CustomDataLayer *cl2 = data2->layers + baseidx;
+
+ int idx = CustomData_get_named_layer_index(data2, cl1->type, cl1[cl1->active].name);
+ if (idx >= 0) {
+ modified |= idx - baseidx != cl2->active;
+ cl2->active = idx - baseidx;
+ }
+
+ idx = CustomData_get_named_layer_index(data2, cl1->type, cl1[cl1->active_rnd].name);
+ if (idx >= 0) {
+ modified |= idx - baseidx != cl2->active_rnd;
+ cl2->active_rnd = idx - baseidx;
+ }
+
+ idx = CustomData_get_named_layer_index(data2, cl1->type, cl1[cl1->active_mask].name);
+ if (idx >= 0) {
+ modified |= idx - baseidx != cl2->active_mask;
+ cl2->active_mask = idx - baseidx;
+ }
+
+ idx = CustomData_get_named_layer_index(data2, cl1->type, cl1[cl1->active_clone].name);
+ if (idx >= 0) {
+ modified |= idx - baseidx != cl2->active_clone;
+ cl2->active_clone = idx - baseidx;
+ }
+
+ for (int k = baseidx; k < data2->totlayer; k++) {
+ CustomDataLayer *cl3 = data2->layers + k;
+
+ if (cl3->type != cl2->type) {
+ break;
+ }
+
+ // based off of how CustomData_set_layer_XXXX_index works
+
+ cl3->active = (cl2->active + baseidx) - k;
+ cl3->active_rnd = (cl2->active_rnd + baseidx) - k;
+ cl3->active_mask = (cl2->active_mask + baseidx) - k;
+ cl3->active_clone = (cl2->active_clone + baseidx) - k;
+ }
+ }
+
+ BLI_array_free(newlayers);
+ }
+
+ if (modified) {
+ SCULPT_dyntopo_node_layers_update_offsets(ss);
+ }
+}
+
+BMesh *BM_mesh_bm_from_me_threaded(BMesh *bm,
+ Object *ob,
+ const Mesh *me,
+ const struct BMeshFromMeshParams *params);
+
void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
{
SculptSession *ss = ob->sculpt;
Mesh *me = ob->data;
- const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(me);
+ const BMAllocTemplate allocsize = {
+ .totvert = 2048 * 16, .totface = 2048 * 16, .totloop = 4196 * 16, .totedge = 2048 * 16};
SCULPT_pbvh_clear(ob);
+ if (ss->mdyntopo_verts) {
+ MEM_freeN(ss->mdyntopo_verts);
+ ss->mdyntopo_verts = NULL;
+ }
+
ss->bm_smooth_shading = (scene->toolsettings->sculpt->flags & SCULPT_DYNTOPO_SMOOTH_SHADING) !=
0;
/* Dynamic topology doesn't ensure selection state is valid, so remove T36280. */
BKE_mesh_mselect_clear(me);
- /* Create triangles-only BMesh. */
+#if 1
ss->bm = BM_mesh_create(&allocsize,
- &((struct BMeshCreateParams){
- .use_toolflags = false,
- }));
-
- BM_mesh_bm_from_me(ss->bm,
+ &((struct BMeshCreateParams){.use_toolflags = false,
+ .create_unique_ids = true,
+ .id_elem_mask = BM_VERT | BM_EDGE | BM_FACE,
+ .id_map = true,
+ .temporary_ids = false,
+ .no_reuse_ids = false}));
+
+ BM_mesh_bm_from_me(NULL,
+ ss->bm,
me,
(&(struct BMeshFromMeshParams){
.calc_face_normal = true,
.use_shapekey = true,
.active_shapekey = ob->shapenr,
}));
- SCULPT_dynamic_topology_triangulate(ss->bm);
- BM_data_layer_add(ss->bm, &ss->bm->vdata, CD_PAINT_MASK);
+#else
+ ss->bm = BM_mesh_bm_from_me_threaded(NULL,
+ NULL,
+ me,
+ (&(struct BMeshFromMeshParams){
+ .calc_face_normal = true,
+ .use_shapekey = true,
+ .active_shapekey = ob->shapenr,
+ }));
+#endif
+
+#ifndef DYNTOPO_DYNAMIC_TESS
+ SCULPT_dynamic_topology_triangulate(ss, ss->bm);
+#endif
+
SCULPT_dyntopo_node_layers_add(ss);
+ SCULPT_dyntopo_save_origverts(ss);
+
+ BMIter iter;
+ BMVert *v;
+
+ int cd_pers_co = -1, cd_pers_no = -1, cd_pers_disp = -1;
+ int cd_layer_disp = -1;
+
+ // convert layer brush data
+ if (ss->persistent_base) {
+ BMCustomLayerReq layers[] = {{CD_PROP_FLOAT3, SCULPT_LAYER_PERS_CO, CD_FLAG_TEMPORARY},
+ {CD_PROP_FLOAT3, SCULPT_LAYER_PERS_NO, CD_FLAG_TEMPORARY},
+ {CD_PROP_FLOAT, SCULPT_LAYER_PERS_DISP, CD_FLAG_TEMPORARY},
+ {CD_PROP_FLOAT, SCULPT_LAYER_DISP, CD_FLAG_TEMPORARY}};
+
+ BM_data_layers_ensure(ss->bm, &ss->bm->vdata, layers, 4);
+
+ cd_pers_co = SCULPT_dyntopo_get_templayer(ss, CD_PROP_FLOAT3, SCULPT_LAYER_PERS_CO);
+ cd_pers_no = SCULPT_dyntopo_get_templayer(ss, CD_PROP_FLOAT3, SCULPT_LAYER_PERS_NO);
+ cd_pers_disp = SCULPT_dyntopo_get_templayer(ss, CD_PROP_FLOAT, SCULPT_LAYER_PERS_DISP);
+ cd_layer_disp = SCULPT_dyntopo_get_templayer(ss, CD_PROP_FLOAT, SCULPT_LAYER_DISP);
+ }
+ else {
+ cd_layer_disp = SCULPT_dyntopo_get_templayer(ss, CD_PROP_FLOAT, SCULPT_LAYER_PERS_DISP);
+ }
+
+ int cd_vcol_offset = CustomData_get_offset(&ss->bm->vdata, CD_PROP_COLOR);
+ SCULPT_dyntopo_node_layers_update_offsets(ss);
+
+ int i = 0;
+ BMEdge *e;
+
+ BM_ITER_MESH (e, &iter, ss->bm, BM_EDGES_OF_MESH) {
+ e->head.hflag |= BM_ELEM_DRAW;
+ }
+
+ BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) {
+ MDynTopoVert *mv = BKE_PBVH_DYNVERT(ss->cd_dyn_vert, v);
+
+ mv->flag |= DYNVERT_NEED_DISK_SORT | DYNVERT_NEED_VALENCE;
+
+ BKE_pbvh_update_vert_boundary(
+ ss->cd_dyn_vert, ss->cd_faceset_offset, v, ss->boundary_symmetry);
+ BKE_pbvh_bmesh_update_valence(ss->cd_dyn_vert, (SculptVertRef){.i = (intptr_t)v});
+
+ // persistent base
+ if (cd_pers_co >= 0) {
+ float *co = BM_ELEM_CD_GET_VOID_P(v, cd_pers_co);
+ float *no = BM_ELEM_CD_GET_VOID_P(v, cd_pers_no);
+ float *disp = BM_ELEM_CD_GET_VOID_P(v, cd_pers_disp);
+
+ copy_v3_v3(co, ss->persistent_base[i].co);
+ copy_v3_v3(no, ss->persistent_base[i].no);
+ *disp = ss->persistent_base[i].disp;
+ }
+
+ if (cd_layer_disp >= 0) {
+ float *disp = BM_ELEM_CD_GET_VOID_P(v, cd_layer_disp);
+ *disp = 0.0f;
+ }
+
+ copy_v3_v3(mv->origco, v->co);
+ copy_v3_v3(mv->origno, v->no);
+
+ if (ss->cd_vcol_offset >= 0) {
+ MPropCol *color = (MPropCol *)BM_ELEM_CD_GET_VOID_P(v, cd_vcol_offset);
+ copy_v4_v4(mv->origcolor, color->color);
+ }
+
+ i++;
+ }
+
/* Make sure the data for existing faces are initialized. */
if (me->totpoly != ss->bm->totface) {
BM_mesh_normals_update(ss->bm);
@@ -178,14 +894,46 @@ void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Scene
me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY;
/* Enable logging for undo/redo. */
- ss->bm_log = BM_log_create(ss->bm);
+ if (!ss->bm_log) {
+ ss->bm_log = BM_log_create(ss->bm, ss->cd_dyn_vert);
+ }
/* Update dependency graph, so modifiers that depend on dyntopo being enabled
* are re-evaluated and the PBVH is re-created. */
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+
+ // TODO: this line here is being slow, do we need it? - joeedh
BKE_scene_graph_update_tagged(depsgraph, bmain);
}
+void SCULPT_dyntopo_save_persistent_base(SculptSession *ss)
+{
+ int cd_pers_co = SCULPT_dyntopo_get_templayer(ss, CD_PROP_FLOAT3, SCULPT_LAYER_PERS_CO);
+ int cd_pers_no = SCULPT_dyntopo_get_templayer(ss, CD_PROP_FLOAT3, SCULPT_LAYER_PERS_NO);
+ int cd_pers_disp = SCULPT_dyntopo_get_templayer(ss, CD_PROP_FLOAT, SCULPT_LAYER_PERS_DISP);
+
+ if (cd_pers_co >= 0) {
+ BMIter iter;
+
+ MEM_SAFE_FREE(ss->persistent_base);
+ ss->persistent_base = MEM_callocN(sizeof(*ss->persistent_base) * ss->bm->totvert,
+ "ss->persistent_base");
+ BMVert *v;
+ int i = 0;
+
+ BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) {
+ float *co = BM_ELEM_CD_GET_VOID_P(v, cd_pers_co);
+ float *no = BM_ELEM_CD_GET_VOID_P(v, cd_pers_no);
+ float *disp = BM_ELEM_CD_GET_VOID_P(v, cd_pers_disp);
+
+ copy_v3_v3(ss->persistent_base[i].co, co);
+ copy_v3_v3(ss->persistent_base[i].no, no);
+ ss->persistent_base[i].disp = *disp;
+
+ i++;
+ }
+ }
+}
/* Free the sculpt BMesh and BMLog
*
* If 'unode' is given, the BMesh's data is copied out to the unode
@@ -198,64 +946,41 @@ static void SCULPT_dynamic_topology_disable_ex(
SCULPT_pbvh_clear(ob);
- if (unode) {
- /* Free all existing custom data. */
- CustomData_free(&me->vdata, me->totvert);
- CustomData_free(&me->edata, me->totedge);
- CustomData_free(&me->fdata, me->totface);
- CustomData_free(&me->ldata, me->totloop);
- CustomData_free(&me->pdata, me->totpoly);
-
- /* Copy over stored custom data. */
- SculptUndoNodeGeometry *geometry = &unode->geometry_bmesh_enter;
- me->totvert = geometry->totvert;
- me->totloop = geometry->totloop;
- me->totpoly = geometry->totpoly;
- me->totedge = geometry->totedge;
- me->totface = 0;
- CustomData_copy(
- &geometry->vdata, &me->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, geometry->totvert);
- CustomData_copy(
- &geometry->edata, &me->edata, CD_MASK_MESH.emask, CD_DUPLICATE, geometry->totedge);
- CustomData_copy(
- &geometry->ldata, &me->ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, geometry->totloop);
- CustomData_copy(
- &geometry->pdata, &me->pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, geometry->totpoly);
-
- BKE_mesh_update_customdata_pointers(me, false);
- }
- else {
- BKE_sculptsession_bm_to_me(ob, true);
-
- /* Reset Face Sets as they are no longer valid. */
- if (!CustomData_has_layer(&me->pdata, CD_SCULPT_FACE_SETS)) {
- CustomData_add_layer(&me->pdata, CD_SCULPT_FACE_SETS, CD_CALLOC, NULL, me->totpoly);
- }
- ss->face_sets = CustomData_get_layer(&me->pdata, CD_SCULPT_FACE_SETS);
- for (int i = 0; i < me->totpoly; i++) {
- ss->face_sets[i] = 1;
- }
- me->face_sets_color_default = 1;
+ BKE_sculptsession_bm_to_me(ob, true);
- /* Sync the visibility to vertices manually as the pmap is still not initialized. */
- for (int i = 0; i < me->totvert; i++) {
- me->mvert[i].flag &= ~ME_HIDE;
- me->mvert[i].flag |= ME_VERT_PBVH_UPDATE;
- }
+ /* Sync the visibility to vertices manually as the pmap is still not initialized. */
+ for (int i = 0; i < me->totvert; i++) {
+ me->mvert[i].flag &= ~ME_HIDE;
+ me->mvert[i].flag |= ME_VERT_PBVH_UPDATE;
}
/* Clear data. */
me->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY;
+ bool disp_saved = false;
+
+ if (ss->bm_log) {
+ if (ss->bm) {
+ disp_saved = true;
+
+ // rebuild ss->persistent_base if necassary
+ SCULPT_dyntopo_save_persistent_base(ss);
+ }
+
+ BM_log_free(ss->bm_log, true);
+ ss->bm_log = NULL;
+ }
+
/* Typically valid but with global-undo they can be NULL, see: T36234. */
if (ss->bm) {
+ if (!disp_saved) {
+ // rebuild ss->persistent_base if necassary
+ SCULPT_dyntopo_save_persistent_base(ss);
+ }
+
BM_mesh_free(ss->bm);
ss->bm = NULL;
}
- if (ss->bm_log) {
- BM_log_free(ss->bm_log);
- ss->bm_log = NULL;
- }
BKE_particlesystem_reset_all(ob);
BKE_ptcache_object_reset(scene, ob, PTCACHE_RESET_OUTDATED);
@@ -292,6 +1017,8 @@ void sculpt_dynamic_topology_disable_with_undo(Main *bmain,
if (use_undo) {
SCULPT_undo_push_end();
}
+
+ ss->active_vertex_index.i = ss->active_face_index.i = 0;
}
}
@@ -301,6 +1028,7 @@ static void sculpt_dynamic_topology_enable_with_undo(Main *bmain,
Object *ob)
{
SculptSession *ss = ob->sculpt;
+
if (ss->bm == NULL) {
/* May be false in background mode. */
const bool use_undo = G.background ? (ED_undo_stack_get() != NULL) : true;
@@ -312,6 +1040,8 @@ static void sculpt_dynamic_topology_enable_with_undo(Main *bmain,
SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_BEGIN);
SCULPT_undo_push_end();
}
+
+ ss->active_vertex_index.i = ss->active_face_index.i = 0;
}
}
@@ -338,14 +1068,33 @@ static int sculpt_dynamic_topology_toggle_exec(bContext *C, wmOperator *UNUSED(o
return OPERATOR_FINISHED;
}
+static int dyntopo_error_popup(bContext *C, wmOperatorType *ot, enum eDynTopoWarnFlag flag)
+{
+ uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Error!"), ICON_ERROR);
+ uiLayout *layout = UI_popup_menu_layout(pup);
+
+ if (flag & DYNTOPO_ERROR_MULTIRES) {
+ const char *msg_error = TIP_("Multires modifier detected; cannot enable dyntopo.");
+ const char *msg = TIP_("Dyntopo and multires cannot be mixed.");
+
+ uiItemL(layout, msg_error, ICON_INFO);
+ uiItemL(layout, msg, ICON_NONE);
+ uiItemS(layout);
+ }
+
+ UI_popup_menu_end(C, pup);
+
+ return OPERATOR_INTERFACE;
+}
+
static int dyntopo_warning_popup(bContext *C, wmOperatorType *ot, enum eDynTopoWarnFlag flag)
{
uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Warning!"), ICON_ERROR);
uiLayout *layout = UI_popup_menu_layout(pup);
- if (flag & (DYNTOPO_WARN_VDATA | DYNTOPO_WARN_EDATA | DYNTOPO_WARN_LDATA)) {
- const char *msg_error = TIP_("Vertex Data Detected!");
- const char *msg = TIP_("Dyntopo will not preserve vertex colors, UVs, or other customdata");
+ if (flag & (DYNTOPO_WARN_EDATA)) {
+ const char *msg_error = TIP_("Edge Data Detected!");
+ const char *msg = TIP_("Dyntopo will not preserve custom edge attributes");
uiItemL(layout, msg_error, ICON_INFO);
uiItemL(layout, msg, ICON_NONE);
uiItemS(layout);
@@ -378,6 +1127,7 @@ enum eDynTopoWarnFlag SCULPT_dynamic_topology_check(Scene *scene, Object *ob)
BLI_assert(ss->bm == NULL);
UNUSED_VARS_NDEBUG(ss);
+#ifndef DYNTOPO_CD_INTERP
for (int i = 0; i < CD_NUMTYPES; i++) {
if (!ELEM(i, CD_MVERT, CD_MEDGE, CD_MFACE, CD_MLOOP, CD_MPOLY, CD_PAINT_MASK, CD_ORIGINDEX)) {
if (CustomData_has_layer(&me->vdata, i)) {
@@ -391,6 +1141,7 @@ enum eDynTopoWarnFlag SCULPT_dynamic_topology_check(Scene *scene, Object *ob)
}
}
}
+#endif
{
VirtualModifierData virtualModifierData;
@@ -403,6 +1154,10 @@ enum eDynTopoWarnFlag SCULPT_dynamic_topology_check(Scene *scene, Object *ob)
continue;
}
+ if (md->type == eModifierType_Multires) {
+ flag |= DYNTOPO_ERROR_MULTIRES;
+ }
+
if (mti->type == eModifierTypeType_Constructive) {
flag |= DYNTOPO_WARN_MODIFIER;
break;
@@ -424,7 +1179,10 @@ static int sculpt_dynamic_topology_toggle_invoke(bContext *C,
Scene *scene = CTX_data_scene(C);
enum eDynTopoWarnFlag flag = SCULPT_dynamic_topology_check(scene, ob);
- if (flag) {
+ if (flag & DYNTOPO_ERROR_MULTIRES) {
+ return dyntopo_error_popup(C, op->type, flag);
+ }
+ else if (flag) {
/* The mesh has customdata that will be lost, let the user confirm this is OK. */
return dyntopo_warning_popup(C, op->type, flag);
}
@@ -438,7 +1196,10 @@ void SCULPT_OT_dynamic_topology_toggle(wmOperatorType *ot)
/* Identifiers. */
ot->name = "Dynamic Topology Toggle";
ot->idname = "SCULPT_OT_dynamic_topology_toggle";
- ot->description = "Dynamic topology alters the mesh topology while sculpting";
+ ot->description =
+ "Dynamic mode; note that you must now check the DynTopo"
+ "option to enable dynamic remesher (which updates topology will sculpting)"
+ "this is on by default.";
/* API callbacks. */
ot->invoke = sculpt_dynamic_topology_toggle_invoke;
@@ -447,3 +1208,708 @@ void SCULPT_OT_dynamic_topology_toggle(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
+
+#define MAXUVLOOPS 32
+#define MAXUVNEIGHBORS 32
+
+typedef struct UVSmoothVert {
+ double uv[2];
+ float co[3]; // world co
+ BMVert *v;
+ double w;
+ int totw;
+ bool pinned, boundary;
+ BMLoop *ls[MAXUVLOOPS];
+ struct UVSmoothVert *neighbors[MAXUVNEIGHBORS];
+ int totloop, totneighbor;
+} UVSmoothVert;
+
+typedef struct UVSmoothTri {
+ UVSmoothVert *vs[3];
+ float area2d, area3d;
+} UVSmoothTri;
+
+#define CON_MAX_VERTS 16
+typedef struct UVSmoothConstraint {
+ int type;
+ double k;
+ UVSmoothVert *vs[CON_MAX_VERTS];
+ UVSmoothTri *tri;
+ double gs[CON_MAX_VERTS][2];
+ int totvert;
+ double params[8];
+} UVSmoothConstraint;
+
+enum { CON_ANGLES = 0, CON_AREA = 1 };
+
+typedef struct UVSolver {
+ BLI_mempool *verts;
+ BLI_mempool *tris;
+ int totvert, tottri;
+ float snap_limit;
+ BLI_mempool *constraints;
+ GHash *vhash;
+ GHash *fhash;
+ int cd_uv;
+
+ double totarea3d;
+ double totarea2d;
+
+ double strength;
+} UVSolver;
+
+/*that that currently this tool is *not* threaded*/
+
+typedef struct SculptUVThreadData {
+ SculptThreadedTaskData data;
+ UVSolver *solver;
+} SculptUVThreadData;
+
+static UVSolver *uvsolver_new(int cd_uv)
+{
+ UVSolver *solver = MEM_callocN(sizeof(*solver), "solver");
+
+ solver->strength = 1.0;
+ solver->cd_uv = cd_uv;
+ solver->snap_limit = 0.0025;
+
+ solver->verts = BLI_mempool_create(sizeof(UVSmoothVert), 0, 512, BLI_MEMPOOL_ALLOW_ITER);
+ solver->tris = BLI_mempool_create(sizeof(UVSmoothTri), 0, 512, BLI_MEMPOOL_ALLOW_ITER);
+ solver->constraints = BLI_mempool_create(
+ sizeof(UVSmoothConstraint), 0, 512, BLI_MEMPOOL_ALLOW_ITER);
+
+ solver->vhash = BLI_ghash_ptr_new("uvsolver");
+ solver->fhash = BLI_ghash_ptr_new("uvsolver");
+
+ return solver;
+}
+
+static void uvsolver_free(UVSolver *solver)
+{
+ BLI_mempool_destroy(solver->verts);
+ BLI_mempool_destroy(solver->tris);
+ BLI_mempool_destroy(solver->constraints);
+
+ BLI_ghash_free(solver->vhash, NULL, NULL);
+ BLI_ghash_free(solver->fhash, NULL, NULL);
+
+ MEM_freeN(solver);
+}
+
+void *uvsolver_calc_loop_key(UVSolver *solver, BMLoop *l)
+{
+ // return (void *)l->v;
+ MLoopUV *uv = BM_ELEM_CD_GET_VOID_P(l, solver->cd_uv);
+
+ float u = floorf(uv->uv[0] / solver->snap_limit) * solver->snap_limit;
+ float v = floorf(uv->uv[1] / solver->snap_limit) * solver->snap_limit;
+
+ intptr_t x = (intptr_t)(uv->uv[0] * 16384.0);
+ intptr_t y = (intptr_t)(uv->uv[1] * 16384.0);
+ intptr_t key = y * 16384LL + x;
+
+ return POINTER_FROM_INT(key);
+}
+
+static UVSmoothVert *uvsolver_get_vert(UVSolver *solver, BMLoop *l)
+{
+ MLoopUV *uv = BM_ELEM_CD_GET_VOID_P(l, solver->cd_uv);
+
+ void *pkey = uvsolver_calc_loop_key(solver, l);
+ void **entry = NULL;
+ UVSmoothVert *v;
+
+ if (!BLI_ghash_ensure_p(solver->vhash, pkey, &entry)) {
+ v = BLI_mempool_alloc(solver->verts);
+ memset(v, 0, sizeof(*v));
+
+ // copy_v2_v2(v->uv, uv->uv);
+ v->uv[0] = (double)uv->uv[0];
+ v->uv[1] = (double)uv->uv[1];
+
+ copy_v3_v3(v->co, l->v->co);
+ v->v = l->v;
+
+ *entry = (void *)v;
+ }
+
+ v = (UVSmoothVert *)*entry;
+
+ if (v->totloop < MAXUVLOOPS) {
+ v->ls[v->totloop++] = l;
+ }
+
+ return v;
+}
+
+MINLINE double area_tri_signed_v2_db(const double v1[2], const double v2[2], const double v3[2])
+{
+ return 0.5f * ((v1[0] - v2[0]) * (v2[1] - v3[1]) + (v1[1] - v2[1]) * (v3[0] - v2[0]));
+}
+
+MINLINE double area_tri_v2_db(const double v1[2], const double v2[2], const double v3[2])
+{
+ return fabsf(area_tri_signed_v2_db(v1, v2, v3));
+}
+
+void cross_tri_v3_db(double n[3], const double v1[3], const double v2[3], const double v3[3])
+{
+ double n1[3], n2[3];
+
+ n1[0] = v1[0] - v2[0];
+ n2[0] = v2[0] - v3[0];
+ n1[1] = v1[1] - v2[1];
+ n2[1] = v2[1] - v3[1];
+ n1[2] = v1[2] - v2[2];
+ n2[2] = v2[2] - v3[2];
+ n[0] = n1[1] * n2[2] - n1[2] * n2[1];
+ n[1] = n1[2] * n2[0] - n1[0] * n2[2];
+ n[2] = n1[0] * n2[1] - n1[1] * n2[0];
+}
+
+double area_tri_v3_db(const double v1[3], const double v2[3], const double v3[3])
+{
+ double n[3];
+ cross_tri_v3_db(n, v1, v2, v3);
+ return len_v3_db(n) * 0.5;
+}
+
+static UVSmoothTri *uvsolver_ensure_face(UVSolver *solver, BMFace *f)
+{
+ void **entry = NULL;
+
+ if (BLI_ghash_ensure_p(solver->fhash, (void *)f, &entry)) {
+ return (UVSmoothTri *)*entry;
+ }
+
+ UVSmoothTri *tri = BLI_mempool_alloc(solver->tris);
+ memset((void *)tri, 0, sizeof(*tri));
+ *entry = (void *)tri;
+
+ BMLoop *l = f->l_first;
+
+ bool nocon = false;
+ int i = 0;
+ do {
+ UVSmoothVert *sv = uvsolver_get_vert(solver, l);
+
+ if (BM_elem_flag_test(l->e, BM_ELEM_SEAM)) {
+ nocon = true;
+ }
+
+ tri->vs[i] = sv;
+
+ if (i > 3) {
+ // bad!
+ break;
+ }
+
+ i++;
+ } while ((l = l->next) != f->l_first);
+
+ double area3d = (double)area_tri_v3(tri->vs[0]->co, tri->vs[1]->co, tri->vs[2]->co);
+ double area2d = area_tri_v2_db(tri->vs[0]->uv, tri->vs[1]->uv, tri->vs[2]->uv);
+
+ if (area2d < 0.000001) {
+ tri->vs[0]->uv[0] -= 0.0001;
+ tri->vs[0]->uv[1] -= 0.0001;
+ tri->vs[1]->uv[0] += 0.0001;
+ tri->vs[2]->uv[1] += 0.0001;
+ }
+
+ solver->totarea2d += area2d;
+ solver->totarea3d += area3d;
+
+ tri->area2d = area2d;
+ tri->area3d = area3d;
+
+ for (int i = 0; !nocon && i < 3; i++) {
+
+ UVSmoothConstraint *con = BLI_mempool_alloc(solver->constraints);
+ memset((void *)con, 0, sizeof(*con));
+ con->type = CON_ANGLES;
+ con->k = 0.5;
+
+ UVSmoothVert *v0 = tri->vs[(i + 2) % 3];
+ UVSmoothVert *v1 = tri->vs[i];
+ UVSmoothVert *v2 = tri->vs[(i + 1) % 3];
+
+ con->vs[0] = v0;
+ con->vs[1] = v1;
+ con->vs[2] = v2;
+ con->totvert = 3;
+
+ float t1[3], t2[3];
+
+ sub_v3_v3v3(t1, v0->co, v1->co);
+ sub_v3_v3v3(t2, v2->co, v1->co);
+
+ normalize_v3(t1);
+ normalize_v3(t2);
+
+ float th3d = saacosf(dot_v3v3(t1, t2));
+
+ con->params[0] = (double)th3d;
+
+ // area constraint
+ con = BLI_mempool_alloc(solver->constraints);
+ memset((void *)con, 0, sizeof(*con));
+
+ con->vs[0] = v0;
+ con->vs[1] = v1;
+ con->vs[2] = v2;
+ con->totvert = 3;
+ con->tri = tri;
+ con->type = CON_AREA;
+ con->k = 1.0;
+ }
+
+#if 1
+ for (int i = 0; i < 3; i++) {
+ UVSmoothVert *v1 = tri->vs[i];
+ UVSmoothVert *v2 = tri->vs[(i + 1) % 3];
+
+ bool ok = true;
+
+ for (int j = 0; j < v1->totneighbor; j++) {
+ if (v1->neighbors[j] == v2) {
+ ok = false;
+ break;
+ }
+ }
+
+ ok = ok && v1->totneighbor < MAXUVNEIGHBORS && v2->totneighbor < MAXUVNEIGHBORS;
+
+ if (!ok) {
+ continue;
+ }
+
+ v1->neighbors[v1->totneighbor++] = v2;
+ v2->neighbors[v2->totneighbor++] = v1;
+ }
+#endif
+
+ return tri;
+}
+
+static double normalize_v2_db(double v[2])
+{
+ double len = v[0] * v[0] + v[1] * v[1];
+
+ if (len < 0.0000001) {
+ v[0] = v[1] = 0.0;
+ return 0.0;
+ }
+
+ len = sqrt(len);
+
+ double mul = 1.0 / len;
+
+ v[0] *= mul;
+ v[1] *= mul;
+
+ return len;
+}
+
+static double uvsolver_eval_constraint(UVSolver *solver, UVSmoothConstraint *con)
+{
+ switch (con->type) {
+ case CON_ANGLES: {
+ UVSmoothVert *v0 = con->vs[0];
+ UVSmoothVert *v1 = con->vs[1];
+ UVSmoothVert *v2 = con->vs[2];
+ double t1[2], t2[2];
+
+ sub_v2_v2v2_db(t1, v0->uv, v1->uv);
+ sub_v2_v2v2_db(t2, v2->uv, v1->uv);
+
+ normalize_v2_db(t1);
+ normalize_v2_db(t2);
+
+ double th = saacos(dot_v2v2_db(t1, t2));
+
+ double wind = t1[0] * t2[1] - t1[1] * t2[0];
+
+ if (wind >= 0.0) {
+ th = M_PI - th;
+ }
+
+ return th - con->params[0];
+ }
+ case CON_AREA: {
+ UVSmoothVert *v0 = con->vs[0];
+ UVSmoothVert *v1 = con->vs[1];
+ UVSmoothVert *v2 = con->vs[2];
+
+ if (con->tri->area3d == 0.0 || solver->totarea3d == 0.0) {
+ return 0.0;
+ }
+
+ double area2d = area_tri_signed_v2_db(v0->uv, v1->uv, v2->uv);
+ double goal = con->tri->area3d * solver->totarea2d / solver->totarea3d;
+
+ con->tri->area2d = area2d;
+ return (area2d - goal) * 1024.0;
+ }
+ default:
+ return 0.0f;
+ }
+}
+
+BLI_INLINE float uvsolver_vert_weight(UVSmoothVert *sv)
+{
+ double w = 1.0;
+
+ if (sv->pinned || sv->boundary) {
+ w = 100000.0;
+ }
+
+ return w;
+}
+
+static void uvsolver_solve_begin(UVSolver *solver)
+{
+ UVSmoothVert *sv;
+ BLI_mempool_iter iter;
+
+ BLI_mempool_iternew(solver->verts, &iter);
+ sv = BLI_mempool_iterstep(&iter);
+ BMIter liter;
+
+ for (; sv; sv = BLI_mempool_iterstep(&iter)) {
+ BMLoop *l;
+ sv->pinned = false;
+
+ BM_ITER_ELEM (l, &liter, sv->v, BM_LOOPS_OF_VERT) {
+ if (!BLI_ghash_haskey(solver->fhash, (void *)l->f)) {
+ sv->pinned = true;
+ }
+ }
+ }
+}
+
+static void uvsolver_simple_relax(UVSolver *solver, float strength)
+{
+ BLI_mempool_iter iter;
+
+ UVSmoothVert *sv1;
+ BLI_mempool_iternew(solver->verts, &iter);
+
+ sv1 = BLI_mempool_iterstep(&iter);
+ for (; sv1; sv1 = BLI_mempool_iterstep(&iter)) {
+ double uv[2] = {0.0, 0.0};
+ double tot = 0.0;
+
+ if (!sv1->totneighbor || sv1->pinned) {
+ continue;
+ }
+
+ for (int i = 0; i < sv1->totneighbor; i++) {
+ UVSmoothVert *sv2 = sv1->neighbors[i];
+
+ if (!sv2 || (sv1->boundary && !sv2->boundary)) {
+ continue;
+ }
+
+ uv[0] += sv2->uv[0];
+ uv[1] += sv2->uv[1];
+ tot += 1.0;
+ }
+
+ if (tot < 2.0) {
+ continue;
+ }
+
+ uv[0] /= tot;
+ uv[1] /= tot;
+
+ sv1->uv[0] += (uv[0] - sv1->uv[0]) * strength;
+ sv1->uv[1] += (uv[1] - sv1->uv[1]) * strength;
+ }
+
+ // update real uvs
+
+ const int cd_uv = solver->cd_uv;
+
+ BLI_mempool_iternew(solver->verts, &iter);
+ UVSmoothVert *sv = BLI_mempool_iterstep(&iter);
+ for (; sv; sv = BLI_mempool_iterstep(&iter)) {
+ for (int i = 0; i < sv->totloop; i++) {
+ BMLoop *l = sv->ls[i];
+ MLoopUV *uv = BM_ELEM_CD_GET_VOID_P(l, cd_uv);
+
+ uv->uv[0] = (float)sv->uv[0];
+ uv->uv[1] = (float)sv->uv[1];
+ }
+ }
+}
+
+static float uvsolver_solve_step(UVSolver *solver)
+{
+ BLI_mempool_iter iter;
+
+ if (solver->strength < 0) {
+ uvsolver_simple_relax(solver, fabs(solver->strength));
+ return 0.0f;
+ }
+ else {
+ uvsolver_simple_relax(solver, solver->strength * 0.1f);
+ }
+
+ double error = 0.0;
+
+ const double eval_limit = 0.00001;
+ const double df = 0.0001;
+ int totcon = 0;
+
+ BLI_mempool_iternew(solver->constraints, &iter);
+ UVSmoothConstraint *con = BLI_mempool_iterstep(&iter);
+ for (; con; con = BLI_mempool_iterstep(&iter)) {
+ double r1 = uvsolver_eval_constraint(solver, con);
+
+ if (fabs(r1) < eval_limit) {
+ totcon++;
+ continue;
+ }
+
+ error += fabs(r1);
+ totcon++;
+
+ double totg = 0.0;
+ double totw = 0.0;
+
+ for (int i = 0; i < con->totvert; i++) {
+ UVSmoothVert *sv = con->vs[i];
+
+ for (int j = 0; j < 2; j++) {
+ double orig = sv->uv[j];
+ sv->uv[j] += df;
+
+ double r2 = uvsolver_eval_constraint(solver, con);
+ double g = (r2 - r1) / df;
+
+ con->gs[i][j] = g;
+ totg += g * g;
+
+ sv->uv[j] = orig;
+
+ totw += 1.0 / uvsolver_vert_weight(sv);
+ }
+ }
+
+ if (totg < eval_limit) {
+ continue;
+ }
+
+ r1 *= -solver->strength * 0.75 * con->k / totg;
+ // totw = 1.0 / totw;
+
+ for (int i = 0; i < con->totvert; i++) {
+ UVSmoothVert *sv = con->vs[i];
+ double w = 1.0 / (uvsolver_vert_weight(sv) * totw);
+
+ for (int j = 0; j < 2; j++) {
+ sv->uv[j] += r1 * con->gs[i][j] * w;
+ }
+ }
+ }
+
+ // update real uvs
+
+ const int cd_uv = solver->cd_uv;
+
+ BLI_mempool_iternew(solver->verts, &iter);
+ UVSmoothVert *sv = BLI_mempool_iterstep(&iter);
+ for (; sv; sv = BLI_mempool_iterstep(&iter)) {
+ for (int i = 0; i < sv->totloop; i++) {
+ BMLoop *l = sv->ls[i];
+ MLoopUV *uv = BM_ELEM_CD_GET_VOID_P(l, cd_uv);
+
+ uv->uv[0] = (float)sv->uv[0];
+ uv->uv[1] = (float)sv->uv[1];
+ }
+ }
+
+ return (float)error / (float)totcon;
+}
+
+static void sculpt_uv_brush_cb(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptUVThreadData *data1 = userdata;
+ SculptThreadedTaskData *data = &data1->data;
+ SculptSession *ss = data->ob->sculpt;
+ const Brush *brush = data->brush;
+ const float *offset = data->offset;
+
+ PBVHVertexIter vd;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+ ss, &test, data->brush->falloff_shape);
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+
+ PBVHNode *node = data->nodes[n];
+ TableGSet *faces = BKE_pbvh_bmesh_node_faces(node);
+ BMFace *f;
+ const int cd_uv = CustomData_get_offset(&ss->bm->ldata, CD_MLOOPUV);
+
+ if (cd_uv < 0) {
+ return; // no uv layers
+ }
+
+ float bstrength = ss->cache->bstrength;
+ const int cd_mask = CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK);
+
+ BKE_pbvh_node_mark_update_color(node);
+
+ TGSET_ITER (f, faces) {
+ BMLoop *l = f->l_first;
+ // float mask = 0.0f;
+ float cent[3] = {0};
+ int tot = 0;
+
+ // uvsolver_get_vert
+ do {
+ add_v3_v3(cent, l->v->co);
+ tot++;
+ } while ((l = l->next) != f->l_first);
+
+ mul_v3_fl(cent, 1.0f / (float)tot);
+
+ if (!sculpt_brush_test_sq_fn(&test, cent)) {
+ continue;
+ }
+
+ BM_log_face_modified(ss->bm_log, f);
+ uvsolver_ensure_face(data1->solver, f);
+
+ do {
+ BMIter iter;
+ BMLoop *l2;
+ int tot2 = 0;
+ float uv[2] = {0};
+ bool ok = true;
+ UVSmoothVert *lastv = NULL;
+
+ BM_ITER_ELEM (l2, &iter, l->v, BM_LOOPS_OF_VERT) {
+ if (l2->v != l->v) {
+ l2 = l2->prev->v == l->v ? l2->prev : l2->next;
+ }
+
+ UVSmoothVert *sv = uvsolver_get_vert(data1->solver, l2);
+
+ if (lastv && lastv != sv) {
+ ok = false;
+ lastv->boundary = true;
+ sv->boundary = true;
+ }
+
+ lastv = sv;
+
+ MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l2, cd_uv);
+
+ add_v2_v2(uv, luv->uv);
+ tot2++;
+
+ if (BM_elem_flag_test(l2->e, BM_ELEM_SEAM)) {
+ ok = false;
+ sv->boundary = true;
+ }
+ }
+
+ ok = ok && tot2;
+
+ if (ok) {
+ mul_v2_fl(uv, 1.0f / (float)tot2);
+
+ BM_ITER_ELEM (l2, &iter, l->v, BM_LOOPS_OF_VERT) {
+ if (l2->v != l->v) {
+ l2 = l2->next;
+ }
+
+ MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l2, cd_uv);
+
+ if (len_v2v2(luv->uv, uv) < 0.02) {
+ copy_v2_v2(luv->uv, uv);
+ }
+ }
+ }
+ } while ((l = l->next) != f->l_first);
+
+#if 0
+ do {
+ if (!sculpt_brush_test_sq_fn(&test, l->v->co)) {
+ continue;
+ }
+
+ if (cd_mask >= 0) {
+ mask = BM_ELEM_CD_GET_FLOAT(l->v, cd_mask);
+ }
+
+ SculptVertRef vertex = {(intptr_t)l->v};
+
+ float direction2[3];
+ const float fade =
+ bstrength *
+ SCULPT_brush_strength_factor(
+ ss, brush, vd.co, sqrtf(test.dist), NULL, l->v->no, mask, vertex, thread_id) *
+ ss->cache->pressure;
+
+ } while ((l = l->next) != f->l_first);
+#endif
+ }
+ TGSET_ITER_END;
+}
+
+void SCULPT_uv_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+ float offset[3];
+ const float bstrength = ss->cache->bstrength;
+
+ if (!ss->bm || BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) {
+ // dyntopo only
+ return;
+ }
+
+ const int cd_uv = CustomData_get_offset(&ss->bm->ldata, CD_MLOOPUV);
+ if (cd_uv < 0) {
+ return; // no uv layer?
+ }
+
+ // add undo log subentry
+ BM_log_entry_add_ex(ss->bm, ss->bm_log, true);
+
+ BKE_curvemapping_init(brush->curve);
+
+ UVSolver *solver = uvsolver_new(cd_uv);
+ solver->strength = ss->cache->bstrength;
+
+ /* Threaded loop over nodes. */
+ SculptUVThreadData data = {.solver = solver,
+ .data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .offset = offset,
+ }};
+
+ TaskParallelSettings settings;
+
+ // for now, be single-threaded
+ BKE_pbvh_parallel_range_settings(&settings, false, totnode);
+ BLI_task_parallel_range(0, totnode, &data, sculpt_uv_brush_cb, &settings);
+
+ uvsolver_solve_begin(solver);
+
+ for (int i = 0; i < 5; i++) {
+ uvsolver_solve_step(solver);
+ }
+
+ // tear down solver
+ uvsolver_free(solver);
+}