/* * 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. */ /** \file * \ingroup eduv */ #include "MEM_guardedalloc.h" #include "BLI_boxpack_2d.h" #include "BLI_convexhull_2d.h" #include "BLI_heap.h" #include "BLI_math.h" #include "BLI_memarena.h" #include "BLI_polyfill_2d.h" #include "BLI_polyfill_2d_beautify.h" #include "BLI_rand.h" #include "BLI_utildefines.h" #include "uvedit_parametrizer.h" #include #include #include #include #include "BLI_sys_types.h" /* for intptr_t support */ #include "eigen_capi.h" /* Utils */ #if 0 # define param_assert(condition) # define param_warning(message) # define param_test_equals_ptr(condition) # define param_test_equals_int(condition) #else # define param_assert(condition) \ if (!(condition)) { /*printf("Assertion %s:%d\n", __FILE__, __LINE__); abort();*/ \ } \ (void)0 # define param_warning(message) \ {/*printf("Warning %s:%d: %s\n", __FILE__, __LINE__, message);*/}(void)0 # if 0 # define param_test_equals_ptr(str, a, b) \ if (a != b) { /*printf("Equals %s => %p != %p\n", str, a, b);*/ \ } \ (void)0 # define param_test_equals_int(str, a, b) \ if (a != b) { /*printf("Equals %s => %d != %d\n", str, a, b);*/ \ } \ (void)0 # endif #endif typedef enum PBool { P_TRUE = 1, P_FALSE = 0, } PBool; /* Special Purpose Hash */ typedef intptr_t PHashKey; typedef struct PHashLink { struct PHashLink *next; PHashKey key; } PHashLink; typedef struct PHash { PHashLink **list; PHashLink **buckets; int size, cursize, cursize_id; } PHash; struct PChart; struct PEdge; struct PFace; struct PHandle; struct PVert; /* Simplices */ typedef struct PVert { struct PVert *nextlink; union PVertUnion { PHashKey key; /* construct */ int id; /* abf/lscm matrix index */ float distortion; /* area smoothing */ HeapNode *heaplink; /* edge collapsing */ } u; struct PEdge *edge; float co[3]; float uv[2]; uchar flag; } PVert; typedef struct PEdge { struct PEdge *nextlink; union PEdgeUnion { PHashKey key; /* construct */ int id; /* abf matrix index */ HeapNode *heaplink; /* fill holes */ struct PEdge *nextcollapse; /* simplification */ } u; struct PVert *vert; struct PEdge *pair; struct PEdge *next; struct PFace *face; float *orig_uv, old_uv[2]; ushort flag; } PEdge; typedef struct PFace { struct PFace *nextlink; union PFaceUnion { PHashKey key; /* construct */ int chart; /* construct splitting*/ float area3d; /* stretch */ int id; /* abf matrix index */ } u; struct PEdge *edge; uchar flag; } PFace; enum PVertFlag { PVERT_PIN = 1, PVERT_SELECT = 2, PVERT_INTERIOR = 4, PVERT_COLLAPSE = 8, PVERT_SPLIT = 16, }; enum PEdgeFlag { PEDGE_SEAM = 1, PEDGE_VERTEX_SPLIT = 2, PEDGE_PIN = 4, PEDGE_SELECT = 8, PEDGE_DONE = 16, PEDGE_FILLED = 32, PEDGE_COLLAPSE = 64, PEDGE_COLLAPSE_EDGE = 128, PEDGE_COLLAPSE_PAIR = 256, }; /* for flipping faces */ #define PEDGE_VERTEX_FLAGS (PEDGE_PIN) enum PFaceFlag { PFACE_CONNECTED = 1, PFACE_FILLED = 2, PFACE_COLLAPSE = 4, }; /* Chart */ typedef struct PChart { PVert *verts; PEdge *edges; PFace *faces; int nverts, nedges, nfaces; PVert *collapsed_verts; PEdge *collapsed_edges; PFace *collapsed_faces; union PChartUnion { struct PChartLscm { LinearSolver *context; float *abf_alpha; PVert *pin1, *pin2; } lscm; struct PChartPack { float rescale, area; float size[2] /* , trans[2] */; } pack; } u; uchar flag; struct PHandle *handle; } PChart; enum PChartFlag { PCHART_HAS_PINS = 1, }; enum PHandleState { PHANDLE_STATE_ALLOCATED, PHANDLE_STATE_CONSTRUCTED, PHANDLE_STATE_LSCM, PHANDLE_STATE_STRETCH, }; typedef struct PHandle { enum PHandleState state; MemArena *arena; MemArena *polyfill_arena; Heap *polyfill_heap; PChart *construction_chart; PHash *hash_verts; PHash *hash_edges; PHash *hash_faces; PChart **charts; int ncharts; float aspx, aspy; RNG *rng; float blend; char do_aspect; } PHandle; /* PHash * - special purpose hash that keeps all its elements in a single linked list. * - after construction, this hash is thrown away, and the list remains. * - removing elements is not possible efficiently. */ static int PHashSizes[] = { 1, 3, 5, 11, 17, 37, 67, 131, 257, 521, 1031, 2053, 4099, 8209, 16411, 32771, 65537, 131101, 262147, 524309, 1048583, 2097169, 4194319, 8388617, 16777259, 33554467, 67108879, 134217757, 268435459, }; #define PHASH_hash(ph, item) (((uintptr_t)(item)) % ((uint)(ph)->cursize)) #define PHASH_edge(v1, v2) (((v1) < (v2)) ? ((v1)*39) ^ ((v2)*31) : ((v1)*31) ^ ((v2)*39)) static PHash *phash_new(PHashLink **list, int sizehint) { PHash *ph = (PHash *)MEM_callocN(sizeof(PHash), "PHash"); ph->size = 0; ph->cursize_id = 0; ph->list = list; while (PHashSizes[ph->cursize_id] < sizehint) { ph->cursize_id++; } ph->cursize = PHashSizes[ph->cursize_id]; ph->buckets = (PHashLink **)MEM_callocN(ph->cursize * sizeof(*ph->buckets), "PHashBuckets"); return ph; } static void phash_delete(PHash *ph) { MEM_freeN(ph->buckets); MEM_freeN(ph); } static int phash_size(PHash *ph) { return ph->size; } static void phash_insert(PHash *ph, PHashLink *link) { int size = ph->cursize; uintptr_t hash = PHASH_hash(ph, link->key); PHashLink *lookup = ph->buckets[hash]; if (lookup == NULL) { /* insert in front of the list */ ph->buckets[hash] = link; link->next = *(ph->list); *(ph->list) = link; } else { /* insert after existing element */ link->next = lookup->next; lookup->next = link; } ph->size++; if (ph->size > (size * 3)) { PHashLink *next = NULL, *first = *(ph->list); ph->cursize = PHashSizes[++ph->cursize_id]; MEM_freeN(ph->buckets); ph->buckets = (PHashLink **)MEM_callocN(ph->cursize * sizeof(*ph->buckets), "PHashBuckets"); ph->size = 0; *(ph->list) = NULL; for (link = first; link; link = next) { next = link->next; phash_insert(ph, link); } } } static PHashLink *phash_lookup(PHash *ph, PHashKey key) { PHashLink *link; uintptr_t hash = PHASH_hash(ph, key); for (link = ph->buckets[hash]; link; link = link->next) { if (link->key == key) { return link; } else if (PHASH_hash(ph, link->key) != hash) { return NULL; } } return link; } static PHashLink *phash_next(PHash *ph, PHashKey key, PHashLink *link) { uintptr_t hash = PHASH_hash(ph, key); for (link = link->next; link; link = link->next) { if (link->key == key) { return link; } else if (PHASH_hash(ph, link->key) != hash) { return NULL; } } return link; } /* Geometry */ static float p_vec_angle_cos(const float v1[3], const float v2[3], const float v3[3]) { float d1[3], d2[3]; d1[0] = v1[0] - v2[0]; d1[1] = v1[1] - v2[1]; d1[2] = v1[2] - v2[2]; d2[0] = v3[0] - v2[0]; d2[1] = v3[1] - v2[1]; d2[2] = v3[2] - v2[2]; normalize_v3(d1); normalize_v3(d2); return d1[0] * d2[0] + d1[1] * d2[1] + d1[2] * d2[2]; } static float p_vec_angle(const float v1[3], const float v2[3], const float v3[3]) { float dot = p_vec_angle_cos(v1, v2, v3); if (dot <= -1.0f) { return (float)M_PI; } else if (dot >= 1.0f) { return 0.0f; } else { return acosf(dot); } } static float p_vec2_angle(const float v1[2], const float v2[2], const float v3[2]) { float u1[3], u2[3], u3[3]; u1[0] = v1[0]; u1[1] = v1[1]; u1[2] = 0.0f; u2[0] = v2[0]; u2[1] = v2[1]; u2[2] = 0.0f; u3[0] = v3[0]; u3[1] = v3[1]; u3[2] = 0.0f; return p_vec_angle(u1, u2, u3); } static void p_triangle_angles( const float v1[3], const float v2[3], const float v3[3], float *r_a1, float *r_a2, float *r_a3) { *r_a1 = p_vec_angle(v3, v1, v2); *r_a2 = p_vec_angle(v1, v2, v3); *r_a3 = (float)M_PI - *r_a2 - *r_a1; } static void p_face_angles(PFace *f, float *r_a1, float *r_a2, float *r_a3) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; p_triangle_angles(v1->co, v2->co, v3->co, r_a1, r_a2, r_a3); } static float p_face_area(PFace *f) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; return area_tri_v3(v1->co, v2->co, v3->co); } static float p_area_signed(const float v1[2], const float v2[2], const float v3[2]) { return 0.5f * (((v2[0] - v1[0]) * (v3[1] - v1[1])) - ((v3[0] - v1[0]) * (v2[1] - v1[1]))); } static float p_face_uv_area_signed(PFace *f) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; return 0.5f * (((v2->uv[0] - v1->uv[0]) * (v3->uv[1] - v1->uv[1])) - ((v3->uv[0] - v1->uv[0]) * (v2->uv[1] - v1->uv[1]))); } static float p_edge_length(PEdge *e) { PVert *v1 = e->vert, *v2 = e->next->vert; float d[3]; d[0] = v2->co[0] - v1->co[0]; d[1] = v2->co[1] - v1->co[1]; d[2] = v2->co[2] - v1->co[2]; return sqrtf(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); } static float p_edge_uv_length(PEdge *e) { PVert *v1 = e->vert, *v2 = e->next->vert; float d[3]; d[0] = v2->uv[0] - v1->uv[0]; d[1] = v2->uv[1] - v1->uv[1]; return sqrtf(d[0] * d[0] + d[1] * d[1]); } static void p_chart_uv_bbox(PChart *chart, float minv[2], float maxv[2]) { PVert *v; INIT_MINMAX2(minv, maxv); for (v = chart->verts; v; v = v->nextlink) { minmax_v2v2_v2(minv, maxv, v->uv); } } static void p_chart_uv_scale(PChart *chart, float scale) { PVert *v; for (v = chart->verts; v; v = v->nextlink) { v->uv[0] *= scale; v->uv[1] *= scale; } } static void p_chart_uv_scale_xy(PChart *chart, float x, float y) { PVert *v; for (v = chart->verts; v; v = v->nextlink) { v->uv[0] *= x; v->uv[1] *= y; } } static void p_chart_uv_translate(PChart *chart, float trans[2]) { PVert *v; for (v = chart->verts; v; v = v->nextlink) { v->uv[0] += trans[0]; v->uv[1] += trans[1]; } } static void p_chart_uv_transform(PChart *chart, float mat[2][2]) { PVert *v; for (v = chart->verts; v; v = v->nextlink) { mul_m2_v2(mat, v->uv); } } static void p_chart_uv_to_array(PChart *chart, float (*points)[2]) { PVert *v; uint i = 0; for (v = chart->verts; v; v = v->nextlink) { copy_v2_v2(points[i++], v->uv); } } static void UNUSED_FUNCTION(p_chart_uv_from_array)(PChart *chart, float (*points)[2]) { PVert *v; uint i = 0; for (v = chart->verts; v; v = v->nextlink) { copy_v2_v2(v->uv, points[i++]); } } static PBool p_intersect_line_2d_dir(const float v1[2], const float dir1[2], const float v2[2], const float dir2[2], float r_isect[2]) { float lmbda, div; div = dir2[0] * dir1[1] - dir2[1] * dir1[0]; if (div == 0.0f) { return P_FALSE; } lmbda = ((v1[1] - v2[1]) * dir1[0] - (v1[0] - v2[0]) * dir1[1]) / div; r_isect[0] = v1[0] + lmbda * dir2[0]; r_isect[1] = v1[1] + lmbda * dir2[1]; return P_TRUE; } #if 0 static PBool p_intersect_line_2d(const float v1[2], const float v2[2], const float v3[2], const float v4[2], const float r_isect[2]) { float dir1[2], dir2[2]; dir1[0] = v4[0] - v3[0]; dir1[1] = v4[1] - v3[1]; dir2[0] = v2[0] - v1[0]; dir2[1] = v2[1] - v1[1]; if (!p_intersect_line_2d_dir(v1, dir1, v2, dir2, isect)) { /* parallel - should never happen in theory for polygon kernel, but * let's give a point nearby in case things go wrong */ isect[0] = (v1[0] + v2[0]) * 0.5f; isect[1] = (v1[1] + v2[1]) * 0.5f; return P_FALSE; } return P_TRUE; } #endif /* Topological Utilities */ static PEdge *p_wheel_edge_next(PEdge *e) { return e->next->next->pair; } static PEdge *p_wheel_edge_prev(PEdge *e) { return (e->pair) ? e->pair->next : NULL; } static PEdge *p_boundary_edge_next(PEdge *e) { return e->next->vert->edge; } static PEdge *p_boundary_edge_prev(PEdge *e) { PEdge *we = e, *last; do { last = we; we = p_wheel_edge_next(we); } while (we && (we != e)); return last->next->next; } static PBool p_vert_interior(PVert *v) { return (v->edge->pair != NULL); } static void p_face_flip(PFace *f) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; int f1 = e1->flag, f2 = e2->flag, f3 = e3->flag; float *orig_uv1 = e1->orig_uv, *orig_uv2 = e2->orig_uv, *orig_uv3 = e3->orig_uv; e1->vert = v2; e1->next = e3; e1->orig_uv = orig_uv2; e1->flag = (f1 & ~PEDGE_VERTEX_FLAGS) | (f2 & PEDGE_VERTEX_FLAGS); e2->vert = v3; e2->next = e1; e2->orig_uv = orig_uv3; e2->flag = (f2 & ~PEDGE_VERTEX_FLAGS) | (f3 & PEDGE_VERTEX_FLAGS); e3->vert = v1; e3->next = e2; e3->orig_uv = orig_uv1; e3->flag = (f3 & ~PEDGE_VERTEX_FLAGS) | (f1 & PEDGE_VERTEX_FLAGS); } #if 0 static void p_chart_topological_sanity_check(PChart *chart) { PVert *v; PEdge *e; for (v = chart->verts; v; v = v->nextlink) { param_test_equals_ptr("v->edge->vert", v, v->edge->vert); } for (e = chart->edges; e; e = e->nextlink) { if (e->pair) { param_test_equals_ptr("e->pair->pair", e, e->pair->pair); param_test_equals_ptr("pair->vert", e->vert, e->pair->next->vert); param_test_equals_ptr("pair->next->vert", e->next->vert, e->pair->vert); } } } #endif /* Loading / Flushing */ static void p_vert_load_pin_select_uvs(PHandle *handle, PVert *v) { PEdge *e; int nedges = 0, npins = 0; float pinuv[2]; v->uv[0] = v->uv[1] = 0.0f; pinuv[0] = pinuv[1] = 0.0f; e = v->edge; do { if (e->orig_uv) { if (e->flag & PEDGE_SELECT) { v->flag |= PVERT_SELECT; } if (e->flag & PEDGE_PIN) { pinuv[0] += e->orig_uv[0] * handle->aspx; pinuv[1] += e->orig_uv[1] * handle->aspy; npins++; } else { v->uv[0] += e->orig_uv[0] * handle->aspx; v->uv[1] += e->orig_uv[1] * handle->aspy; } nedges++; } e = p_wheel_edge_next(e); } while (e && e != (v->edge)); if (npins > 0) { v->uv[0] = pinuv[0] / npins; v->uv[1] = pinuv[1] / npins; v->flag |= PVERT_PIN; } else if (nedges > 0) { v->uv[0] /= nedges; v->uv[1] /= nedges; } } static void p_flush_uvs(PHandle *handle, PChart *chart) { PEdge *e; for (e = chart->edges; e; e = e->nextlink) { if (e->orig_uv) { e->orig_uv[0] = e->vert->uv[0] / handle->aspx; e->orig_uv[1] = e->vert->uv[1] / handle->aspy; } } } static void p_flush_uvs_blend(PHandle *handle, PChart *chart, float blend) { PEdge *e; float invblend = 1.0f - blend; for (e = chart->edges; e; e = e->nextlink) { if (e->orig_uv) { e->orig_uv[0] = blend * e->old_uv[0] + invblend * e->vert->uv[0] / handle->aspx; e->orig_uv[1] = blend * e->old_uv[1] + invblend * e->vert->uv[1] / handle->aspy; } } } static void p_face_backup_uvs(PFace *f) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; if (e1->orig_uv) { e1->old_uv[0] = e1->orig_uv[0]; e1->old_uv[1] = e1->orig_uv[1]; } if (e2->orig_uv) { e2->old_uv[0] = e2->orig_uv[0]; e2->old_uv[1] = e2->orig_uv[1]; } if (e3->orig_uv) { e3->old_uv[0] = e3->orig_uv[0]; e3->old_uv[1] = e3->orig_uv[1]; } } static void p_face_restore_uvs(PFace *f) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; if (e1->orig_uv) { e1->orig_uv[0] = e1->old_uv[0]; e1->orig_uv[1] = e1->old_uv[1]; } if (e2->orig_uv) { e2->orig_uv[0] = e2->old_uv[0]; e2->orig_uv[1] = e2->old_uv[1]; } if (e3->orig_uv) { e3->orig_uv[0] = e3->old_uv[0]; e3->orig_uv[1] = e3->old_uv[1]; } } /* Construction (use only during construction, relies on u.key being set */ static PVert *p_vert_add(PHandle *handle, PHashKey key, const float co[3], PEdge *e) { PVert *v = (PVert *)BLI_memarena_alloc(handle->arena, sizeof(*v)); copy_v3_v3(v->co, co); /* Sanity check, a single nan/inf point causes the entire result to be invalid. * Note that values within the calculation may _become_ non-finite, * so the rest of the code still needs to take this possibility into account. */ for (int i = 0; i < 3; i++) { if (UNLIKELY(!isfinite(v->co[i]))) { v->co[i] = 0.0f; } } v->u.key = key; v->edge = e; v->flag = 0; phash_insert(handle->hash_verts, (PHashLink *)v); return v; } static PVert *p_vert_lookup(PHandle *handle, PHashKey key, const float co[3], PEdge *e) { PVert *v = (PVert *)phash_lookup(handle->hash_verts, key); if (v) { return v; } else { return p_vert_add(handle, key, co, e); } } static PVert *p_vert_copy(PChart *chart, PVert *v) { PVert *nv = (PVert *)BLI_memarena_alloc(chart->handle->arena, sizeof(*nv)); copy_v3_v3(nv->co, v->co); nv->uv[0] = v->uv[0]; nv->uv[1] = v->uv[1]; nv->u.key = v->u.key; nv->edge = v->edge; nv->flag = v->flag; return nv; } static PEdge *p_edge_lookup(PHandle *handle, PHashKey *vkeys) { PHashKey key = PHASH_edge(vkeys[0], vkeys[1]); PEdge *e = (PEdge *)phash_lookup(handle->hash_edges, key); while (e) { if ((e->vert->u.key == vkeys[0]) && (e->next->vert->u.key == vkeys[1])) { return e; } else if ((e->vert->u.key == vkeys[1]) && (e->next->vert->u.key == vkeys[0])) { return e; } e = (PEdge *)phash_next(handle->hash_edges, key, (PHashLink *)e); } return NULL; } static int p_face_exists(ParamHandle *phandle, ParamKey *pvkeys, int i1, int i2, int i3) { PHandle *handle = (PHandle *)phandle; PHashKey *vkeys = (PHashKey *)pvkeys; PHashKey key = PHASH_edge(vkeys[i1], vkeys[i2]); PEdge *e = (PEdge *)phash_lookup(handle->hash_edges, key); while (e) { if ((e->vert->u.key == vkeys[i1]) && (e->next->vert->u.key == vkeys[i2])) { if (e->next->next->vert->u.key == vkeys[i3]) { return P_TRUE; } } else if ((e->vert->u.key == vkeys[i2]) && (e->next->vert->u.key == vkeys[i1])) { if (e->next->next->vert->u.key == vkeys[i3]) { return P_TRUE; } } e = (PEdge *)phash_next(handle->hash_edges, key, (PHashLink *)e); } return P_FALSE; } static PChart *p_chart_new(PHandle *handle) { PChart *chart = (PChart *)MEM_callocN(sizeof(*chart), "PChart"); chart->handle = handle; return chart; } static void p_chart_delete(PChart *chart) { /* the actual links are free by memarena */ MEM_freeN(chart); } static PBool p_edge_implicit_seam(PEdge *e, PEdge *ep) { float *uv1, *uv2, *uvp1, *uvp2; float limit[2]; limit[0] = 0.00001; limit[1] = 0.00001; uv1 = e->orig_uv; uv2 = e->next->orig_uv; if (e->vert->u.key == ep->vert->u.key) { uvp1 = ep->orig_uv; uvp2 = ep->next->orig_uv; } else { uvp1 = ep->next->orig_uv; uvp2 = ep->orig_uv; } if ((fabsf(uv1[0] - uvp1[0]) > limit[0]) || (fabsf(uv1[1] - uvp1[1]) > limit[1])) { e->flag |= PEDGE_SEAM; ep->flag |= PEDGE_SEAM; return P_TRUE; } if ((fabsf(uv2[0] - uvp2[0]) > limit[0]) || (fabsf(uv2[1] - uvp2[1]) > limit[1])) { e->flag |= PEDGE_SEAM; ep->flag |= PEDGE_SEAM; return P_TRUE; } return P_FALSE; } static PBool p_edge_has_pair(PHandle *handle, PEdge *e, PBool impl, PEdge **r_pair) { PHashKey key; PEdge *pe; PVert *v1, *v2; PHashKey key1 = e->vert->u.key; PHashKey key2 = e->next->vert->u.key; if (e->flag & PEDGE_SEAM) { return P_FALSE; } key = PHASH_edge(key1, key2); pe = (PEdge *)phash_lookup(handle->hash_edges, key); *r_pair = NULL; while (pe) { if (pe != e) { v1 = pe->vert; v2 = pe->next->vert; if (((v1->u.key == key1) && (v2->u.key == key2)) || ((v1->u.key == key2) && (v2->u.key == key1))) { /* don't connect seams and t-junctions */ if ((pe->flag & PEDGE_SEAM) || *r_pair || (impl && p_edge_implicit_seam(e, pe))) { *r_pair = NULL; return P_FALSE; } *r_pair = pe; } } pe = (PEdge *)phash_next(handle->hash_edges, key, (PHashLink *)pe); } if (*r_pair && (e->vert == (*r_pair)->vert)) { if ((*r_pair)->next->pair || (*r_pair)->next->next->pair) { /* non unfoldable, maybe mobius ring or klein bottle */ *r_pair = NULL; return P_FALSE; } } return (*r_pair != NULL); } static PBool p_edge_connect_pair(PHandle *handle, PEdge *e, PBool impl, PEdge ***stack) { PEdge *pair = NULL; if (!e->pair && p_edge_has_pair(handle, e, impl, &pair)) { if (e->vert == pair->vert) { p_face_flip(pair->face); } e->pair = pair; pair->pair = e; if (!(pair->face->flag & PFACE_CONNECTED)) { **stack = pair; (*stack)++; } } return (e->pair != NULL); } static int p_connect_pairs(PHandle *handle, PBool impl) { PEdge **stackbase = MEM_mallocN(sizeof(*stackbase) * phash_size(handle->hash_faces), "Pstackbase"); PEdge **stack = stackbase; PFace *f, *first; PEdge *e, *e1, *e2; PChart *chart = handle->construction_chart; int ncharts = 0; /* connect pairs, count edges, set vertex-edge pointer to a pairless edge */ for (first = chart->faces; first; first = first->nextlink) { if (first->flag & PFACE_CONNECTED) { continue; } *stack = first->edge; stack++; while (stack != stackbase) { stack--; e = *stack; e1 = e->next; e2 = e1->next; f = e->face; f->flag |= PFACE_CONNECTED; /* assign verts to charts so we can sort them later */ f->u.chart = ncharts; if (!p_edge_connect_pair(handle, e, impl, &stack)) { e->vert->edge = e; } if (!p_edge_connect_pair(handle, e1, impl, &stack)) { e1->vert->edge = e1; } if (!p_edge_connect_pair(handle, e2, impl, &stack)) { e2->vert->edge = e2; } } ncharts++; } MEM_freeN(stackbase); return ncharts; } static void p_split_vert(PChart *chart, PEdge *e) { PEdge *we, *lastwe = NULL; PVert *v = e->vert; PBool copy = P_TRUE; if (e->flag & PEDGE_PIN) { chart->flag |= PCHART_HAS_PINS; } if (e->flag & PEDGE_VERTEX_SPLIT) { return; } /* rewind to start */ lastwe = e; for (we = p_wheel_edge_prev(e); we && (we != e); we = p_wheel_edge_prev(we)) { lastwe = we; } /* go over all edges in wheel */ for (we = lastwe; we; we = p_wheel_edge_next(we)) { if (we->flag & PEDGE_VERTEX_SPLIT) { break; } we->flag |= PEDGE_VERTEX_SPLIT; if (we == v->edge) { /* found it, no need to copy */ copy = P_FALSE; v->nextlink = chart->verts; chart->verts = v; chart->nverts++; } } if (copy) { /* not found, copying */ v->flag |= PVERT_SPLIT; v = p_vert_copy(chart, v); v->flag |= PVERT_SPLIT; v->nextlink = chart->verts; chart->verts = v; chart->nverts++; v->edge = lastwe; we = lastwe; do { we->vert = v; we = p_wheel_edge_next(we); } while (we && (we != lastwe)); } } static PChart **p_split_charts(PHandle *handle, PChart *chart, int ncharts) { PChart **charts = MEM_mallocN(sizeof(*charts) * ncharts, "PCharts"), *nchart; PFace *f, *nextf; int i; for (i = 0; i < ncharts; i++) { charts[i] = p_chart_new(handle); } f = chart->faces; while (f) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; nextf = f->nextlink; nchart = charts[f->u.chart]; f->nextlink = nchart->faces; nchart->faces = f; e1->nextlink = nchart->edges; nchart->edges = e1; e2->nextlink = nchart->edges; nchart->edges = e2; e3->nextlink = nchart->edges; nchart->edges = e3; nchart->nfaces++; nchart->nedges += 3; p_split_vert(nchart, e1); p_split_vert(nchart, e2); p_split_vert(nchart, e3); f = nextf; } return charts; } static PFace *p_face_add(PHandle *handle) { PFace *f; PEdge *e1, *e2, *e3; /* allocate */ f = (PFace *)BLI_memarena_alloc(handle->arena, sizeof(*f)); f->flag = 0; /* init ! */ e1 = (PEdge *)BLI_memarena_alloc(handle->arena, sizeof(*e1)); e2 = (PEdge *)BLI_memarena_alloc(handle->arena, sizeof(*e2)); e3 = (PEdge *)BLI_memarena_alloc(handle->arena, sizeof(*e3)); /* set up edges */ f->edge = e1; e1->face = e2->face = e3->face = f; e1->next = e2; e2->next = e3; e3->next = e1; e1->pair = NULL; e2->pair = NULL; e3->pair = NULL; e1->flag = 0; e2->flag = 0; e3->flag = 0; return f; } static PFace *p_face_add_construct(PHandle *handle, ParamKey key, ParamKey *vkeys, float *co[4], float *uv[4], int i1, int i2, int i3, ParamBool *pin, ParamBool *select) { PFace *f = p_face_add(handle); PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; e1->vert = p_vert_lookup(handle, vkeys[i1], co[i1], e1); e2->vert = p_vert_lookup(handle, vkeys[i2], co[i2], e2); e3->vert = p_vert_lookup(handle, vkeys[i3], co[i3], e3); e1->orig_uv = uv[i1]; e2->orig_uv = uv[i2]; e3->orig_uv = uv[i3]; if (pin) { if (pin[i1]) { e1->flag |= PEDGE_PIN; } if (pin[i2]) { e2->flag |= PEDGE_PIN; } if (pin[i3]) { e3->flag |= PEDGE_PIN; } } if (select) { if (select[i1]) { e1->flag |= PEDGE_SELECT; } if (select[i2]) { e2->flag |= PEDGE_SELECT; } if (select[i3]) { e3->flag |= PEDGE_SELECT; } } /* insert into hash */ f->u.key = key; phash_insert(handle->hash_faces, (PHashLink *)f); e1->u.key = PHASH_edge(vkeys[i1], vkeys[i2]); e2->u.key = PHASH_edge(vkeys[i2], vkeys[i3]); e3->u.key = PHASH_edge(vkeys[i3], vkeys[i1]); phash_insert(handle->hash_edges, (PHashLink *)e1); phash_insert(handle->hash_edges, (PHashLink *)e2); phash_insert(handle->hash_edges, (PHashLink *)e3); return f; } static PFace *p_face_add_fill(PChart *chart, PVert *v1, PVert *v2, PVert *v3) { PFace *f = p_face_add(chart->handle); PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; e1->vert = v1; e2->vert = v2; e3->vert = v3; e1->orig_uv = e2->orig_uv = e3->orig_uv = NULL; f->nextlink = chart->faces; chart->faces = f; e1->nextlink = chart->edges; chart->edges = e1; e2->nextlink = chart->edges; chart->edges = e2; e3->nextlink = chart->edges; chart->edges = e3; chart->nfaces++; chart->nedges += 3; return f; } static PBool p_quad_split_direction(PHandle *handle, float **co, PHashKey *vkeys) { /* slight bias to prefer one edge over the other in case they are equal, so * that in symmetric models we choose the same split direction instead of * depending on floating point errors to decide */ float bias = 1.0f + 1e-6f; float fac = len_v3v3(co[0], co[2]) * bias - len_v3v3(co[1], co[3]); PBool dir = (fac <= 0.0f); /* the face exists check is there because of a special case: when * two quads share three vertices, they can each be split into two * triangles, resulting in two identical triangles. for example in * suzanne's nose. */ if (dir) { if (p_face_exists(handle, vkeys, 0, 1, 2) || p_face_exists(handle, vkeys, 0, 2, 3)) { return !dir; } } else { if (p_face_exists(handle, vkeys, 0, 1, 3) || p_face_exists(handle, vkeys, 1, 2, 3)) { return !dir; } } return dir; } /* Construction: boundary filling */ static void p_chart_boundaries(PChart *chart, int *r_nboundaries, PEdge **r_outer) { PEdge *e, *be; float len, maxlen = -1.0; if (r_nboundaries) { *r_nboundaries = 0; } if (r_outer) { *r_outer = NULL; } for (e = chart->edges; e; e = e->nextlink) { if (e->pair || (e->flag & PEDGE_DONE)) { continue; } if (r_nboundaries) { (*r_nboundaries)++; } len = 0.0f; be = e; do { be->flag |= PEDGE_DONE; len += p_edge_length(be); be = be->next->vert->edge; } while (be != e); if (r_outer && (len > maxlen)) { *r_outer = e; maxlen = len; } } for (e = chart->edges; e; e = e->nextlink) { e->flag &= ~PEDGE_DONE; } } static float p_edge_boundary_angle(PEdge *e) { PEdge *we; PVert *v, *v1, *v2; float angle; int n = 0; v = e->vert; /* concave angle check -- could be better */ angle = M_PI; we = v->edge; do { v1 = we->next->vert; v2 = we->next->next->vert; angle -= p_vec_angle(v1->co, v->co, v2->co); we = we->next->next->pair; n++; } while (we && (we != v->edge)); return angle; } static void p_chart_fill_boundary(PChart *chart, PEdge *be, int nedges) { PEdge *e, *e1, *e2; PFace *f; struct Heap *heap = BLI_heap_new(); float angle; e = be; do { angle = p_edge_boundary_angle(e); e->u.heaplink = BLI_heap_insert(heap, angle, e); e = p_boundary_edge_next(e); } while (e != be); if (nedges == 2) { /* no real boundary, but an isolated seam */ e = be->next->vert->edge; e->pair = be; be->pair = e; BLI_heap_remove(heap, e->u.heaplink); BLI_heap_remove(heap, be->u.heaplink); } else { while (nedges > 2) { PEdge *ne, *ne1, *ne2; e = (PEdge *)BLI_heap_pop_min(heap); e1 = p_boundary_edge_prev(e); e2 = p_boundary_edge_next(e); BLI_heap_remove(heap, e1->u.heaplink); BLI_heap_remove(heap, e2->u.heaplink); e->u.heaplink = e1->u.heaplink = e2->u.heaplink = NULL; e->flag |= PEDGE_FILLED; e1->flag |= PEDGE_FILLED; f = p_face_add_fill(chart, e->vert, e1->vert, e2->vert); f->flag |= PFACE_FILLED; ne = f->edge->next->next; ne1 = f->edge; ne2 = f->edge->next; ne->flag = ne1->flag = ne2->flag = PEDGE_FILLED; e->pair = ne; ne->pair = e; e1->pair = ne1; ne1->pair = e1; ne->vert = e2->vert; ne1->vert = e->vert; ne2->vert = e1->vert; if (nedges == 3) { e2->pair = ne2; ne2->pair = e2; } else { ne2->vert->edge = ne2; ne2->u.heaplink = BLI_heap_insert(heap, p_edge_boundary_angle(ne2), ne2); e2->u.heaplink = BLI_heap_insert(heap, p_edge_boundary_angle(e2), e2); } nedges--; } } BLI_heap_free(heap, NULL); } static void p_chart_fill_boundaries(PChart *chart, PEdge *outer) { PEdge *e, *be; /* *enext - as yet unused */ int nedges; for (e = chart->edges; e; e = e->nextlink) { /* enext = e->nextlink; - as yet unused */ if (e->pair || (e->flag & PEDGE_FILLED)) { continue; } nedges = 0; be = e; do { be->flag |= PEDGE_FILLED; be = be->next->vert->edge; nedges++; } while (be != e); if (e != outer) { p_chart_fill_boundary(chart, e, nedges); } } } #if 0 /* Polygon kernel for inserting uv's non overlapping */ static int p_polygon_point_in(const float cp1[2], const float cp2[2], const float p[2]) { if ((cp1[0] == p[0]) && (cp1[1] == p[1])) { return 2; } else if ((cp2[0] == p[0]) && (cp2[1] == p[1])) { return 3; } else { return (p_area_signed(cp1, cp2, p) >= 0.0f); } } static void p_polygon_kernel_clip(float (*oldpoints)[2], int noldpoints, float (*newpoints)[2], int *r_nnewpoints, const float cp1[2], const float cp2[2]) { float *p2, *p1, isect[2]; int i, p2in, p1in; p1 = oldpoints[noldpoints - 1]; p1in = p_polygon_point_in(cp1, cp2, p1); *r_nnewpoints = 0; for (i = 0; i < noldpoints; i++) { p2 = oldpoints[i]; p2in = p_polygon_point_in(cp1, cp2, p2); if ((p2in >= 2) || (p1in && p2in)) { newpoints[*r_nnewpoints][0] = p2[0]; newpoints[*r_nnewpoints][1] = p2[1]; (*r_nnewpoints)++; } else if (p1in && !p2in) { if (p1in != 3) { p_intersect_line_2d(p1, p2, cp1, cp2, isect); newpoints[*r_nnewpoints][0] = isect[0]; newpoints[*r_nnewpoints][1] = isect[1]; (*r_nnewpoints)++; } } else if (!p1in && p2in) { p_intersect_line_2d(p1, p2, cp1, cp2, isect); newpoints[*r_nnewpoints][0] = isect[0]; newpoints[*r_nnewpoints][1] = isect[1]; (*r_nnewpoints)++; newpoints[*r_nnewpoints][0] = p2[0]; newpoints[*r_nnewpoints][1] = p2[1]; (*r_nnewpoints)++; } p1in = p2in; p1 = p2; } } static void p_polygon_kernel_center(float (*points)[2], int npoints, float *center) { int i, size, nnewpoints = npoints; float(*oldpoints)[2], (*newpoints)[2], *p1, *p2; size = npoints * 3; oldpoints = MEM_mallocN(sizeof(float) * 2 * size, "PPolygonOldPoints"); newpoints = MEM_mallocN(sizeof(float) * 2 * size, "PPolygonNewPoints"); memcpy(oldpoints, points, sizeof(float) * 2 * npoints); for (i = 0; i < npoints; i++) { p1 = points[i]; p2 = points[(i + 1) % npoints]; p_polygon_kernel_clip(oldpoints, nnewpoints, newpoints, &nnewpoints, p1, p2); if (nnewpoints == 0) { /* degenerate case, use center of original polygon */ memcpy(oldpoints, points, sizeof(float) * 2 * npoints); nnewpoints = npoints; break; } else if (nnewpoints == 1) { /* degenerate case, use remaining point */ center[0] = newpoints[0][0]; center[1] = newpoints[0][1]; MEM_freeN(oldpoints); MEM_freeN(newpoints); return; } if (nnewpoints * 2 > size) { size *= 2; MEM_freeN(oldpoints); oldpoints = MEM_mallocN(sizeof(float) * 2 * size, "oldpoints"); memcpy(oldpoints, newpoints, sizeof(float) * 2 * nnewpoints); MEM_freeN(newpoints); newpoints = MEM_mallocN(sizeof(float) * 2 * size, "newpoints"); } else { float(*sw_points)[2] = oldpoints; oldpoints = newpoints; newpoints = sw_points; } } center[0] = center[1] = 0.0f; for (i = 0; i < nnewpoints; i++) { center[0] += oldpoints[i][0]; center[1] += oldpoints[i][1]; } center[0] /= nnewpoints; center[1] /= nnewpoints; MEM_freeN(oldpoints); MEM_freeN(newpoints); } #endif #if 0 /* Edge Collapser */ int NCOLLAPSE = 1; int NCOLLAPSEX = 0; static float p_vert_cotan(const float v1[3], const float v2[3], const float v3[3]) { float a[3], b[3], c[3], clen; sub_v3_v3v3(a, v2, v1); sub_v3_v3v3(b, v3, v1); cross_v3_v3v3(c, a, b); clen = len_v3(c); if (clen == 0.0f) { return 0.0f; } return dot_v3v3(a, b) / clen; } static PBool p_vert_flipped_wheel_triangle(PVert *v) { PEdge *e = v->edge; do { if (p_face_uv_area_signed(e->face) < 0.0f) { return P_TRUE; } e = p_wheel_edge_next(e); } while (e && (e != v->edge)); return P_FALSE; } static PBool p_vert_map_harmonic_weights(PVert *v) { float weightsum, positionsum[2], olduv[2]; weightsum = 0.0f; positionsum[0] = positionsum[1] = 0.0f; if (p_vert_interior(v)) { PEdge *e = v->edge; do { float t1, t2, weight; PVert *v1, *v2; v1 = e->next->vert; v2 = e->next->next->vert; t1 = p_vert_cotan(v2->co, e->vert->co, v1->co); v1 = e->pair->next->vert; v2 = e->pair->next->next->vert; t2 = p_vert_cotan(v2->co, e->pair->vert->co, v1->co); weight = 0.5f * (t1 + t2); weightsum += weight; positionsum[0] += weight * e->pair->vert->uv[0]; positionsum[1] += weight * e->pair->vert->uv[1]; e = p_wheel_edge_next(e); } while (e && (e != v->edge)); } else { PEdge *e = v->edge; do { float t1, t2; PVert *v1, *v2; v2 = e->next->vert; v1 = e->next->next->vert; t1 = p_vert_cotan(v1->co, v->co, v2->co); t2 = p_vert_cotan(v2->co, v->co, v1->co); weightsum += t1 + t2; positionsum[0] += (v2->uv[1] - v1->uv[1]) + (t1 * v2->uv[0] + t2 * v1->uv[0]); positionsum[1] += (v1->uv[0] - v2->uv[0]) + (t1 * v2->uv[1] + t2 * v1->uv[1]); e = p_wheel_edge_next(e); } while (e && (e != v->edge)); } if (weightsum != 0.0f) { weightsum = 1.0f / weightsum; positionsum[0] *= weightsum; positionsum[1] *= weightsum; } olduv[0] = v->uv[0]; olduv[1] = v->uv[1]; v->uv[0] = positionsum[0]; v->uv[1] = positionsum[1]; if (p_vert_flipped_wheel_triangle(v)) { v->uv[0] = olduv[0]; v->uv[1] = olduv[1]; return P_FALSE; } return P_TRUE; } static void p_vert_harmonic_insert(PVert *v) { PEdge *e; if (!p_vert_map_harmonic_weights(v)) { /* do polygon kernel center insertion: this is quite slow, but should * only be needed for 0.01 % of verts or so, when insert with harmonic * weights fails */ int npoints = 0, i; float(*points)[2]; e = v->edge; do { npoints++; e = p_wheel_edge_next(e); } while (e && (e != v->edge)); if (e == NULL) { npoints++; } points = MEM_mallocN(sizeof(float) * 2 * npoints, "PHarmonicPoints"); e = v->edge; i = 0; do { PEdge *nexte = p_wheel_edge_next(e); points[i][0] = e->next->vert->uv[0]; points[i][1] = e->next->vert->uv[1]; if (nexte == NULL) { i++; points[i][0] = e->next->next->vert->uv[0]; points[i][1] = e->next->next->vert->uv[1]; break; } e = nexte; i++; } while (e != v->edge); p_polygon_kernel_center(points, npoints, v->uv); MEM_freeN(points); } e = v->edge; do { if (!(e->next->vert->flag & PVERT_PIN)) { p_vert_map_harmonic_weights(e->next->vert); } e = p_wheel_edge_next(e); } while (e && (e != v->edge)); p_vert_map_harmonic_weights(v); } static void p_vert_fix_edge_pointer(PVert *v) { PEdge *start = v->edge; /* set v->edge pointer to the edge with no pair, if there is one */ while (v->edge->pair) { v->edge = p_wheel_edge_prev(v->edge); if (v->edge == start) { break; } } } static void p_collapsing_verts(PEdge *edge, PEdge *pair, PVert **r_newv, PVert **r_keepv) { /* the two vertices that are involved in the collapse */ if (edge) { *r_newv = edge->vert; *r_keepv = edge->next->vert; } else { *r_newv = pair->next->vert; *r_keepv = pair->vert; } } static void p_collapse_edge(PEdge *edge, PEdge *pair) { PVert *oldv, *keepv; PEdge *e; p_collapsing_verts(edge, pair, &oldv, &keepv); /* change e->vert pointers from old vertex to the target vertex */ e = oldv->edge; do { if ((e != edge) && !(pair && pair->next == e)) { e->vert = keepv; } e = p_wheel_edge_next(e); } while (e && (e != oldv->edge)); /* set keepv->edge pointer */ if ((edge && (keepv->edge == edge->next)) || (keepv->edge == pair)) { if (edge && edge->next->pair) { keepv->edge = edge->next->pair->next; } else if (pair && pair->next->next->pair) { keepv->edge = pair->next->next->pair; } else if (edge && edge->next->next->pair) { keepv->edge = edge->next->next->pair; } else { keepv->edge = pair->next->pair->next; } } /* update pairs and v->edge pointers */ if (edge) { PEdge *e1 = edge->next, *e2 = e1->next; if (e1->pair) { e1->pair->pair = e2->pair; } if (e2->pair) { e2->pair->pair = e1->pair; e2->vert->edge = p_wheel_edge_prev(e2); } else { e2->vert->edge = p_wheel_edge_next(e2); } p_vert_fix_edge_pointer(e2->vert); } if (pair) { PEdge *e1 = pair->next, *e2 = e1->next; if (e1->pair) { e1->pair->pair = e2->pair; } if (e2->pair) { e2->pair->pair = e1->pair; e2->vert->edge = p_wheel_edge_prev(e2); } else { e2->vert->edge = p_wheel_edge_next(e2); } p_vert_fix_edge_pointer(e2->vert); } p_vert_fix_edge_pointer(keepv); /* mark for move to collapsed list later */ oldv->flag |= PVERT_COLLAPSE; if (edge) { PFace *f = edge->face; PEdge *e1 = edge->next, *e2 = e1->next; f->flag |= PFACE_COLLAPSE; edge->flag |= PEDGE_COLLAPSE; e1->flag |= PEDGE_COLLAPSE; e2->flag |= PEDGE_COLLAPSE; } if (pair) { PFace *f = pair->face; PEdge *e1 = pair->next, *e2 = e1->next; f->flag |= PFACE_COLLAPSE; pair->flag |= PEDGE_COLLAPSE; e1->flag |= PEDGE_COLLAPSE; e2->flag |= PEDGE_COLLAPSE; } } static void p_split_vertex(PEdge *edge, PEdge *pair) { PVert *newv, *keepv; PEdge *e; p_collapsing_verts(edge, pair, &newv, &keepv); /* update edge pairs */ if (edge) { PEdge *e1 = edge->next, *e2 = e1->next; if (e1->pair) { e1->pair->pair = e1; } if (e2->pair) { e2->pair->pair = e2; } e2->vert->edge = e2; p_vert_fix_edge_pointer(e2->vert); keepv->edge = e1; } if (pair) { PEdge *e1 = pair->next, *e2 = e1->next; if (e1->pair) { e1->pair->pair = e1; } if (e2->pair) { e2->pair->pair = e2; } e2->vert->edge = e2; p_vert_fix_edge_pointer(e2->vert); keepv->edge = pair; } p_vert_fix_edge_pointer(keepv); /* set e->vert pointers to restored vertex */ e = newv->edge; do { e->vert = newv; e = p_wheel_edge_next(e); } while (e && (e != newv->edge)); } static PBool p_collapse_allowed_topologic(PEdge *edge, PEdge *pair) { PVert *oldv, *keepv; p_collapsing_verts(edge, pair, &oldv, &keepv); /* boundary edges */ if (!edge || !pair) { /* avoid collapsing chart into an edge */ if (edge && !edge->next->pair && !edge->next->next->pair) { return P_FALSE; } else if (pair && !pair->next->pair && !pair->next->next->pair) { return P_FALSE; } } /* avoid merging two boundaries (oldv and keepv are on the 'other side' of * the chart) */ else if (!p_vert_interior(oldv) && !p_vert_interior(keepv)) { return P_FALSE; } return P_TRUE; } static PBool p_collapse_normal_flipped(float *v1, float *v2, float *vold, float *vnew) { float nold[3], nnew[3], sub1[3], sub2[3]; sub_v3_v3v3(sub1, vold, v1); sub_v3_v3v3(sub2, vold, v2); cross_v3_v3v3(nold, sub1, sub2); sub_v3_v3v3(sub1, vnew, v1); sub_v3_v3v3(sub2, vnew, v2); cross_v3_v3v3(nnew, sub1, sub2); return (dot_v3v3(nold, nnew) <= 0.0f); } static PBool p_collapse_allowed_geometric(PEdge *edge, PEdge *pair) { PVert *oldv, *keepv; PEdge *e; float angulardefect, angle; p_collapsing_verts(edge, pair, &oldv, &keepv); angulardefect = 2 * M_PI; e = oldv->edge; do { float a[3], b[3], minangle, maxangle; PEdge *e1 = e->next, *e2 = e1->next; PVert *v1 = e1->vert, *v2 = e2->vert; int i; angle = p_vec_angle(v1->co, oldv->co, v2->co); angulardefect -= angle; /* skip collapsing faces */ if (v1 == keepv || v2 == keepv) { e = p_wheel_edge_next(e); continue; } if (p_collapse_normal_flipped(v1->co, v2->co, oldv->co, keepv->co)) { return P_FALSE; } a[0] = angle; a[1] = p_vec_angle(v2->co, v1->co, oldv->co); a[2] = M_PI - a[0] - a[1]; b[0] = p_vec_angle(v1->co, keepv->co, v2->co); b[1] = p_vec_angle(v2->co, v1->co, keepv->co); b[2] = M_PI - b[0] - b[1]; /* abf criterion 1: avoid sharp and obtuse angles */ minangle = 15.0f * M_PI / 180.0f; maxangle = M_PI - minangle; for (i = 0; i < 3; i++) { if ((b[i] < a[i]) && (b[i] < minangle)) { return P_FALSE; } else if ((b[i] > a[i]) && (b[i] > maxangle)) { return P_FALSE; } } e = p_wheel_edge_next(e); } while (e && (e != oldv->edge)); if (p_vert_interior(oldv)) { /* hlscm criterion: angular defect smaller than threshold */ if (fabsf(angulardefect) > (float)(M_PI * 30.0 / 180.0)) { return P_FALSE; } } else { PVert *v1 = p_boundary_edge_next(oldv->edge)->vert; PVert *v2 = p_boundary_edge_prev(oldv->edge)->vert; /* abf++ criterion 2: avoid collapsing verts inwards */ if (p_vert_interior(keepv)) { return P_FALSE; } /* don't collapse significant boundary changes */ angle = p_vec_angle(v1->co, oldv->co, v2->co); if (angle < (M_PI * 160.0 / 180.0)) { return P_FALSE; } } return P_TRUE; } static PBool p_collapse_allowed(PEdge *edge, PEdge *pair) { PVert *oldv, *keepv; p_collapsing_verts(edge, pair, &oldv, &keepv); if (oldv->flag & PVERT_PIN) { return P_FALSE; } return (p_collapse_allowed_topologic(edge, pair) && p_collapse_allowed_geometric(edge, pair)); } static float p_collapse_cost(PEdge *edge, PEdge *pair) { /* based on volume and boundary optimization from: * "Fast and Memory Efficient Polygonal Simplification" P. Lindstrom, G. Turk */ PVert *oldv, *keepv; PEdge *e; PFace *oldf1, *oldf2; float volumecost = 0.0f, areacost = 0.0f, edgevec[3], cost, weight, elen; float shapecost = 0.0f; float shapeold = 0.0f, shapenew = 0.0f; int nshapeold = 0, nshapenew = 0; p_collapsing_verts(edge, pair, &oldv, &keepv); oldf1 = (edge) ? edge->face : NULL; oldf2 = (pair) ? pair->face : NULL; sub_v3_v3v3(edgevec, keepv->co, oldv->co); e = oldv->edge; do { float a1, a2, a3; float *co1 = e->next->vert->co; float *co2 = e->next->next->vert->co; if ((e->face != oldf1) && (e->face != oldf2)) { float tetrav2[3], tetrav3[3]; /* tetrahedron volume = (1/3!)*|a.(b x c)| */ sub_v3_v3v3(tetrav2, co1, oldv->co); sub_v3_v3v3(tetrav3, co2, oldv->co); volumecost += fabsf(volume_tri_tetrahedron_signed_v3(tetrav2, tetrav3, edgevec)); # if 0 shapecost += dot_v3v3(co1, keepv->co); if (p_wheel_edge_next(e) == NULL) { shapecost += dot_v3v3(co2, keepv->co); } # endif p_triangle_angles(oldv->co, co1, co2, &a1, &a2, &a3); a1 = a1 - M_PI / 3.0; a2 = a2 - M_PI / 3.0; a3 = a3 - M_PI / 3.0; shapeold = (a1 * a1 + a2 * a2 + a3 * a3) / ((M_PI / 2) * (M_PI / 2)); nshapeold++; } else { p_triangle_angles(keepv->co, co1, co2, &a1, &a2, &a3); a1 = a1 - M_PI / 3.0; a2 = a2 - M_PI / 3.0; a3 = a3 - M_PI / 3.0; shapenew = (a1 * a1 + a2 * a2 + a3 * a3) / ((M_PI / 2) * (M_PI / 2)); nshapenew++; } e = p_wheel_edge_next(e); } while (e && (e != oldv->edge)); if (!p_vert_interior(oldv)) { PVert *v1 = p_boundary_edge_prev(oldv->edge)->vert; PVert *v2 = p_boundary_edge_next(oldv->edge)->vert; areacost = area_tri_v3(oldv->co, v1->co, v2->co); } elen = len_v3(edgevec); weight = 1.0f; /* 0.2f */ cost = weight * volumecost * volumecost + elen * elen * areacost * areacost; # if 0 cost += shapecost; # else shapeold /= nshapeold; shapenew /= nshapenew; shapecost = (shapeold + 0.00001) / (shapenew + 0.00001); cost *= shapecost; # endif return cost; } static void p_collapse_cost_vertex(PVert *vert, float *r_mincost, PEdge **r_mine) { PEdge *e, *enext, *pair; *r_mine = NULL; *r_mincost = 0.0f; e = vert->edge; do { if (p_collapse_allowed(e, e->pair)) { float cost = p_collapse_cost(e, e->pair); if ((*r_mine == NULL) || (cost < *r_mincost)) { *r_mincost = cost; *r_mine = e; } } enext = p_wheel_edge_next(e); if (enext == NULL) { /* the other boundary edge, where we only have the pair halfedge */ pair = e->next->next; if (p_collapse_allowed(NULL, pair)) { float cost = p_collapse_cost(NULL, pair); if ((*r_mine == NULL) || (cost < *r_mincost)) { *r_mincost = cost; *r_mine = pair; } } break; } e = enext; } while (e != vert->edge); } static void p_chart_post_collapse_flush(PChart *chart, PEdge *collapsed) { /* move to collapsed_ */ PVert *v, *nextv = NULL, *verts = chart->verts; PEdge *e, *nexte = NULL, *edges = chart->edges, *laste = NULL; PFace *f, *nextf = NULL, *faces = chart->faces; chart->verts = chart->collapsed_verts = NULL; chart->edges = chart->collapsed_edges = NULL; chart->faces = chart->collapsed_faces = NULL; chart->nverts = chart->nedges = chart->nfaces = 0; for (v = verts; v; v = nextv) { nextv = v->nextlink; if (v->flag & PVERT_COLLAPSE) { v->nextlink = chart->collapsed_verts; chart->collapsed_verts = v; } else { v->nextlink = chart->verts; chart->verts = v; chart->nverts++; } } for (e = edges; e; e = nexte) { nexte = e->nextlink; if (!collapsed || !(e->flag & PEDGE_COLLAPSE_EDGE)) { if (e->flag & PEDGE_COLLAPSE) { e->nextlink = chart->collapsed_edges; chart->collapsed_edges = e; } else { e->nextlink = chart->edges; chart->edges = e; chart->nedges++; } } } /* these are added last so they can be popped of in the right order * for splitting */ for (e = collapsed; e; e = e->nextlink) { e->nextlink = e->u.nextcollapse; laste = e; } if (laste) { laste->nextlink = chart->collapsed_edges; chart->collapsed_edges = collapsed; } for (f = faces; f; f = nextf) { nextf = f->nextlink; if (f->flag & PFACE_COLLAPSE) { f->nextlink = chart->collapsed_faces; chart->collapsed_faces = f; } else { f->nextlink = chart->faces; chart->faces = f; chart->nfaces++; } } } static void p_chart_post_split_flush(PChart *chart) { /* move from collapsed_ */ PVert *v, *nextv = NULL; PEdge *e, *nexte = NULL; PFace *f, *nextf = NULL; for (v = chart->collapsed_verts; v; v = nextv) { nextv = v->nextlink; v->nextlink = chart->verts; chart->verts = v; chart->nverts++; } for (e = chart->collapsed_edges; e; e = nexte) { nexte = e->nextlink; e->nextlink = chart->edges; chart->edges = e; chart->nedges++; } for (f = chart->collapsed_faces; f; f = nextf) { nextf = f->nextlink; f->nextlink = chart->faces; chart->faces = f; chart->nfaces++; } chart->collapsed_verts = NULL; chart->collapsed_edges = NULL; chart->collapsed_faces = NULL; } static void p_chart_simplify_compute(PChart *chart) { /* Computes a list of edge collapses / vertex splits. The collapsed * simplices go in the chart->collapsed_* lists, The original and * collapsed may then be view as stacks, where the next collapse/split * is at the top of the respective lists. */ Heap *heap = BLI_heap_new(); PVert *v, **wheelverts; PEdge *collapsededges = NULL, *e; int nwheelverts, i, ncollapsed = 0; wheelverts = MEM_mallocN(sizeof(PVert *) * chart->nverts, "PChartWheelVerts"); /* insert all potential collapses into heap */ for (v = chart->verts; v; v = v->nextlink) { float cost; PEdge *e = NULL; p_collapse_cost_vertex(v, &cost, &e); if (e) { v->u.heaplink = BLI_heap_insert(heap, cost, e); } else { v->u.heaplink = NULL; } } for (e = chart->edges; e; e = e->nextlink) { e->u.nextcollapse = NULL; } /* pop edge collapse out of heap one by one */ while (!BLI_heap_is_empty(heap)) { if (ncollapsed == NCOLLAPSE) { break; } HeapNode *link = BLI_heap_top(heap); PEdge *edge = (PEdge *)BLI_heap_pop_min(heap), *pair = edge->pair; PVert *oldv, *keepv; PEdge *wheele, *nexte; /* remember the edges we collapsed */ edge->u.nextcollapse = collapsededges; collapsededges = edge; if (edge->vert->u.heaplink != link) { edge->flag |= (PEDGE_COLLAPSE_EDGE | PEDGE_COLLAPSE_PAIR); edge->next->vert->u.heaplink = NULL; SWAP(PEdge *, edge, pair); } else { edge->flag |= PEDGE_COLLAPSE_EDGE; edge->vert->u.heaplink = NULL; } p_collapsing_verts(edge, pair, &oldv, &keepv); /* gather all wheel verts and remember them before collapse */ nwheelverts = 0; wheele = oldv->edge; do { wheelverts[nwheelverts++] = wheele->next->vert; nexte = p_wheel_edge_next(wheele); if (nexte == NULL) { wheelverts[nwheelverts++] = wheele->next->next->vert; } wheele = nexte; } while (wheele && (wheele != oldv->edge)); /* collapse */ p_collapse_edge(edge, pair); for (i = 0; i < nwheelverts; i++) { float cost; PEdge *collapse = NULL; v = wheelverts[i]; if (v->u.heaplink) { BLI_heap_remove(heap, v->u.heaplink); v->u.heaplink = NULL; } p_collapse_cost_vertex(v, &cost, &collapse); if (collapse) { v->u.heaplink = BLI_heap_insert(heap, cost, collapse); } } ncollapsed++; } MEM_freeN(wheelverts); BLI_heap_free(heap, NULL); p_chart_post_collapse_flush(chart, collapsededges); } static void p_chart_complexify(PChart *chart) { PEdge *e, *pair, *edge; PVert *newv, *keepv; int x = 0; for (e = chart->collapsed_edges; e; e = e->nextlink) { if (!(e->flag & PEDGE_COLLAPSE_EDGE)) { break; } edge = e; pair = e->pair; if (edge->flag & PEDGE_COLLAPSE_PAIR) { SWAP(PEdge *, edge, pair); } p_split_vertex(edge, pair); p_collapsing_verts(edge, pair, &newv, &keepv); if (x >= NCOLLAPSEX) { newv->uv[0] = keepv->uv[0]; newv->uv[1] = keepv->uv[1]; } else { p_vert_harmonic_insert(newv); x++; } } p_chart_post_split_flush(chart); } # if 0 static void p_chart_simplify(PChart *chart) { /* Not implemented, needs proper reordering in split_flush. */ } # endif #endif /* ABF */ #define ABF_MAX_ITER 20 typedef struct PAbfSystem { int ninterior, nfaces, nangles; float *alpha, *beta, *sine, *cosine, *weight; float *bAlpha, *bTriangle, *bInterior; float *lambdaTriangle, *lambdaPlanar, *lambdaLength; float (*J2dt)[3], *bstar, *dstar; float minangle, maxangle; } PAbfSystem; static void p_abf_setup_system(PAbfSystem *sys) { int i; sys->alpha = (float *)MEM_mallocN(sizeof(float) * sys->nangles, "ABFalpha"); sys->beta = (float *)MEM_mallocN(sizeof(float) * sys->nangles, "ABFbeta"); sys->sine = (float *)MEM_mallocN(sizeof(float) * sys->nangles, "ABFsine"); sys->cosine = (float *)MEM_mallocN(sizeof(float) * sys->nangles, "ABFcosine"); sys->weight = (float *)MEM_mallocN(sizeof(float) * sys->nangles, "ABFweight"); sys->bAlpha = (float *)MEM_mallocN(sizeof(float) * sys->nangles, "ABFbalpha"); sys->bTriangle = (float *)MEM_mallocN(sizeof(float) * sys->nfaces, "ABFbtriangle"); sys->bInterior = (float *)MEM_mallocN(sizeof(float) * 2 * sys->ninterior, "ABFbinterior"); sys->lambdaTriangle = (float *)MEM_callocN(sizeof(float) * sys->nfaces, "ABFlambdatri"); sys->lambdaPlanar = (float *)MEM_callocN(sizeof(float) * sys->ninterior, "ABFlamdaplane"); sys->lambdaLength = (float *)MEM_mallocN(sizeof(float) * sys->ninterior, "ABFlambdalen"); sys->J2dt = MEM_mallocN(sizeof(float) * sys->nangles * 3, "ABFj2dt"); sys->bstar = (float *)MEM_mallocN(sizeof(float) * sys->nfaces, "ABFbstar"); sys->dstar = (float *)MEM_mallocN(sizeof(float) * sys->nfaces, "ABFdstar"); for (i = 0; i < sys->ninterior; i++) { sys->lambdaLength[i] = 1.0; } sys->minangle = 1.0 * M_PI / 180.0; sys->maxangle = (float)M_PI - sys->minangle; } static void p_abf_free_system(PAbfSystem *sys) { MEM_freeN(sys->alpha); MEM_freeN(sys->beta); MEM_freeN(sys->sine); MEM_freeN(sys->cosine); MEM_freeN(sys->weight); MEM_freeN(sys->bAlpha); MEM_freeN(sys->bTriangle); MEM_freeN(sys->bInterior); MEM_freeN(sys->lambdaTriangle); MEM_freeN(sys->lambdaPlanar); MEM_freeN(sys->lambdaLength); MEM_freeN(sys->J2dt); MEM_freeN(sys->bstar); MEM_freeN(sys->dstar); } static void p_abf_compute_sines(PAbfSystem *sys) { int i; float *sine = sys->sine, *cosine = sys->cosine, *alpha = sys->alpha; for (i = 0; i < sys->nangles; i++, sine++, cosine++, alpha++) { *sine = sinf(*alpha); *cosine = cosf(*alpha); } } static float p_abf_compute_sin_product(PAbfSystem *sys, PVert *v, int aid) { PEdge *e, *e1, *e2; float sin1, sin2; sin1 = sin2 = 1.0; e = v->edge; do { e1 = e->next; e2 = e->next->next; if (aid == e1->u.id) { /* we are computing a derivative for this angle, * so we use cos and drop the other part */ sin1 *= sys->cosine[e1->u.id]; sin2 = 0.0; } else { sin1 *= sys->sine[e1->u.id]; } if (aid == e2->u.id) { /* see above */ sin1 = 0.0; sin2 *= sys->cosine[e2->u.id]; } else { sin2 *= sys->sine[e2->u.id]; } e = e->next->next->pair; } while (e && (e != v->edge)); return (sin1 - sin2); } static float p_abf_compute_grad_alpha(PAbfSystem *sys, PFace *f, PEdge *e) { PVert *v = e->vert, *v1 = e->next->vert, *v2 = e->next->next->vert; float deriv; deriv = (sys->alpha[e->u.id] - sys->beta[e->u.id]) * sys->weight[e->u.id]; deriv += sys->lambdaTriangle[f->u.id]; if (v->flag & PVERT_INTERIOR) { deriv += sys->lambdaPlanar[v->u.id]; } if (v1->flag & PVERT_INTERIOR) { float product = p_abf_compute_sin_product(sys, v1, e->u.id); deriv += sys->lambdaLength[v1->u.id] * product; } if (v2->flag & PVERT_INTERIOR) { float product = p_abf_compute_sin_product(sys, v2, e->u.id); deriv += sys->lambdaLength[v2->u.id] * product; } return deriv; } static float p_abf_compute_gradient(PAbfSystem *sys, PChart *chart) { PFace *f; PEdge *e; PVert *v; float norm = 0.0; for (f = chart->faces; f; f = f->nextlink) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; float gtriangle, galpha1, galpha2, galpha3; galpha1 = p_abf_compute_grad_alpha(sys, f, e1); galpha2 = p_abf_compute_grad_alpha(sys, f, e2); galpha3 = p_abf_compute_grad_alpha(sys, f, e3); sys->bAlpha[e1->u.id] = -galpha1; sys->bAlpha[e2->u.id] = -galpha2; sys->bAlpha[e3->u.id] = -galpha3; norm += galpha1 * galpha1 + galpha2 * galpha2 + galpha3 * galpha3; gtriangle = sys->alpha[e1->u.id] + sys->alpha[e2->u.id] + sys->alpha[e3->u.id] - (float)M_PI; sys->bTriangle[f->u.id] = -gtriangle; norm += gtriangle * gtriangle; } for (v = chart->verts; v; v = v->nextlink) { if (v->flag & PVERT_INTERIOR) { float gplanar = -2 * M_PI, glength; e = v->edge; do { gplanar += sys->alpha[e->u.id]; e = e->next->next->pair; } while (e && (e != v->edge)); sys->bInterior[v->u.id] = -gplanar; norm += gplanar * gplanar; glength = p_abf_compute_sin_product(sys, v, -1); sys->bInterior[sys->ninterior + v->u.id] = -glength; norm += glength * glength; } } return norm; } static PBool p_abf_matrix_invert(PAbfSystem *sys, PChart *chart) { PFace *f; PEdge *e; int i, j, ninterior = sys->ninterior, nvar = 2 * sys->ninterior; PBool success; LinearSolver *context; context = EIG_linear_solver_new(0, nvar, 1); for (i = 0; i < nvar; i++) { EIG_linear_solver_right_hand_side_add(context, 0, i, sys->bInterior[i]); } for (f = chart->faces; f; f = f->nextlink) { float wi1, wi2, wi3, b, si, beta[3], j2[3][3], W[3][3]; float row1[6], row2[6], row3[6]; int vid[6]; PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; wi1 = 1.0f / sys->weight[e1->u.id]; wi2 = 1.0f / sys->weight[e2->u.id]; wi3 = 1.0f / sys->weight[e3->u.id]; /* bstar1 = (J1*dInv*bAlpha - bTriangle) */ b = sys->bAlpha[e1->u.id] * wi1; b += sys->bAlpha[e2->u.id] * wi2; b += sys->bAlpha[e3->u.id] * wi3; b -= sys->bTriangle[f->u.id]; /* si = J1*d*J1t */ si = 1.0f / (wi1 + wi2 + wi3); /* J1t*si*bstar1 - bAlpha */ beta[0] = b * si - sys->bAlpha[e1->u.id]; beta[1] = b * si - sys->bAlpha[e2->u.id]; beta[2] = b * si - sys->bAlpha[e3->u.id]; /* use this later for computing other lambda's */ sys->bstar[f->u.id] = b; sys->dstar[f->u.id] = si; /* set matrix */ W[0][0] = si - sys->weight[e1->u.id]; W[0][1] = si; W[0][2] = si; W[1][0] = si; W[1][1] = si - sys->weight[e2->u.id]; W[1][2] = si; W[2][0] = si; W[2][1] = si; W[2][2] = si - sys->weight[e3->u.id]; vid[0] = vid[1] = vid[2] = vid[3] = vid[4] = vid[5] = -1; if (v1->flag & PVERT_INTERIOR) { vid[0] = v1->u.id; vid[3] = ninterior + v1->u.id; sys->J2dt[e1->u.id][0] = j2[0][0] = 1.0f * wi1; sys->J2dt[e2->u.id][0] = j2[1][0] = p_abf_compute_sin_product(sys, v1, e2->u.id) * wi2; sys->J2dt[e3->u.id][0] = j2[2][0] = p_abf_compute_sin_product(sys, v1, e3->u.id) * wi3; EIG_linear_solver_right_hand_side_add(context, 0, v1->u.id, j2[0][0] * beta[0]); EIG_linear_solver_right_hand_side_add( context, 0, ninterior + v1->u.id, j2[1][0] * beta[1] + j2[2][0] * beta[2]); row1[0] = j2[0][0] * W[0][0]; row2[0] = j2[0][0] * W[1][0]; row3[0] = j2[0][0] * W[2][0]; row1[3] = j2[1][0] * W[0][1] + j2[2][0] * W[0][2]; row2[3] = j2[1][0] * W[1][1] + j2[2][0] * W[1][2]; row3[3] = j2[1][0] * W[2][1] + j2[2][0] * W[2][2]; } if (v2->flag & PVERT_INTERIOR) { vid[1] = v2->u.id; vid[4] = ninterior + v2->u.id; sys->J2dt[e1->u.id][1] = j2[0][1] = p_abf_compute_sin_product(sys, v2, e1->u.id) * wi1; sys->J2dt[e2->u.id][1] = j2[1][1] = 1.0f * wi2; sys->J2dt[e3->u.id][1] = j2[2][1] = p_abf_compute_sin_product(sys, v2, e3->u.id) * wi3; EIG_linear_solver_right_hand_side_add(context, 0, v2->u.id, j2[1][1] * beta[1]); EIG_linear_solver_right_hand_side_add( context, 0, ninterior + v2->u.id, j2[0][1] * beta[0] + j2[2][1] * beta[2]); row1[1] = j2[1][1] * W[0][1]; row2[1] = j2[1][1] * W[1][1]; row3[1] = j2[1][1] * W[2][1]; row1[4] = j2[0][1] * W[0][0] + j2[2][1] * W[0][2]; row2[4] = j2[0][1] * W[1][0] + j2[2][1] * W[1][2]; row3[4] = j2[0][1] * W[2][0] + j2[2][1] * W[2][2]; } if (v3->flag & PVERT_INTERIOR) { vid[2] = v3->u.id; vid[5] = ninterior + v3->u.id; sys->J2dt[e1->u.id][2] = j2[0][2] = p_abf_compute_sin_product(sys, v3, e1->u.id) * wi1; sys->J2dt[e2->u.id][2] = j2[1][2] = p_abf_compute_sin_product(sys, v3, e2->u.id) * wi2; sys->J2dt[e3->u.id][2] = j2[2][2] = 1.0f * wi3; EIG_linear_solver_right_hand_side_add(context, 0, v3->u.id, j2[2][2] * beta[2]); EIG_linear_solver_right_hand_side_add( context, 0, ninterior + v3->u.id, j2[0][2] * beta[0] + j2[1][2] * beta[1]); row1[2] = j2[2][2] * W[0][2]; row2[2] = j2[2][2] * W[1][2]; row3[2] = j2[2][2] * W[2][2]; row1[5] = j2[0][2] * W[0][0] + j2[1][2] * W[0][1]; row2[5] = j2[0][2] * W[1][0] + j2[1][2] * W[1][1]; row3[5] = j2[0][2] * W[2][0] + j2[1][2] * W[2][1]; } for (i = 0; i < 3; i++) { int r = vid[i]; if (r == -1) { continue; } for (j = 0; j < 6; j++) { int c = vid[j]; if (c == -1) { continue; } if (i == 0) { EIG_linear_solver_matrix_add(context, r, c, j2[0][i] * row1[j]); } else { EIG_linear_solver_matrix_add(context, r + ninterior, c, j2[0][i] * row1[j]); } if (i == 1) { EIG_linear_solver_matrix_add(context, r, c, j2[1][i] * row2[j]); } else { EIG_linear_solver_matrix_add(context, r + ninterior, c, j2[1][i] * row2[j]); } if (i == 2) { EIG_linear_solver_matrix_add(context, r, c, j2[2][i] * row3[j]); } else { EIG_linear_solver_matrix_add(context, r + ninterior, c, j2[2][i] * row3[j]); } } } } success = EIG_linear_solver_solve(context); if (success) { for (f = chart->faces; f; f = f->nextlink) { float dlambda1, pre[3], dalpha; PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; pre[0] = pre[1] = pre[2] = 0.0; if (v1->flag & PVERT_INTERIOR) { float x = EIG_linear_solver_variable_get(context, 0, v1->u.id); float x2 = EIG_linear_solver_variable_get(context, 0, ninterior + v1->u.id); pre[0] += sys->J2dt[e1->u.id][0] * x; pre[1] += sys->J2dt[e2->u.id][0] * x2; pre[2] += sys->J2dt[e3->u.id][0] * x2; } if (v2->flag & PVERT_INTERIOR) { float x = EIG_linear_solver_variable_get(context, 0, v2->u.id); float x2 = EIG_linear_solver_variable_get(context, 0, ninterior + v2->u.id); pre[0] += sys->J2dt[e1->u.id][1] * x2; pre[1] += sys->J2dt[e2->u.id][1] * x; pre[2] += sys->J2dt[e3->u.id][1] * x2; } if (v3->flag & PVERT_INTERIOR) { float x = EIG_linear_solver_variable_get(context, 0, v3->u.id); float x2 = EIG_linear_solver_variable_get(context, 0, ninterior + v3->u.id); pre[0] += sys->J2dt[e1->u.id][2] * x2; pre[1] += sys->J2dt[e2->u.id][2] * x2; pre[2] += sys->J2dt[e3->u.id][2] * x; } dlambda1 = pre[0] + pre[1] + pre[2]; dlambda1 = sys->dstar[f->u.id] * (sys->bstar[f->u.id] - dlambda1); sys->lambdaTriangle[f->u.id] += dlambda1; dalpha = (sys->bAlpha[e1->u.id] - dlambda1); sys->alpha[e1->u.id] += dalpha / sys->weight[e1->u.id] - pre[0]; dalpha = (sys->bAlpha[e2->u.id] - dlambda1); sys->alpha[e2->u.id] += dalpha / sys->weight[e2->u.id] - pre[1]; dalpha = (sys->bAlpha[e3->u.id] - dlambda1); sys->alpha[e3->u.id] += dalpha / sys->weight[e3->u.id] - pre[2]; /* clamp */ e = f->edge; do { if (sys->alpha[e->u.id] > (float)M_PI) { sys->alpha[e->u.id] = (float)M_PI; } else if (sys->alpha[e->u.id] < 0.0f) { sys->alpha[e->u.id] = 0.0f; } } while (e != f->edge); } for (i = 0; i < ninterior; i++) { sys->lambdaPlanar[i] += (float)EIG_linear_solver_variable_get(context, 0, i); sys->lambdaLength[i] += (float)EIG_linear_solver_variable_get(context, 0, ninterior + i); } } EIG_linear_solver_delete(context); return success; } static PBool p_chart_abf_solve(PChart *chart) { PVert *v; PFace *f; PEdge *e, *e1, *e2, *e3; PAbfSystem sys; int i; float /* lastnorm, */ /* UNUSED */ limit = (chart->nfaces > 100) ? 1.0f : 0.001f; /* setup id's */ sys.ninterior = sys.nfaces = sys.nangles = 0; for (v = chart->verts; v; v = v->nextlink) { if (p_vert_interior(v)) { v->flag |= PVERT_INTERIOR; v->u.id = sys.ninterior++; } else { v->flag &= ~PVERT_INTERIOR; } } for (f = chart->faces; f; f = f->nextlink) { e1 = f->edge; e2 = e1->next; e3 = e2->next; f->u.id = sys.nfaces++; /* angle id's are conveniently stored in half edges */ e1->u.id = sys.nangles++; e2->u.id = sys.nangles++; e3->u.id = sys.nangles++; } p_abf_setup_system(&sys); /* compute initial angles */ for (f = chart->faces; f; f = f->nextlink) { float a1, a2, a3; e1 = f->edge; e2 = e1->next; e3 = e2->next; p_face_angles(f, &a1, &a2, &a3); if (a1 < sys.minangle) { a1 = sys.minangle; } else if (a1 > sys.maxangle) { a1 = sys.maxangle; } if (a2 < sys.minangle) { a2 = sys.minangle; } else if (a2 > sys.maxangle) { a2 = sys.maxangle; } if (a3 < sys.minangle) { a3 = sys.minangle; } else if (a3 > sys.maxangle) { a3 = sys.maxangle; } sys.alpha[e1->u.id] = sys.beta[e1->u.id] = a1; sys.alpha[e2->u.id] = sys.beta[e2->u.id] = a2; sys.alpha[e3->u.id] = sys.beta[e3->u.id] = a3; sys.weight[e1->u.id] = 2.0f / (a1 * a1); sys.weight[e2->u.id] = 2.0f / (a2 * a2); sys.weight[e3->u.id] = 2.0f / (a3 * a3); } for (v = chart->verts; v; v = v->nextlink) { if (v->flag & PVERT_INTERIOR) { float anglesum = 0.0, scale; e = v->edge; do { anglesum += sys.beta[e->u.id]; e = e->next->next->pair; } while (e && (e != v->edge)); scale = (anglesum == 0.0f) ? 0.0f : 2.0f * (float)M_PI / anglesum; e = v->edge; do { sys.beta[e->u.id] = sys.alpha[e->u.id] = sys.beta[e->u.id] * scale; e = e->next->next->pair; } while (e && (e != v->edge)); } } if (sys.ninterior > 0) { p_abf_compute_sines(&sys); /* iteration */ /* lastnorm = 1e10; */ /* UNUSED */ for (i = 0; i < ABF_MAX_ITER; i++) { float norm = p_abf_compute_gradient(&sys, chart); /* lastnorm = norm; */ /* UNUSED */ if (norm < limit) { break; } if (!p_abf_matrix_invert(&sys, chart)) { param_warning("ABF failed to invert matrix"); p_abf_free_system(&sys); return P_FALSE; } p_abf_compute_sines(&sys); } if (i == ABF_MAX_ITER) { param_warning("ABF maximum iterations reached"); p_abf_free_system(&sys); return P_FALSE; } } chart->u.lscm.abf_alpha = MEM_dupallocN(sys.alpha); p_abf_free_system(&sys); return P_TRUE; } /* Least Squares Conformal Maps */ static void p_chart_pin_positions(PChart *chart, PVert **pin1, PVert **pin2) { if (!*pin1 || !*pin2 || *pin1 == *pin2) { /* degenerate case */ PFace *f = chart->faces; *pin1 = f->edge->vert; *pin2 = f->edge->next->vert; (*pin1)->uv[0] = 0.0f; (*pin1)->uv[1] = 0.5f; (*pin2)->uv[0] = 1.0f; (*pin2)->uv[1] = 0.5f; } else { int diru, dirv, dirx, diry; float sub[3]; sub_v3_v3v3(sub, (*pin1)->co, (*pin2)->co); sub[0] = fabsf(sub[0]); sub[1] = fabsf(sub[1]); sub[2] = fabsf(sub[2]); if ((sub[0] > sub[1]) && (sub[0] > sub[2])) { dirx = 0; diry = (sub[1] > sub[2]) ? 1 : 2; } else if ((sub[1] > sub[0]) && (sub[1] > sub[2])) { dirx = 1; diry = (sub[0] > sub[2]) ? 0 : 2; } else { dirx = 2; diry = (sub[0] > sub[1]) ? 0 : 1; } if (dirx == 2) { diru = 1; dirv = 0; } else { diru = 0; dirv = 1; } (*pin1)->uv[diru] = (*pin1)->co[dirx]; (*pin1)->uv[dirv] = (*pin1)->co[diry]; (*pin2)->uv[diru] = (*pin2)->co[dirx]; (*pin2)->uv[dirv] = (*pin2)->co[diry]; } } static PBool p_chart_symmetry_pins(PChart *chart, PEdge *outer, PVert **pin1, PVert **pin2) { PEdge *be, *lastbe = NULL, *maxe1 = NULL, *maxe2 = NULL, *be1, *be2; PEdge *cure = NULL, *firste1 = NULL, *firste2 = NULL, *nextbe; float maxlen = 0.0f, curlen = 0.0f, totlen = 0.0f, firstlen = 0.0f; float len1, len2; /* find longest series of verts split in the chart itself, these are * marked during construction */ be = outer; lastbe = p_boundary_edge_prev(be); do { float len = p_edge_length(be); totlen += len; nextbe = p_boundary_edge_next(be); if ((be->vert->flag & PVERT_SPLIT) || (lastbe->vert->flag & nextbe->vert->flag & PVERT_SPLIT)) { if (!cure) { if (be == outer) { firste1 = be; } cure = be; } else { curlen += p_edge_length(lastbe); } } else if (cure) { if (curlen > maxlen) { maxlen = curlen; maxe1 = cure; maxe2 = lastbe; } if (firste1 == cure) { firstlen = curlen; firste2 = lastbe; } curlen = 0.0f; cure = NULL; } lastbe = be; be = nextbe; } while (be != outer); /* make sure we also count a series of splits over the starting point */ if (cure && (cure != outer)) { firstlen += curlen + p_edge_length(be); if (firstlen > maxlen) { maxlen = firstlen; maxe1 = cure; maxe2 = firste2; } } if (!maxe1 || !maxe2 || (maxlen < 0.5f * totlen)) { return P_FALSE; } /* find pin1 in the split vertices */ be1 = maxe1; be2 = maxe2; len1 = 0.0f; len2 = 0.0f; do { if (len1 < len2) { len1 += p_edge_length(be1); be1 = p_boundary_edge_next(be1); } else { be2 = p_boundary_edge_prev(be2); len2 += p_edge_length(be2); } } while (be1 != be2); *pin1 = be1->vert; /* find pin2 outside the split vertices */ be1 = maxe1; be2 = maxe2; len1 = 0.0f; len2 = 0.0f; do { if (len1 < len2) { be1 = p_boundary_edge_prev(be1); len1 += p_edge_length(be1); } else { len2 += p_edge_length(be2); be2 = p_boundary_edge_next(be2); } } while (be1 != be2); *pin2 = be1->vert; p_chart_pin_positions(chart, pin1, pin2); return !equals_v3v3((*pin1)->co, (*pin2)->co); } static void p_chart_extrema_verts(PChart *chart, PVert **pin1, PVert **pin2) { float minv[3], maxv[3], dirlen; PVert *v, *minvert[3], *maxvert[3]; int i, dir; /* find minimum and maximum verts over x/y/z axes */ minv[0] = minv[1] = minv[2] = 1e20; maxv[0] = maxv[1] = maxv[2] = -1e20; minvert[0] = minvert[1] = minvert[2] = NULL; maxvert[0] = maxvert[1] = maxvert[2] = NULL; for (v = chart->verts; v; v = v->nextlink) { for (i = 0; i < 3; i++) { if (v->co[i] < minv[i]) { minv[i] = v->co[i]; minvert[i] = v; } if (v->co[i] > maxv[i]) { maxv[i] = v->co[i]; maxvert[i] = v; } } } /* find axes with longest distance */ dir = 0; dirlen = -1.0; for (i = 0; i < 3; i++) { if (maxv[i] - minv[i] > dirlen) { dir = i; dirlen = maxv[i] - minv[i]; } } *pin1 = minvert[dir]; *pin2 = maxvert[dir]; p_chart_pin_positions(chart, pin1, pin2); } static void p_chart_lscm_load_solution(PChart *chart) { LinearSolver *context = chart->u.lscm.context; PVert *v; for (v = chart->verts; v; v = v->nextlink) { v->uv[0] = EIG_linear_solver_variable_get(context, 0, 2 * v->u.id); v->uv[1] = EIG_linear_solver_variable_get(context, 0, 2 * v->u.id + 1); } } static void p_chart_lscm_begin(PChart *chart, PBool live, PBool abf) { PVert *v, *pin1, *pin2; PBool select = P_FALSE, deselect = P_FALSE; int npins = 0, id = 0; /* give vertices matrix indices and count pins */ for (v = chart->verts; v; v = v->nextlink) { if (v->flag & PVERT_PIN) { npins++; if (v->flag & PVERT_SELECT) { select = P_TRUE; } } if (!(v->flag & PVERT_SELECT)) { deselect = P_TRUE; } } if ((live && (!select || !deselect)) || (npins == 1)) { chart->u.lscm.context = NULL; } else { #if 0 p_chart_simplify_compute(chart); p_chart_topological_sanity_check(chart); #endif if (abf) { if (!p_chart_abf_solve(chart)) { param_warning("ABF solving failed: falling back to LSCM.\n"); } } if (npins <= 1) { /* not enough pins, lets find some ourself */ PEdge *outer; p_chart_boundaries(chart, NULL, &outer); /* outer can be NULL with non-finite coords. */ if (!(outer && p_chart_symmetry_pins(chart, outer, &pin1, &pin2))) { p_chart_extrema_verts(chart, &pin1, &pin2); } chart->u.lscm.pin1 = pin1; chart->u.lscm.pin2 = pin2; } for (v = chart->verts; v; v = v->nextlink) { v->u.id = id++; } chart->u.lscm.context = EIG_linear_least_squares_solver_new( 2 * chart->nfaces, 2 * chart->nverts, 1); } } static PBool p_chart_lscm_solve(PHandle *handle, PChart *chart) { LinearSolver *context = chart->u.lscm.context; PVert *v, *pin1 = chart->u.lscm.pin1, *pin2 = chart->u.lscm.pin2; PFace *f; const float *alpha = chart->u.lscm.abf_alpha; float area_pinned_up, area_pinned_down; bool flip_faces; int row; #if 0 /* TODO: make loading pins work for simplify/complexify. */ #endif for (v = chart->verts; v; v = v->nextlink) { if (v->flag & PVERT_PIN) { p_vert_load_pin_select_uvs(handle, v); /* reload for live */ } } if (chart->u.lscm.pin1) { EIG_linear_solver_variable_lock(context, 2 * pin1->u.id); EIG_linear_solver_variable_lock(context, 2 * pin1->u.id + 1); EIG_linear_solver_variable_lock(context, 2 * pin2->u.id); EIG_linear_solver_variable_lock(context, 2 * pin2->u.id + 1); EIG_linear_solver_variable_set(context, 0, 2 * pin1->u.id, pin1->uv[0]); EIG_linear_solver_variable_set(context, 0, 2 * pin1->u.id + 1, pin1->uv[1]); EIG_linear_solver_variable_set(context, 0, 2 * pin2->u.id, pin2->uv[0]); EIG_linear_solver_variable_set(context, 0, 2 * pin2->u.id + 1, pin2->uv[1]); } else { /* set and lock the pins */ for (v = chart->verts; v; v = v->nextlink) { if (v->flag & PVERT_PIN) { EIG_linear_solver_variable_lock(context, 2 * v->u.id); EIG_linear_solver_variable_lock(context, 2 * v->u.id + 1); EIG_linear_solver_variable_set(context, 0, 2 * v->u.id, v->uv[0]); EIG_linear_solver_variable_set(context, 0, 2 * v->u.id + 1, v->uv[1]); } } } /* detect up direction based on pinned vertices */ area_pinned_up = 0.0f; area_pinned_down = 0.0f; for (f = chart->faces; f; f = f->nextlink) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; if ((v1->flag & PVERT_PIN) && (v2->flag & PVERT_PIN) && (v3->flag & PVERT_PIN)) { float area = p_face_uv_area_signed(f); if (area > 0.0f) { area_pinned_up += area; } else { area_pinned_down -= area; } } } flip_faces = (area_pinned_down > area_pinned_up); /* construct matrix */ row = 0; for (f = chart->faces; f; f = f->nextlink) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; float a1, a2, a3, ratio, cosine, sine; float sina1, sina2, sina3, sinmax; if (alpha) { /* use abf angles if passed on */ a1 = *(alpha++); a2 = *(alpha++); a3 = *(alpha++); } else { p_face_angles(f, &a1, &a2, &a3); } if (flip_faces) { SWAP(float, a2, a3); SWAP(PEdge *, e2, e3); SWAP(PVert *, v2, v3); } sina1 = sinf(a1); sina2 = sinf(a2); sina3 = sinf(a3); sinmax = max_fff(sina1, sina2, sina3); /* shift vertices to find most stable order */ if (sina3 != sinmax) { SHIFT3(PVert *, v1, v2, v3); SHIFT3(float, a1, a2, a3); SHIFT3(float, sina1, sina2, sina3); if (sina2 == sinmax) { SHIFT3(PVert *, v1, v2, v3); SHIFT3(float, a1, a2, a3); SHIFT3(float, sina1, sina2, sina3); } } /* angle based lscm formulation */ ratio = (sina3 == 0.0f) ? 1.0f : sina2 / sina3; cosine = cosf(a1) * ratio; sine = sina1 * ratio; EIG_linear_solver_matrix_add(context, row, 2 * v1->u.id, cosine - 1.0f); EIG_linear_solver_matrix_add(context, row, 2 * v1->u.id + 1, -sine); EIG_linear_solver_matrix_add(context, row, 2 * v2->u.id, -cosine); EIG_linear_solver_matrix_add(context, row, 2 * v2->u.id + 1, sine); EIG_linear_solver_matrix_add(context, row, 2 * v3->u.id, 1.0); row++; EIG_linear_solver_matrix_add(context, row, 2 * v1->u.id, sine); EIG_linear_solver_matrix_add(context, row, 2 * v1->u.id + 1, cosine - 1.0f); EIG_linear_solver_matrix_add(context, row, 2 * v2->u.id, -sine); EIG_linear_solver_matrix_add(context, row, 2 * v2->u.id + 1, -cosine); EIG_linear_solver_matrix_add(context, row, 2 * v3->u.id + 1, 1.0); row++; } if (EIG_linear_solver_solve(context)) { p_chart_lscm_load_solution(chart); return P_TRUE; } else { for (v = chart->verts; v; v = v->nextlink) { v->uv[0] = 0.0f; v->uv[1] = 0.0f; } } return P_FALSE; } static void p_chart_lscm_end(PChart *chart) { if (chart->u.lscm.context) { EIG_linear_solver_delete(chart->u.lscm.context); } if (chart->u.lscm.abf_alpha) { MEM_freeN(chart->u.lscm.abf_alpha); chart->u.lscm.abf_alpha = NULL; } chart->u.lscm.context = NULL; chart->u.lscm.pin1 = NULL; chart->u.lscm.pin2 = NULL; } /* Stretch */ #define P_STRETCH_ITER 20 static void p_stretch_pin_boundary(PChart *chart) { PVert *v; for (v = chart->verts; v; v = v->nextlink) { if (v->edge->pair == NULL) { v->flag |= PVERT_PIN; } else { v->flag &= ~PVERT_PIN; } } } static float p_face_stretch(PFace *f) { float T, w, tmp[3]; float Ps[3], Pt[3]; float a, c, area; PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; area = p_face_uv_area_signed(f); if (area <= 0.0f) { /* flipped face -> infinite stretch */ return 1e10f; } w = 1.0f / (2.0f * area); /* compute derivatives */ copy_v3_v3(Ps, v1->co); mul_v3_fl(Ps, (v2->uv[1] - v3->uv[1])); copy_v3_v3(tmp, v2->co); mul_v3_fl(tmp, (v3->uv[1] - v1->uv[1])); add_v3_v3(Ps, tmp); copy_v3_v3(tmp, v3->co); mul_v3_fl(tmp, (v1->uv[1] - v2->uv[1])); add_v3_v3(Ps, tmp); mul_v3_fl(Ps, w); copy_v3_v3(Pt, v1->co); mul_v3_fl(Pt, (v3->uv[0] - v2->uv[0])); copy_v3_v3(tmp, v2->co); mul_v3_fl(tmp, (v1->uv[0] - v3->uv[0])); add_v3_v3(Pt, tmp); copy_v3_v3(tmp, v3->co); mul_v3_fl(tmp, (v2->uv[0] - v1->uv[0])); add_v3_v3(Pt, tmp); mul_v3_fl(Pt, w); /* Sander Tensor */ a = dot_v3v3(Ps, Ps); c = dot_v3v3(Pt, Pt); T = sqrtf(0.5f * (a + c)); if (f->flag & PFACE_FILLED) { T *= 0.2f; } return T; } static float p_stretch_compute_vertex(PVert *v) { PEdge *e = v->edge; float sum = 0.0f; do { sum += p_face_stretch(e->face); e = p_wheel_edge_next(e); } while (e && e != (v->edge)); return sum; } static void p_chart_stretch_minimize(PChart *chart, RNG *rng) { PVert *v; PEdge *e; int j, nedges; float orig_stretch, low, stretch_low, high, stretch_high, mid, stretch; float orig_uv[2], dir[2], random_angle, trusted_radius; for (v = chart->verts; v; v = v->nextlink) { if ((v->flag & PVERT_PIN) || !(v->flag & PVERT_SELECT)) { continue; } orig_stretch = p_stretch_compute_vertex(v); orig_uv[0] = v->uv[0]; orig_uv[1] = v->uv[1]; /* move vertex in a random direction */ trusted_radius = 0.0f; nedges = 0; e = v->edge; do { trusted_radius += p_edge_uv_length(e); nedges++; e = p_wheel_edge_next(e); } while (e && e != (v->edge)); trusted_radius /= 2 * nedges; random_angle = BLI_rng_get_float(rng) * 2.0f * (float)M_PI; dir[0] = trusted_radius * cosf(random_angle); dir[1] = trusted_radius * sinf(random_angle); /* calculate old and new stretch */ low = 0; stretch_low = orig_stretch; add_v2_v2v2(v->uv, orig_uv, dir); high = 1; stretch = stretch_high = p_stretch_compute_vertex(v); /* binary search for lowest stretch position */ for (j = 0; j < P_STRETCH_ITER; j++) { mid = 0.5f * (low + high); v->uv[0] = orig_uv[0] + mid * dir[0]; v->uv[1] = orig_uv[1] + mid * dir[1]; stretch = p_stretch_compute_vertex(v); if (stretch_low < stretch_high) { high = mid; stretch_high = stretch; } else { low = mid; stretch_low = stretch; } } /* no luck, stretch has increased, reset to old values */ if (stretch >= orig_stretch) { copy_v2_v2(v->uv, orig_uv); } } } /* Minimum area enclosing rectangle for packing */ static int p_compare_geometric_uv(const void *a, const void *b) { const PVert *v1 = *(const PVert *const *)a; const PVert *v2 = *(const PVert *const *)b; if (v1->uv[0] < v2->uv[0]) { return -1; } else if (v1->uv[0] == v2->uv[0]) { if (v1->uv[1] < v2->uv[1]) { return -1; } else if (v1->uv[1] == v2->uv[1]) { return 0; } else { return 1; } } else { return 1; } } static PBool p_chart_convex_hull(PChart *chart, PVert ***r_verts, int *r_nverts, int *r_right) { /* Graham algorithm, taken from: * http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/117225 */ PEdge *be, *e; int npoints = 0, i, ulen, llen; PVert **U, **L, **points, **p; p_chart_boundaries(chart, NULL, &be); if (!be) { return P_FALSE; } e = be; do { npoints++; e = p_boundary_edge_next(e); } while (e != be); p = points = (PVert **)MEM_mallocN(sizeof(PVert *) * npoints * 2, "PCHullpoints"); U = (PVert **)MEM_mallocN(sizeof(PVert *) * npoints, "PCHullU"); L = (PVert **)MEM_mallocN(sizeof(PVert *) * npoints, "PCHullL"); e = be; do { *p = e->vert; p++; e = p_boundary_edge_next(e); } while (e != be); qsort(points, npoints, sizeof(PVert *), p_compare_geometric_uv); ulen = llen = 0; for (p = points, i = 0; i < npoints; i++, p++) { while ((ulen > 1) && (p_area_signed(U[ulen - 2]->uv, (*p)->uv, U[ulen - 1]->uv) <= 0)) { ulen--; } while ((llen > 1) && (p_area_signed(L[llen - 2]->uv, (*p)->uv, L[llen - 1]->uv) >= 0)) { llen--; } U[ulen] = *p; ulen++; L[llen] = *p; llen++; } npoints = 0; for (p = points, i = 0; i < ulen; i++, p++, npoints++) { *p = U[i]; } /* the first and last point in L are left out, since they are also in U */ for (i = llen - 2; i > 0; i--, p++, npoints++) { *p = L[i]; } *r_verts = points; *r_nverts = npoints; *r_right = ulen - 1; MEM_freeN(U); MEM_freeN(L); return P_TRUE; } static float p_rectangle_area(float *p1, float *dir, float *p2, float *p3, float *p4) { /* given 4 points on the rectangle edges and the direction of on edge, * compute the area of the rectangle */ float orthodir[2], corner1[2], corner2[2], corner3[2]; orthodir[0] = dir[1]; orthodir[1] = -dir[0]; if (!p_intersect_line_2d_dir(p1, dir, p2, orthodir, corner1)) { return 1e10; } if (!p_intersect_line_2d_dir(p1, dir, p4, orthodir, corner2)) { return 1e10; } if (!p_intersect_line_2d_dir(p3, dir, p4, orthodir, corner3)) { return 1e10; } return len_v2v2(corner1, corner2) * len_v2v2(corner2, corner3); } static float p_chart_minimum_area_angle(PChart *chart) { /* minimum area enclosing rectangle with rotating calipers, info: * http://cgm.cs.mcgill.ca/~orm/maer.html */ float rotated, minarea, minangle, area, len; float *angles, miny, maxy, v[2], a[4], mina; int npoints, right, i_min, i_max, i, idx[4], nextidx; PVert **points, *p1, *p2, *p3, *p4, *p1n; /* compute convex hull */ if (!p_chart_convex_hull(chart, &points, &npoints, &right)) { return 0.0; } /* find left/top/right/bottom points, and compute angle for each point */ angles = MEM_mallocN(sizeof(float) * npoints, "PMinAreaAngles"); i_min = i_max = 0; miny = 1e10; maxy = -1e10; for (i = 0; i < npoints; i++) { p1 = (i == 0) ? points[npoints - 1] : points[i - 1]; p2 = points[i]; p3 = (i == npoints - 1) ? points[0] : points[i + 1]; angles[i] = (float)M_PI - p_vec2_angle(p1->uv, p2->uv, p3->uv); if (points[i]->uv[1] < miny) { miny = points[i]->uv[1]; i_min = i; } if (points[i]->uv[1] > maxy) { maxy = points[i]->uv[1]; i_max = i; } } /* left, top, right, bottom */ idx[0] = 0; idx[1] = i_max; idx[2] = right; idx[3] = i_min; v[0] = points[idx[0]]->uv[0]; v[1] = points[idx[0]]->uv[1] + 1.0f; a[0] = p_vec2_angle(points[(idx[0] + 1) % npoints]->uv, points[idx[0]]->uv, v); v[0] = points[idx[1]]->uv[0] + 1.0f; v[1] = points[idx[1]]->uv[1]; a[1] = p_vec2_angle(points[(idx[1] + 1) % npoints]->uv, points[idx[1]]->uv, v); v[0] = points[idx[2]]->uv[0]; v[1] = points[idx[2]]->uv[1] - 1.0f; a[2] = p_vec2_angle(points[(idx[2] + 1) % npoints]->uv, points[idx[2]]->uv, v); v[0] = points[idx[3]]->uv[0] - 1.0f; v[1] = points[idx[3]]->uv[1]; a[3] = p_vec2_angle(points[(idx[3] + 1) % npoints]->uv, points[idx[3]]->uv, v); /* 4 rotating calipers */ rotated = 0.0; minarea = 1e10; minangle = 0.0; while (rotated <= (float)(M_PI / 2.0)) { /* INVESTIGATE: how far to rotate? */ /* rotate with the smallest angle */ i_min = 0; mina = 1e10; for (i = 0; i < 4; i++) { if (a[i] < mina) { mina = a[i]; i_min = i; } } rotated += mina; nextidx = (idx[i_min] + 1) % npoints; a[i_min] = angles[nextidx]; a[(i_min + 1) % 4] = a[(i_min + 1) % 4] - mina; a[(i_min + 2) % 4] = a[(i_min + 2) % 4] - mina; a[(i_min + 3) % 4] = a[(i_min + 3) % 4] - mina; /* compute area */ p1 = points[idx[i_min]]; p1n = points[nextidx]; p2 = points[idx[(i_min + 1) % 4]]; p3 = points[idx[(i_min + 2) % 4]]; p4 = points[idx[(i_min + 3) % 4]]; len = len_v2v2(p1->uv, p1n->uv); if (len > 0.0f) { len = 1.0f / len; v[0] = (p1n->uv[0] - p1->uv[0]) * len; v[1] = (p1n->uv[1] - p1->uv[1]) * len; area = p_rectangle_area(p1->uv, v, p2->uv, p3->uv, p4->uv); /* remember smallest area */ if (area < minarea) { minarea = area; minangle = rotated; } } idx[i_min] = nextidx; } /* try keeping rotation as small as possible */ if (minangle > (float)(M_PI / 4)) { minangle -= (float)(M_PI / 2.0); } MEM_freeN(angles); MEM_freeN(points); return minangle; } static void p_chart_rotate_minimum_area(PChart *chart) { float angle = p_chart_minimum_area_angle(chart); float sine = sinf(angle); float cosine = cosf(angle); PVert *v; for (v = chart->verts; v; v = v->nextlink) { float oldu = v->uv[0], oldv = v->uv[1]; v->uv[0] = cosine * oldu - sine * oldv; v->uv[1] = sine * oldu + cosine * oldv; } } /* Area Smoothing */ /* 2d bsp tree for inverse mapping - that's a bit silly */ typedef struct SmoothTriangle { float co1[2], co2[2], co3[2]; float oco1[2], oco2[2], oco3[2]; } SmoothTriangle; typedef struct SmoothNode { struct SmoothNode *c1, *c2; SmoothTriangle **tri; float split; int axis, ntri; } SmoothNode; static void p_barycentric_2d( const float v1[2], const float v2[2], const float v3[2], const float p[2], float b[3]) { float a[2], c[2], h[2], div; a[0] = v2[0] - v1[0]; a[1] = v2[1] - v1[1]; c[0] = v3[0] - v1[0]; c[1] = v3[1] - v1[1]; div = a[0] * c[1] - a[1] * c[0]; if (div == 0.0f) { b[0] = 1.0f / 3.0f; b[1] = 1.0f / 3.0f; b[2] = 1.0f / 3.0f; } else { h[0] = p[0] - v1[0]; h[1] = p[1] - v1[1]; div = 1.0f / div; b[1] = (h[0] * c[1] - h[1] * c[0]) * div; b[2] = (a[0] * h[1] - a[1] * h[0]) * div; b[0] = 1.0f - b[1] - b[2]; } } static PBool p_triangle_inside(SmoothTriangle *t, float co[2]) { float b[3]; p_barycentric_2d(t->co1, t->co2, t->co3, co, b); if ((b[0] >= 0.0f) && (b[1] >= 0.0f) && (b[2] >= 0.0f)) { co[0] = t->oco1[0] * b[0] + t->oco2[0] * b[1] + t->oco3[0] * b[2]; co[1] = t->oco1[1] * b[0] + t->oco2[1] * b[1] + t->oco3[1] * b[2]; return P_TRUE; } return P_FALSE; } static SmoothNode *p_node_new( MemArena *arena, SmoothTriangle **tri, int ntri, float *bmin, float *bmax, int depth) { SmoothNode *node = BLI_memarena_alloc(arena, sizeof(*node)); int axis, i, t1size = 0, t2size = 0; float split, /* mi, */ /* UNUSED */ mx; SmoothTriangle **t1, **t2, *t; node->tri = tri; node->ntri = ntri; if (ntri <= 10 || depth >= 15) { return node; } t1 = MEM_mallocN(sizeof(*t1) * ntri, "PNodeTri1"); t2 = MEM_mallocN(sizeof(*t2) * ntri, "PNodeTri1"); axis = (bmax[0] - bmin[0] > bmax[1] - bmin[1]) ? 0 : 1; split = 0.5f * (bmin[axis] + bmax[axis]); for (i = 0; i < ntri; i++) { t = tri[i]; if ((t->co1[axis] <= split) || (t->co2[axis] <= split) || (t->co3[axis] <= split)) { t1[t1size] = t; t1size++; } if ((t->co1[axis] >= split) || (t->co2[axis] >= split) || (t->co3[axis] >= split)) { t2[t2size] = t; t2size++; } } if ((t1size == t2size) && (t1size == ntri)) { MEM_freeN(t1); MEM_freeN(t2); return node; } node->tri = NULL; node->ntri = 0; MEM_freeN(tri); node->axis = axis; node->split = split; /* mi = bmin[axis]; */ /* UNUSED */ mx = bmax[axis]; bmax[axis] = split; node->c1 = p_node_new(arena, t1, t1size, bmin, bmax, depth + 1); bmin[axis] = bmax[axis]; bmax[axis] = mx; node->c2 = p_node_new(arena, t2, t2size, bmin, bmax, depth + 1); return node; } static void p_node_delete(SmoothNode *node) { if (node->c1) { p_node_delete(node->c1); } if (node->c2) { p_node_delete(node->c2); } if (node->tri) { MEM_freeN(node->tri); } } static PBool p_node_intersect(SmoothNode *node, float co[2]) { int i; if (node->tri) { for (i = 0; i < node->ntri; i++) { if (p_triangle_inside(node->tri[i], co)) { return P_TRUE; } } return P_FALSE; } else { if (co[node->axis] < node->split) { return p_node_intersect(node->c1, co); } else { return p_node_intersect(node->c2, co); } } } /* smoothing */ static int p_compare_float(const void *a_, const void *b_) { const float a = *(const float *)a_; const float b = *(const float *)b_; if (a < b) { return -1; } else if (a == b) { return 0; } else { return 1; } } static float p_smooth_median_edge_length(PChart *chart) { PEdge *e; float *lengths = MEM_mallocN(sizeof(chart->edges) * chart->nedges, "PMedianLength"); float median; int i; /* ok, so I'm lazy */ for (i = 0, e = chart->edges; e; e = e->nextlink, i++) { lengths[i] = p_edge_length(e); } qsort(lengths, i, sizeof(float), p_compare_float); median = lengths[i / 2]; MEM_freeN(lengths); return median; } static float p_smooth_distortion(PEdge *e, float avg2d, float avg3d) { float len2d = p_edge_uv_length(e) * avg3d; float len3d = p_edge_length(e) * avg2d; return (len3d == 0.0f) ? 0.0f : len2d / len3d; } static void p_smooth(PChart *chart) { PEdge *e; PVert *v; PFace *f; int j, it2, maxiter2, it; int nedges = chart->nedges, nwheel, gridx, gridy; int edgesx, edgesy, nsize, esize, i, x, y, maxiter, totiter; float minv[2], maxv[2], median, invmedian, avglen2d, avglen3d; float center[2], dx, dy, *nodes, dlimit, d, *oldnodesx, *oldnodesy; float *nodesx, *nodesy, *hedges, *vedges, climit, moved, padding; SmoothTriangle *triangles, *t, *t2, **tri, **trip; SmoothNode *root; MemArena *arena; if (nedges == 0) { return; } p_chart_uv_bbox(chart, minv, maxv); median = p_smooth_median_edge_length(chart) * 0.10f; if (median == 0.0f) { return; } invmedian = 1.0f / median; /* compute edge distortion */ avglen2d = avglen3d = 0.0; for (e = chart->edges; e; e = e->nextlink) { avglen2d += p_edge_uv_length(e); avglen3d += p_edge_length(e); } avglen2d /= nedges; avglen3d /= nedges; for (v = chart->verts; v; v = v->nextlink) { v->u.distortion = 0.0; nwheel = 0; e = v->edge; do { v->u.distortion += p_smooth_distortion(e, avglen2d, avglen3d); nwheel++; e = e->next->next->pair; } while (e && (e != v->edge)); v->u.distortion /= nwheel; } /* need to do excessive grid size checking still */ center[0] = 0.5f * (minv[0] + maxv[0]); center[1] = 0.5f * (minv[1] + maxv[1]); dx = 0.5f * (maxv[0] - minv[0]); dy = 0.5f * (maxv[1] - minv[1]); padding = 0.15f; dx += padding * dx + 2.0f * median; dy += padding * dy + 2.0f * median; gridx = (int)(dx * invmedian); gridy = (int)(dy * invmedian); minv[0] = center[0] - median * gridx; minv[1] = center[1] - median * gridy; maxv[0] = center[0] + median * gridx; maxv[1] = center[1] + median * gridy; /* create grid */ gridx = gridx * 2 + 1; gridy = gridy * 2 + 1; if ((gridx <= 2) || (gridy <= 2)) { return; } edgesx = gridx - 1; edgesy = gridy - 1; nsize = gridx * gridy; esize = edgesx * edgesy; nodes = MEM_mallocN(sizeof(float) * nsize, "PSmoothNodes"); nodesx = MEM_mallocN(sizeof(float) * nsize, "PSmoothNodesX"); nodesy = MEM_mallocN(sizeof(float) * nsize, "PSmoothNodesY"); oldnodesx = MEM_mallocN(sizeof(float) * nsize, "PSmoothOldNodesX"); oldnodesy = MEM_mallocN(sizeof(float) * nsize, "PSmoothOldNodesY"); hedges = MEM_mallocN(sizeof(float) * esize, "PSmoothHEdges"); vedges = MEM_mallocN(sizeof(float) * esize, "PSmoothVEdges"); if (!nodes || !nodesx || !nodesy || !oldnodesx || !oldnodesy || !hedges || !vedges) { if (nodes) { MEM_freeN(nodes); } if (nodesx) { MEM_freeN(nodesx); } if (nodesy) { MEM_freeN(nodesy); } if (oldnodesx) { MEM_freeN(oldnodesx); } if (oldnodesy) { MEM_freeN(oldnodesy); } if (hedges) { MEM_freeN(hedges); } if (vedges) { MEM_freeN(vedges); } // printf("Not enough memory for area smoothing grid"); return; } for (x = 0; x < gridx; x++) { for (y = 0; y < gridy; y++) { i = x + y * gridx; nodesx[i] = minv[0] + median * x; nodesy[i] = minv[1] + median * y; nodes[i] = 1.0f; } } /* embed in grid */ for (f = chart->faces; f; f = f->nextlink) { PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; float fmin[2], fmax[2]; int bx1, by1, bx2, by2; INIT_MINMAX2(fmin, fmax); minmax_v2v2_v2(fmin, fmax, e1->vert->uv); minmax_v2v2_v2(fmin, fmax, e2->vert->uv); minmax_v2v2_v2(fmin, fmax, e3->vert->uv); bx1 = (int)((fmin[0] - minv[0]) * invmedian); by1 = (int)((fmin[1] - minv[1]) * invmedian); bx2 = (int)((fmax[0] - minv[0]) * invmedian + 2); by2 = (int)((fmax[1] - minv[1]) * invmedian + 2); for (x = bx1; x < bx2; x++) { for (y = by1; y < by2; y++) { float p[2], b[3]; i = x + y * gridx; p[0] = nodesx[i]; p[1] = nodesy[i]; p_barycentric_2d(e1->vert->uv, e2->vert->uv, e3->vert->uv, p, b); if ((b[0] > 0.0f) && (b[1] > 0.0f) && (b[2] > 0.0f)) { nodes[i] = e1->vert->u.distortion * b[0]; nodes[i] += e2->vert->u.distortion * b[1]; nodes[i] += e3->vert->u.distortion * b[2]; } } } } /* smooth the grid */ maxiter = 10; totiter = 0; climit = 0.00001f * nsize; for (it = 0; it < maxiter; it++) { moved = 0.0f; for (x = 0; x < edgesx; x++) { for (y = 0; y < edgesy; y++) { i = x + y * gridx; j = x + y * edgesx; hedges[j] = (nodes[i] + nodes[i + 1]) * 0.5f; vedges[j] = (nodes[i] + nodes[i + gridx]) * 0.5f; /* we do *inverse* mapping */ hedges[j] = 1.0f / hedges[j]; vedges[j] = 1.0f / vedges[j]; } } maxiter2 = 50; dlimit = 0.0001f; for (it2 = 0; it2 < maxiter2; it2++) { d = 0.0f; totiter += 1; memcpy(oldnodesx, nodesx, sizeof(float) * nsize); memcpy(oldnodesy, nodesy, sizeof(float) * nsize); for (x = 1; x < gridx - 1; x++) { for (y = 1; y < gridy - 1; y++) { float p[2], oldp[2], sum1, sum2, diff[2], length; i = x + gridx * y; j = x + edgesx * y; oldp[0] = oldnodesx[i]; oldp[1] = oldnodesy[i]; sum1 = hedges[j - 1] * oldnodesx[i - 1]; sum1 += hedges[j] * oldnodesx[i + 1]; sum1 += vedges[j - edgesx] * oldnodesx[i - gridx]; sum1 += vedges[j] * oldnodesx[i + gridx]; sum2 = hedges[j - 1]; sum2 += hedges[j]; sum2 += vedges[j - edgesx]; sum2 += vedges[j]; nodesx[i] = sum1 / sum2; sum1 = hedges[j - 1] * oldnodesy[i - 1]; sum1 += hedges[j] * oldnodesy[i + 1]; sum1 += vedges[j - edgesx] * oldnodesy[i - gridx]; sum1 += vedges[j] * oldnodesy[i + gridx]; nodesy[i] = sum1 / sum2; p[0] = nodesx[i]; p[1] = nodesy[i]; diff[0] = p[0] - oldp[0]; diff[1] = p[1] - oldp[1]; length = len_v2(diff); d = max_ff(d, length); moved += length; } } if (d < dlimit) { break; } } if (moved < climit) { break; } } MEM_freeN(oldnodesx); MEM_freeN(oldnodesy); MEM_freeN(hedges); MEM_freeN(vedges); /* create bsp */ t = triangles = MEM_mallocN(sizeof(SmoothTriangle) * esize * 2, "PSmoothTris"); trip = tri = MEM_mallocN(sizeof(SmoothTriangle *) * esize * 2, "PSmoothTriP"); if (!triangles || !tri) { MEM_freeN(nodes); MEM_freeN(nodesx); MEM_freeN(nodesy); if (triangles) { MEM_freeN(triangles); } if (tri) { MEM_freeN(tri); } // printf("Not enough memory for area smoothing grid"); return; } for (x = 0; x < edgesx; x++) { for (y = 0; y < edgesy; y++) { i = x + y * gridx; t->co1[0] = nodesx[i]; t->co1[1] = nodesy[i]; t->co2[0] = nodesx[i + 1]; t->co2[1] = nodesy[i + 1]; t->co3[0] = nodesx[i + gridx]; t->co3[1] = nodesy[i + gridx]; t->oco1[0] = minv[0] + x * median; t->oco1[1] = minv[1] + y * median; t->oco2[0] = minv[0] + (x + 1) * median; t->oco2[1] = minv[1] + y * median; t->oco3[0] = minv[0] + x * median; t->oco3[1] = minv[1] + (y + 1) * median; t2 = t + 1; t2->co1[0] = nodesx[i + gridx + 1]; t2->co1[1] = nodesy[i + gridx + 1]; t2->oco1[0] = minv[0] + (x + 1) * median; t2->oco1[1] = minv[1] + (y + 1) * median; t2->co2[0] = t->co2[0]; t2->co2[1] = t->co2[1]; t2->oco2[0] = t->oco2[0]; t2->oco2[1] = t->oco2[1]; t2->co3[0] = t->co3[0]; t2->co3[1] = t->co3[1]; t2->oco3[0] = t->oco3[0]; t2->oco3[1] = t->oco3[1]; *trip = t; trip++; t++; *trip = t; trip++; t++; } } MEM_freeN(nodes); MEM_freeN(nodesx); MEM_freeN(nodesy); arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 16), "param smooth arena"); root = p_node_new(arena, tri, esize * 2, minv, maxv, 0); for (v = chart->verts; v; v = v->nextlink) { if (!p_node_intersect(root, v->uv)) { param_warning("area smoothing error: couldn't find mapping triangle\n"); } } p_node_delete(root); BLI_memarena_free(arena); MEM_freeN(triangles); } /* Exported */ ParamHandle *param_construct_begin(void) { PHandle *handle = MEM_callocN(sizeof(*handle), "PHandle"); handle->construction_chart = p_chart_new(handle); handle->state = PHANDLE_STATE_ALLOCATED; handle->arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 16), "param construct arena"); handle->polyfill_arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "param polyfill arena"); handle->polyfill_heap = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE); handle->aspx = 1.0f; handle->aspy = 1.0f; handle->do_aspect = false; handle->hash_verts = phash_new((PHashLink **)&handle->construction_chart->verts, 1); handle->hash_edges = phash_new((PHashLink **)&handle->construction_chart->edges, 1); handle->hash_faces = phash_new((PHashLink **)&handle->construction_chart->faces, 1); return (ParamHandle *)handle; } void param_aspect_ratio(ParamHandle *handle, float aspx, float aspy) { PHandle *phandle = (PHandle *)handle; phandle->aspx = aspx; phandle->aspy = aspy; phandle->do_aspect = true; } void param_delete(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; int i; param_assert((phandle->state == PHANDLE_STATE_ALLOCATED) || (phandle->state == PHANDLE_STATE_CONSTRUCTED)); for (i = 0; i < phandle->ncharts; i++) { p_chart_delete(phandle->charts[i]); } if (phandle->charts) { MEM_freeN(phandle->charts); } if (phandle->construction_chart) { p_chart_delete(phandle->construction_chart); phash_delete(phandle->hash_verts); phash_delete(phandle->hash_edges); phash_delete(phandle->hash_faces); } BLI_memarena_free(phandle->arena); BLI_memarena_free(phandle->polyfill_arena); BLI_heap_free(phandle->polyfill_heap, NULL); MEM_freeN(phandle); } static void p_add_ngon(ParamHandle *handle, ParamKey key, int nverts, ParamKey *vkeys, float **co, float **uv, ParamBool *pin, ParamBool *select) { /* Allocate memory for polyfill. */ PHandle *phandle = (PHandle *)handle; MemArena *arena = phandle->polyfill_arena; Heap *heap = phandle->polyfill_heap; uint nfilltri = nverts - 2; uint(*tris)[3] = BLI_memarena_alloc(arena, sizeof(*tris) * (size_t)nfilltri); float(*projverts)[2] = BLI_memarena_alloc(arena, sizeof(*projverts) * (size_t)nverts); /* Calc normal, flipped: to get a positive 2d cross product. */ float normal[3]; zero_v3(normal); const float *co_curr, *co_prev = co[nverts - 1]; for (int j = 0; j < nverts; j++) { co_curr = co[j]; add_newell_cross_v3_v3v3(normal, co_prev, co_curr); co_prev = co_curr; } if (UNLIKELY(normalize_v3(normal) == 0.0f)) { normal[2] = 1.0f; } /* Project verts to 2d. */ float axis_mat[3][3]; axis_dominant_v3_to_m3_negate(axis_mat, normal); for (int j = 0; j < nverts; j++) { mul_v2_m3v3(projverts[j], axis_mat, co[j]); } BLI_polyfill_calc_arena(projverts, nverts, 1, tris, arena); /* Beautify helps avoid thin triangles that give numerical problems. */ BLI_polyfill_beautify(projverts, nverts, tris, arena, heap); /* Add triangles. */ for (int j = 0; j < nfilltri; j++) { uint *tri = tris[j]; uint v0 = tri[0]; uint v1 = tri[1]; uint v2 = tri[2]; ParamKey tri_vkeys[3] = {vkeys[v0], vkeys[v1], vkeys[v2]}; float *tri_co[3] = {co[v0], co[v1], co[v2]}; float *tri_uv[3] = {uv[v0], uv[v1], uv[v2]}; ParamBool tri_pin[3] = {pin[v0], pin[v1], pin[v2]}; ParamBool tri_select[3] = {select[v0], select[v1], select[v2]}; param_face_add(handle, key, 3, tri_vkeys, tri_co, tri_uv, tri_pin, tri_select); } BLI_memarena_clear(arena); } void param_face_add(ParamHandle *handle, ParamKey key, int nverts, ParamKey *vkeys, float *co[4], float *uv[4], ParamBool *pin, ParamBool *select) { PHandle *phandle = (PHandle *)handle; param_assert(phash_lookup(phandle->hash_faces, key) == NULL); param_assert(phandle->state == PHANDLE_STATE_ALLOCATED); param_assert((nverts == 3) || (nverts == 4)); if (nverts > 4) { /* ngon */ p_add_ngon(handle, key, nverts, vkeys, co, uv, pin, select); } else if (nverts == 4) { /* quad */ if (p_quad_split_direction(phandle, co, vkeys)) { p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select); p_face_add_construct(phandle, key, vkeys, co, uv, 0, 2, 3, pin, select); } else { p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 3, pin, select); p_face_add_construct(phandle, key, vkeys, co, uv, 1, 2, 3, pin, select); } } else if (!p_face_exists(phandle, vkeys, 0, 1, 2)) { /* triangle */ p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select); } } void param_edge_set_seam(ParamHandle *handle, ParamKey *vkeys) { PHandle *phandle = (PHandle *)handle; PEdge *e; param_assert(phandle->state == PHANDLE_STATE_ALLOCATED); e = p_edge_lookup(phandle, vkeys); if (e) { e->flag |= PEDGE_SEAM; } } void param_construct_end(ParamHandle *handle, ParamBool fill, ParamBool impl) { PHandle *phandle = (PHandle *)handle; PChart *chart = phandle->construction_chart; int i, j, nboundaries = 0; PEdge *outer; param_assert(phandle->state == PHANDLE_STATE_ALLOCATED); phandle->ncharts = p_connect_pairs(phandle, (PBool)impl); phandle->charts = p_split_charts(phandle, chart, phandle->ncharts); p_chart_delete(phandle->construction_chart); phandle->construction_chart = NULL; phash_delete(phandle->hash_verts); phash_delete(phandle->hash_edges); phash_delete(phandle->hash_faces); phandle->hash_verts = phandle->hash_edges = phandle->hash_faces = NULL; for (i = j = 0; i < phandle->ncharts; i++) { PVert *v; chart = phandle->charts[i]; p_chart_boundaries(chart, &nboundaries, &outer); if (!impl && nboundaries == 0) { p_chart_delete(chart); continue; } phandle->charts[j] = chart; j++; if (fill && (nboundaries > 1)) { p_chart_fill_boundaries(chart, outer); } for (v = chart->verts; v; v = v->nextlink) { p_vert_load_pin_select_uvs(handle, v); } } phandle->ncharts = j; phandle->state = PHANDLE_STATE_CONSTRUCTED; } void param_lscm_begin(ParamHandle *handle, ParamBool live, ParamBool abf) { PHandle *phandle = (PHandle *)handle; PFace *f; int i; param_assert(phandle->state == PHANDLE_STATE_CONSTRUCTED); phandle->state = PHANDLE_STATE_LSCM; for (i = 0; i < phandle->ncharts; i++) { for (f = phandle->charts[i]->faces; f; f = f->nextlink) { p_face_backup_uvs(f); } p_chart_lscm_begin(phandle->charts[i], (PBool)live, (PBool)abf); } } void param_lscm_solve(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; PChart *chart; int i; PBool result; param_assert(phandle->state == PHANDLE_STATE_LSCM); for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; if (chart->u.lscm.context) { result = p_chart_lscm_solve(phandle, chart); if (result && !(chart->flag & PCHART_HAS_PINS)) { p_chart_rotate_minimum_area(chart); } if (!result || (chart->u.lscm.pin1)) { p_chart_lscm_end(chart); } } } } void param_lscm_end(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; int i; param_assert(phandle->state == PHANDLE_STATE_LSCM); for (i = 0; i < phandle->ncharts; i++) { p_chart_lscm_end(phandle->charts[i]); #if 0 p_chart_complexify(phandle->charts[i]); #endif } phandle->state = PHANDLE_STATE_CONSTRUCTED; } void param_stretch_begin(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; PChart *chart; PVert *v; PFace *f; int i; param_assert(phandle->state == PHANDLE_STATE_CONSTRUCTED); phandle->state = PHANDLE_STATE_STRETCH; phandle->rng = BLI_rng_new(31415926); phandle->blend = 0.0f; for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; for (v = chart->verts; v; v = v->nextlink) { v->flag &= ~PVERT_PIN; /* don't use user-defined pins */ } p_stretch_pin_boundary(chart); for (f = chart->faces; f; f = f->nextlink) { p_face_backup_uvs(f); f->u.area3d = p_face_area(f); } } } void param_stretch_blend(ParamHandle *handle, float blend) { PHandle *phandle = (PHandle *)handle; param_assert(phandle->state == PHANDLE_STATE_STRETCH); phandle->blend = blend; } void param_stretch_iter(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; PChart *chart; int i; param_assert(phandle->state == PHANDLE_STATE_STRETCH); for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; p_chart_stretch_minimize(chart, phandle->rng); } } void param_stretch_end(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; param_assert(phandle->state == PHANDLE_STATE_STRETCH); phandle->state = PHANDLE_STATE_CONSTRUCTED; BLI_rng_free(phandle->rng); phandle->rng = NULL; } void param_smooth_area(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; int i; param_assert(phandle->state == PHANDLE_STATE_CONSTRUCTED); for (i = 0; i < phandle->ncharts; i++) { PChart *chart = phandle->charts[i]; PVert *v; for (v = chart->verts; v; v = v->nextlink) { v->flag &= ~PVERT_PIN; } p_smooth(chart); } } /* don't pack, just rotate (used for better packing) */ static void param_pack_rotate(ParamHandle *handle, bool ignore_pinned) { PChart *chart; int i; PHandle *phandle = (PHandle *)handle; for (i = 0; i < phandle->ncharts; i++) { float(*points)[2]; float angle; chart = phandle->charts[i]; if (ignore_pinned && (chart->flag & PCHART_HAS_PINS)) { continue; } points = MEM_mallocN(sizeof(*points) * chart->nverts, __func__); p_chart_uv_to_array(chart, points); angle = BLI_convexhull_aabb_fit_points_2d(points, chart->nverts); MEM_freeN(points); if (angle != 0.0f) { float mat[2][2]; angle_to_mat2(mat, angle); p_chart_uv_transform(chart, mat); } } } void param_pack(ParamHandle *handle, float margin, bool do_rotate, bool ignore_pinned) { /* box packing variables */ BoxPack *boxarray, *box; float tot_width, tot_height, scale; PChart *chart; int i, unpacked = 0; float trans[2]; double area = 0.0; PHandle *phandle = (PHandle *)handle; if (phandle->ncharts == 0) { return; } /* this could be its own function */ if (do_rotate) { param_pack_rotate(handle, ignore_pinned); } if (phandle->aspx != phandle->aspy) { param_scale(handle, 1.0f / phandle->aspx, 1.0f / phandle->aspy); } /* we may not use all these boxes */ boxarray = MEM_mallocN(phandle->ncharts * sizeof(BoxPack), "BoxPack box"); for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; if (ignore_pinned && (chart->flag & PCHART_HAS_PINS)) { unpacked++; continue; } box = boxarray + (i - unpacked); p_chart_uv_bbox(chart, trans, chart->u.pack.size); trans[0] = -trans[0]; trans[1] = -trans[1]; p_chart_uv_translate(chart, trans); box->w = chart->u.pack.size[0] + trans[0]; box->h = chart->u.pack.size[1] + trans[1]; box->index = i; /* warning this index skips PCHART_HAS_PINS boxes */ if (margin > 0.0f) { area += (double)sqrtf(box->w * box->h); } } if (margin > 0.0f) { /* multiply the margin by the area to give predictable results not dependent on UV scale, * ...Without using the area running pack multiple times also gives a bad feedback loop. * multiply by 0.1 so the margin value from the UI can be from * 0.0 to 1.0 but not give a massive margin */ margin = (margin * (float)area) * 0.1f; unpacked = 0; for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; if (ignore_pinned && (chart->flag & PCHART_HAS_PINS)) { unpacked++; continue; } box = boxarray + (i - unpacked); trans[0] = margin; trans[1] = margin; p_chart_uv_translate(chart, trans); box->w += margin * 2; box->h += margin * 2; } } BLI_box_pack_2d(boxarray, phandle->ncharts - unpacked, &tot_width, &tot_height); if (tot_height > tot_width) { scale = 1.0f / tot_height; } else { scale = 1.0f / tot_width; } for (i = 0; i < phandle->ncharts - unpacked; i++) { box = boxarray + i; trans[0] = box->x; trans[1] = box->y; chart = phandle->charts[box->index]; p_chart_uv_translate(chart, trans); p_chart_uv_scale(chart, scale); } MEM_freeN(boxarray); if (phandle->aspx != phandle->aspy) { param_scale(handle, phandle->aspx, phandle->aspy); } } void param_average(ParamHandle *handle, bool ignore_pinned) { PChart *chart; int i; float tot_uvarea = 0.0f, tot_facearea = 0.0f; float tot_fac, fac; float minv[2], maxv[2], trans[2]; PHandle *phandle = (PHandle *)handle; if (phandle->ncharts == 0) { return; } for (i = 0; i < phandle->ncharts; i++) { PFace *f; chart = phandle->charts[i]; if (ignore_pinned && (chart->flag & PCHART_HAS_PINS)) { continue; } chart->u.pack.area = 0.0f; /* 3d area */ chart->u.pack.rescale = 0.0f; /* UV area, abusing rescale for tmp storage, oh well :/ */ for (f = chart->faces; f; f = f->nextlink) { chart->u.pack.area += p_face_area(f); chart->u.pack.rescale += fabsf(p_face_uv_area_signed(f)); } tot_facearea += chart->u.pack.area; tot_uvarea += chart->u.pack.rescale; } if (tot_facearea == tot_uvarea || tot_facearea == 0.0f || tot_uvarea == 0.0f) { /* nothing to do */ return; } tot_fac = tot_facearea / tot_uvarea; for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; if (ignore_pinned && (chart->flag & PCHART_HAS_PINS)) { continue; } if (chart->u.pack.area != 0.0f && chart->u.pack.rescale != 0.0f) { fac = chart->u.pack.area / chart->u.pack.rescale; /* Get the island center */ p_chart_uv_bbox(chart, minv, maxv); trans[0] = (minv[0] + maxv[0]) / -2.0f; trans[1] = (minv[1] + maxv[1]) / -2.0f; /* Move center to 0,0 */ p_chart_uv_translate(chart, trans); p_chart_uv_scale(chart, sqrtf(fac / tot_fac)); /* Move to original center */ trans[0] = -trans[0]; trans[1] = -trans[1]; p_chart_uv_translate(chart, trans); } } } void param_scale(ParamHandle *handle, float x, float y) { PHandle *phandle = (PHandle *)handle; PChart *chart; int i; for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; p_chart_uv_scale_xy(chart, x, y); } } void param_flush(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; PChart *chart; int i; for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; if ((phandle->state == PHANDLE_STATE_LSCM) && !chart->u.lscm.context) { continue; } if (phandle->blend == 0.0f) { p_flush_uvs(phandle, chart); } else { p_flush_uvs_blend(phandle, chart, phandle->blend); } } } void param_flush_restore(ParamHandle *handle) { PHandle *phandle = (PHandle *)handle; PChart *chart; PFace *f; int i; for (i = 0; i < phandle->ncharts; i++) { chart = phandle->charts[i]; for (f = chart->faces; f; f = f->nextlink) { p_face_restore_uvs(f); } } }