Files
test/source/blender/geometry/intern/uv_parametrizer.cc
Lukasz Czyz 788bc5158e UV: add support for the SLIM unwrapping algorithm
Integrate an existing implementation of the SLIM unwrapping algorithm
into Blender. More info about SLIM here:
https://igl.ethz.ch/projects/slim/

This commit is based on the integration code written by Aurel Gruber
for Blender 2.7x (unfinished and never merged with the main branch).

This commit is based on Aurel's code, rebased and further improved.

Details:

- Unwrap has been moved into a sub-menu,
  slim unwrapping is exposed as: "Minimum Stretch".
- Live unwrap with SLIM refines the solutions using a timer.
- When using SLIM there are options to:
  - Set the number of iterations.
  - Weight the influence using vertex weights.
- SLIM can be disabled using the `WITH_UV_SLIM` build option.

Co-authored-by: Aurel Gruber <aurel.gruber@infix.ch>

Ref !114545
2024-09-21 16:48:53 +10:00

5394 lines
135 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eduv
*/
#include <functional>
#include <vector>
#include "GEO_uv_parametrizer.hh"
#include "BLI_array.hh"
#include "BLI_convexhull_2d.h"
#include "BLI_ghash.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "BLI_rand.h"
#ifdef WITH_UV_SLIM
# include "slim_matrix_transfer.h"
#endif
#include "GEO_uv_pack.hh"
#include "eigen_capi.h"
/* Utils */
namespace blender::geometry {
#define param_warning(message) \
{/* `printf("Warning %s:%d: %s\n", __FILE__, __LINE__, message);` */}(void)0
/* Prevent unused function warnings when slim is disabled. */
#ifdef WITH_UV_SLIM
# define UNUSED_FUNCTION_NO_SLIM(x) x
#else
# define UNUSED_FUNCTION_NO_SLIM UNUSED_FUNCTION
#endif
/* Special Purpose Hash */
using PHashKey = uintptr_t;
struct PHashLink {
PHashLink *next;
PHashKey key;
};
struct PHash {
PHashLink **list;
PHashLink **buckets;
int size, cursize, cursize_id;
};
/* Simplices */
struct PVert {
PVert *nextlink;
union PVertUnion {
PHashKey key; /* Construct. */
int id; /* ABF/LSCM matrix index. */
HeapNode *heaplink; /* Edge collapsing. */
} u;
struct PEdge *edge;
float co[3];
float uv[2];
uint flag;
float weight;
bool on_boundary_flag;
int slim_id;
};
struct PEdge {
PEdge *nextlink;
union PEdgeUnion {
PHashKey key; /* Construct. */
int id; /* ABF matrix index. */
HeapNode *heaplink; /* Fill holes. */
PEdge *nextcollapse; /* Simplification. */
} u;
PVert *vert;
PEdge *pair;
PEdge *next;
struct PFace *face;
float *orig_uv, old_uv[2];
uint flag;
};
struct PFace {
PFace *nextlink;
union PFaceUnion {
PHashKey key; /* Construct. */
int chart; /* Construct splitting. */
float area3d; /* Stretch. */
int id; /* ABF matrix index. */
} u;
PEdge *edge;
uint flag;
};
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,
PFACE_DONE = 8,
};
/* Chart */
struct PChart {
PVert *verts;
PEdge *edges;
PFace *faces;
int nverts, nedges, nfaces;
int nboundaries;
PVert *collapsed_verts;
PEdge *collapsed_edges;
PFace *collapsed_faces;
float area_uv;
float area_3d;
float origin[2];
LinearSolver *context;
float *abf_alpha;
PVert *pin1;
PVert *pin2;
PVert *single_pin;
bool has_pins;
bool skip_flush;
};
/* 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_safe_delete(PHash **pph)
{
if (!*pph) {
return;
}
MEM_SAFE_FREE((*pph)->buckets);
MEM_freeN(*pph);
*pph = nullptr;
}
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 == nullptr) {
/* 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 = nullptr, *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) = nullptr;
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;
}
if (PHASH_hash(ph, link->key) != hash) {
return nullptr;
}
}
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;
}
if (PHASH_hash(ph, link->key) != hash) {
return nullptr;
}
}
return link;
}
/* Angles close to 0 or 180 degrees cause rows filled with zeros in the linear_solver.
* The matrix will then be rank deficient and / or have poor conditioning.
* => Reduce the maximum angle to 179 degrees, and spread the remainder to the other angles.
*/
static void fix_large_angle(const float v_fix[3],
const float v1[3],
const float v2[3],
double *r_fix,
double *r_a1,
double *r_a2)
{
const double max_angle = DEG2RADF(179.0);
const double fix_amount = *r_fix - max_angle;
if (fix_amount < 0.0f) {
return; /* angle is reasonable, i.e. less than 179 degrees. */
}
/* The triangle is probably degenerate, or close to it.
* Without loss of generality, transform the triangle such that
* v_fix == { 0, s}, *r_fix = 180 degrees
* v1 == {-x1, 0}, *r_a1 = 0
* v2 == { x2, 0}, *r_a2 = 0
*
* With `s = 0`, `x1 > 0`, `x2 > 0`
*
* Now make `s` a small number and do some math:
* tan(*r_a1) = s / x1
* tan(*r_a2) = s / x2
*
* Remember that `tan(angle) ~= angle`
*
* Rearrange to obtain:
* *r_a1 = fix_amount * x2 / (x1 + x2)
* *r_a2 = fix_amount * x1 / (x1 + x2)
*/
const double dist_v1 = len_v3v3(v_fix, v1);
const double dist_v2 = len_v3v3(v_fix, v2);
const double sum = dist_v1 + dist_v2;
const double weight = (sum > 1e-20f) ? dist_v2 / sum : 0.5f;
/* Ensure sum of angles in triangle is unchanged. */
*r_fix -= fix_amount;
*r_a1 += fix_amount * weight;
*r_a2 += fix_amount * (1.0f - weight);
}
static void p_triangle_angles(const float v1[3],
const float v2[3],
const float v3[3],
double *r_a1,
double *r_a2,
double *r_a3)
{
*r_a1 = angle_v3v3v3(v3, v1, v2);
*r_a2 = angle_v3v3v3(v1, v2, v3);
*r_a3 = angle_v3v3v3(v2, v3, v1);
/* Fix for degenerate geometry e.g. v1 = sum(v2 + v3). See #100874 */
fix_large_angle(v1, v2, v3, r_a1, r_a2, r_a3);
fix_large_angle(v2, v3, v1, r_a2, r_a3, r_a1);
fix_large_angle(v3, v1, v2, r_a3, r_a1, r_a2);
/* Workaround for degenerate geometry, e.g. v1 == v2 == v3. */
*r_a1 = max_dd(*r_a1, 0.001f);
*r_a2 = max_dd(*r_a2, 0.001f);
*r_a3 = max_dd(*r_a3, 0.001f);
}
static void p_face_angles(PFace *f, double *r_a1, double *r_a2, double *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_vec_cos(const float v1[3], const float v2[3], const float v3[3])
{
return cos_v3v3v3(v1, v2, v3);
}
static void p_triangle_cos(const float v1[3],
const float v2[3],
const float v3[3],
float *r_cos1,
float *r_cos2,
float *r_cos3)
{
*r_cos1 = p_vec_cos(v3, v1, v2);
*r_cos2 = p_vec_cos(v1, v2, v3);
*r_cos3 = p_vec_cos(v2, v3, v1);
}
static void UNUSED_FUNCTION(p_face_cos)(PFace *f, float *r_cos1, float *r_cos2, float *r_cos3)
{
PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next;
PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert;
p_triangle_cos(v1->co, v2->co, v3->co, r_cos1, r_cos2, r_cos3);
}
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)
{
return len_v3v3(e->vert->co, e->next->vert->co);
}
static float p_edge_length_squared(PEdge *e)
{
return len_squared_v3v3(e->vert->co, e->next->vert->co);
}
static float p_edge_uv_length(PEdge *e)
{
return len_v2v2(e->vert->uv, e->next->vert->uv);
}
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 float p_chart_uv_area(PChart *chart)
{
float area = 0.0f;
for (PFace *f = chart->faces; f; f = f->nextlink) {
area += fabsf(p_face_uv_area_signed(f));
}
return area;
}
static void p_chart_uv_scale(PChart *chart, const float scale)
{
if (scale == 1.0f) {
return; /* Identity transform. */
}
for (PVert *v = chart->verts; v; v = v->nextlink) {
v->uv[0] *= scale;
v->uv[1] *= scale;
}
}
static void uv_parametrizer_scale_x(ParamHandle *phandle, const float scale_x)
{
if (scale_x == 1.0f) {
return; /* Identity transform. */
}
/* Scale every chart. */
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
for (PVert *v = chart->verts; v; v = v->nextlink) {
v->uv[0] *= scale_x; /* Only scale x axis. */
}
}
}
static void p_chart_uv_translate(PChart *chart, const float trans[2])
{
for (PVert *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, const float mat[2][2])
{
for (PVert *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 bool 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 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 true;
}
/* Topological Utilities */
static PEdge *p_wheel_edge_next(PEdge *e)
{
return e->next->next->pair;
}
static const PEdge *p_wheel_edge_next(const PEdge *e)
{
return e->next->next->pair;
}
static PEdge *p_wheel_edge_prev(PEdge *e)
{
return (e->pair) ? e->pair->next : nullptr;
}
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 bool p_vert_interior(PVert *v)
{
return v->edge->pair;
}
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) {
GEO_uv_parametrizer_test_equals_ptr("v->edge->vert", v, v->edge->vert);
}
for (e = chart->edges; e; e = e->nextlink) {
if (e->pair) {
GEO_uv_parametrizer_test_equals_ptr("e->pair->pair", e, e->pair->pair);
GEO_uv_parametrizer_test_equals_ptr("pair->vert", e->vert, e->pair->next->vert);
GEO_uv_parametrizer_test_equals_ptr("pair->next->vert", e->next->vert, e->pair->vert);
}
}
}
#endif
/* Loading / Flushing */
static void p_vert_load_pin_select_uvs(ParamHandle *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->aspect_y;
pinuv[1] += e->orig_uv[1];
npins++;
}
else {
v->uv[0] += e->orig_uv[0] * handle->aspect_y;
v->uv[1] += e->orig_uv[1];
}
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_chart_flush_collapsed_uvs(PChart *chart);
static void p_flush_uvs(ParamHandle *handle, PChart *chart)
{
const float blend = handle->blend;
const float invblend = 1.0f - blend;
const float invblend_x = invblend / handle->aspect_y;
for (PEdge *e = chart->edges; e; e = e->nextlink) {
if (e->orig_uv) {
e->orig_uv[0] = blend * e->old_uv[0] + invblend_x * e->vert->uv[0];
e->orig_uv[1] = blend * e->old_uv[1] + invblend * e->vert->uv[1];
}
}
if (chart->collapsed_edges) {
p_chart_flush_collapsed_uvs(chart);
for (PEdge *e = chart->collapsed_edges; e; e = e->nextlink) {
if (e->orig_uv) {
e->orig_uv[0] = blend * e->old_uv[0] + invblend_x * e->vert->uv[0];
e->orig_uv[1] = blend * e->old_uv[1] + invblend * e->vert->uv[1];
}
}
}
}
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(
ParamHandle *handle, PHashKey key, const float co[3], const float weight, PEdge *e)
{
PVert *v = (PVert *)BLI_memarena_alloc(handle->arena, sizeof(*v));
copy_v3_v3(v->co, co);
v->weight = weight;
/* 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(
ParamHandle *handle, PHashKey key, const float co[3], const float weight, PEdge *e)
{
PVert *v = (PVert *)phash_lookup(handle->hash_verts, key);
if (v) {
return v;
}
return p_vert_add(handle, key, co, weight, e);
}
static PVert *p_vert_copy(ParamHandle *handle, PVert *v)
{
PVert *nv = (PVert *)BLI_memarena_alloc(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(ParamHandle *handle, const 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;
}
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 nullptr;
}
static int p_face_exists(ParamHandle *handle, const ParamKey *pvkeys, int i1, int i2, int i3)
{
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 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 true;
}
}
e = (PEdge *)phash_next(handle->hash_edges, key, (PHashLink *)e);
}
return false;
}
static bool p_edge_implicit_seam(PEdge *e, PEdge *ep)
{
const 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 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 true;
}
return false;
}
static bool p_edge_has_pair(ParamHandle *handle, PEdge *e, bool topology_from_uvs, PEdge **r_pair)
{
PHashKey key;
PEdge *pe;
const PVert *v1, *v2;
PHashKey key1 = e->vert->u.key;
PHashKey key2 = e->next->vert->u.key;
if (e->flag & PEDGE_SEAM) {
return false;
}
key = PHASH_edge(key1, key2);
pe = (PEdge *)phash_lookup(handle->hash_edges, key);
*r_pair = nullptr;
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 ||
(topology_from_uvs && p_edge_implicit_seam(e, pe)))
{
*r_pair = nullptr;
return 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 = nullptr;
return false;
}
}
return (*r_pair != nullptr);
}
static bool p_edge_connect_pair(ParamHandle *handle,
PEdge *e,
bool topology_from_uvs,
PEdge ***stack)
{
PEdge *pair = nullptr;
if (!e->pair && p_edge_has_pair(handle, e, topology_from_uvs, &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 != nullptr);
}
static int p_connect_pairs(ParamHandle *handle, bool topology_from_uvs)
{
PEdge **stackbase = (PEdge **)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 pair-less 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, topology_from_uvs, &stack)) {
e->vert->edge = e;
}
if (!p_edge_connect_pair(handle, e1, topology_from_uvs, &stack)) {
e1->vert->edge = e1;
}
if (!p_edge_connect_pair(handle, e2, topology_from_uvs, &stack)) {
e2->vert->edge = e2;
}
}
ncharts++;
}
MEM_freeN(stackbase);
return ncharts;
}
static void p_split_vert(ParamHandle *handle, PChart *chart, PEdge *e)
{
PEdge *we, *lastwe = nullptr;
PVert *v = e->vert;
bool copy = true;
if (e->flag & PEDGE_PIN) {
chart->has_pins = true;
}
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 = false;
v->nextlink = chart->verts;
chart->verts = v;
chart->nverts++;
}
}
if (copy) {
/* not found, copying */
v->flag |= PVERT_SPLIT;
v = p_vert_copy(handle, 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(ParamHandle *handle, PChart *chart, int ncharts)
{
PChart **charts = (PChart **)MEM_callocN(sizeof(*charts) * ncharts, "PCharts");
for (int i = 0; i < ncharts; i++) {
charts[i] = (PChart *)MEM_callocN(sizeof(*chart), "PChart");
}
PFace *f = chart->faces;
while (f) {
PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next;
PFace *nextf = f->nextlink;
PChart *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(handle, nchart, e1);
p_split_vert(handle, nchart, e2);
p_split_vert(handle, nchart, e3);
f = nextf;
}
return charts;
}
static PFace *p_face_add(ParamHandle *handle)
{
PFace *f;
/* allocate */
f = (PFace *)BLI_memarena_alloc(handle->arena, sizeof(*f));
f->flag = 0;
PEdge *e1 = (PEdge *)BLI_memarena_calloc(handle->arena, sizeof(*e1));
PEdge *e2 = (PEdge *)BLI_memarena_calloc(handle->arena, sizeof(*e2));
PEdge *e3 = (PEdge *)BLI_memarena_calloc(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;
return f;
}
static PFace *p_face_add_construct(ParamHandle *handle,
ParamKey key,
const ParamKey *vkeys,
const float **co,
float **uv,
const float *weight,
int i1,
int i2,
int i3,
const bool *pin,
const bool *select)
{
PFace *f = p_face_add(handle);
PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next;
float weight1, weight2, weight3;
if (weight) {
weight1 = weight[i1];
weight2 = weight[i2];
weight3 = weight[i3];
}
else {
weight1 = 1.0f;
weight2 = 1.0f;
weight3 = 1.0f;
}
e1->vert = p_vert_lookup(handle, vkeys[i1], co[i1], weight1, e1);
e2->vert = p_vert_lookup(handle, vkeys[i2], co[i2], weight2, e2);
e3->vert = p_vert_lookup(handle, vkeys[i3], co[i3], weight3, 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;
}
}
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(ParamHandle *handle, PChart *chart, PVert *v1, PVert *v2, PVert *v3)
{
PFace *f = p_face_add(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 = nullptr;
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;
}
/* Construction: boundary filling */
static void p_chart_boundaries(PChart *chart, PEdge **r_outer)
{
PEdge *e, *be;
float len, maxlen = -1.0;
chart->nboundaries = 0;
if (r_outer) {
*r_outer = nullptr;
}
for (e = chart->edges; e; e = e->nextlink) {
if (e->pair || (e->flag & PEDGE_DONE)) {
continue;
}
chart->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;
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 -= angle_v3v3v3(v1->co, v->co, v2->co);
we = we->next->next->pair;
} while (we && (we != v->edge));
return angle;
}
static void p_chart_fill_boundary(ParamHandle *handle, PChart *chart, PEdge *be, int nedges)
{
PEdge *e, *e1, *e2;
PFace *f;
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 = nullptr;
e->flag |= PEDGE_FILLED;
e1->flag |= PEDGE_FILLED;
f = p_face_add_fill(handle, 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, nullptr);
}
static void p_chart_fill_boundaries(ParamHandle *handle, PChart *chart, const 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(handle, 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 face */
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
/* Simplify/Complexity
*
* This is currently used for eliminating degenerate vertex coordinates.
* In the future this can be used for efficient unwrapping of high resolution
* charts at lower resolution. */
#if 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 bool p_vert_flipped_wheel_triangle(PVert *v)
{
PEdge *e = v->edge;
do {
if (p_face_uv_area_signed(e->face) < 0.0f) {
return true;
}
e = p_wheel_edge_next(e);
} while (e && (e != v->edge));
return false;
}
static bool 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 false;
}
return true;
}
static void p_vert_harmonic_insert(PVert *v)
{
PEdge *e;
if (!p_vert_map_harmonic_weights(v)) {
/* do face 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 == nullptr) {
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 == nullptr) {
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);
}
#endif
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;
}
}
#if 0
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));
}
#endif
static bool 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 false;
}
else if (pair && !pair->next->pair && !pair->next->next->pair) {
return 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 false;
}
return true;
}
#if 0
static bool 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 bool 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 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 = DEG2RADF(15.0);
maxangle = M_PI - minangle;
for (i = 0; i < 3; i++) {
if ((b[i] < a[i]) && (b[i] < minangle)) {
return false;
}
else if ((b[i] > a[i]) && (b[i] > maxangle)) {
return 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) > DEG2RADF(30.0)) {
return 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 false;
}
/* Don't collapse significant boundary changes. */
angle = p_vec_angle(v1->co, oldv->co, v2->co);
if (angle < DEG2RADF(160.0)) {
return false;
}
}
return true;
}
static bool p_collapse_allowed(PEdge *edge, PEdge *pair)
{
PVert *oldv, *keepv;
p_collapsing_verts(edge, pair, &oldv, &keepv);
if (oldv->flag & PVERT_PIN) {
return 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 : nullptr;
oldf2 = (pair) ? pair->face : nullptr;
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 (!ELEM(e->face, oldf1, 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) == nullptr) {
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;
}
#endif
static void p_collapse_cost_vertex(
PVert *vert,
float *r_mincost,
PEdge **r_mine,
const std::function<float(PEdge *, PEdge *)> &collapse_cost_fn,
const std::function<float(PEdge *, PEdge *)> &collapse_allowed_fn)
{
PEdge *e, *enext, *pair;
*r_mine = nullptr;
*r_mincost = 0.0f;
e = vert->edge;
do {
if (collapse_allowed_fn(e, e->pair)) {
float cost = collapse_cost_fn(e, e->pair);
if ((*r_mine == nullptr) || (cost < *r_mincost)) {
*r_mincost = cost;
*r_mine = e;
}
}
enext = p_wheel_edge_next(e);
if (enext == nullptr) {
/* The other boundary edge, where we only have the pair half-edge. */
pair = e->next->next;
if (collapse_allowed_fn(nullptr, pair)) {
float cost = collapse_cost_fn(nullptr, pair);
if ((*r_mine == nullptr) || (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 = nullptr, *verts = chart->verts;
PEdge *e, *nexte = nullptr, *edges = chart->edges, *laste = nullptr;
PFace *f, *nextf = nullptr, *faces = chart->faces;
chart->verts = chart->collapsed_verts = nullptr;
chart->edges = chart->collapsed_edges = nullptr;
chart->faces = chart->collapsed_faces = nullptr;
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_simplify_compute(PChart *chart,
std::function<float(PEdge *, PEdge *)> collapse_cost_fn,
std::function<float(PEdge *, PEdge *)> collapse_allowed_fn)
{
/* For debugging. */
static const int MAX_SIMPLIFY = INT_MAX;
/* 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;
PEdge *collapsededges = nullptr, *e;
int ncollapsed = 0;
Vector<PVert *> wheelverts;
wheelverts.reserve(16);
/* insert all potential collapses into heap */
for (v = chart->verts; v; v = v->nextlink) {
float cost;
PEdge *e = nullptr;
p_collapse_cost_vertex(v, &cost, &e, collapse_cost_fn, collapse_allowed_fn);
if (e) {
v->u.heaplink = BLI_heap_insert(heap, cost, e);
}
else {
v->u.heaplink = nullptr;
}
}
for (e = chart->edges; e; e = e->nextlink) {
e->u.nextcollapse = nullptr;
}
/* pop edge collapse out of heap one by one */
while (!BLI_heap_is_empty(heap)) {
if (ncollapsed == MAX_SIMPLIFY) {
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 = nullptr;
std::swap(edge, pair);
}
else {
edge->flag |= PEDGE_COLLAPSE_EDGE;
edge->vert->u.heaplink = nullptr;
}
p_collapsing_verts(edge, pair, &oldv, &keepv);
/* gather all wheel verts and remember them before collapse */
wheelverts.clear();
wheele = oldv->edge;
do {
wheelverts.append(wheele->next->vert);
nexte = p_wheel_edge_next(wheele);
if (nexte == nullptr) {
wheelverts.append(wheele->next->next->vert);
}
wheele = nexte;
} while (wheele && (wheele != oldv->edge));
/* collapse */
p_collapse_edge(edge, pair);
for (PVert *v : wheelverts) {
float cost;
PEdge *collapse = nullptr;
if (v->u.heaplink) {
BLI_heap_remove(heap, v->u.heaplink);
v->u.heaplink = nullptr;
}
p_collapse_cost_vertex(v, &cost, &collapse, collapse_cost_fn, collapse_allowed_fn);
if (collapse) {
v->u.heaplink = BLI_heap_insert(heap, cost, collapse);
}
}
ncollapsed++;
}
BLI_heap_free(heap, nullptr);
p_chart_post_collapse_flush(chart, collapsededges);
}
#if 0
static void p_chart_post_split_flush(PChart *chart)
{
/* Move from `collapsed_*`. */
PVert *v, *nextv = nullptr;
PEdge *e, *nexte = nullptr;
PFace *f, *nextf = nullptr;
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 = nullptr;
chart->collapsed_edges = nullptr;
chart->collapsed_faces = nullptr;
}
static void p_chart_complexify(PChart *chart)
{
/* For debugging. */
static const int MAX_COMPLEXIFY = INT_MAX;
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) {
std::swap(edge, pair);
}
p_split_vertex(edge, pair);
p_collapsing_verts(edge, pair, &newv, &keepv);
if (x >= MAX_COMPLEXIFY) {
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);
}
static void p_chart_simplify(PChart *chart)
{
/* Not implemented, needs proper reordering in split_flush. */
}
#endif
/* ABF */
#define ABF_MAX_ITER 20
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;
};
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 = static_cast<float(*)[3]>(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;
}
}
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)
{
float *sine = sys->sine;
float *cosine = sys->cosine;
const float *alpha = sys->alpha;
for (int i = 0; i < sys->nangles; i++) {
const double angle = alpha[i];
sine[i] = sin(angle);
cosine[i] = cos(angle);
}
}
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 void p_abf_adjust_alpha(PAbfSystem *sys,
const int id,
const float dlambda1,
const float pre)
{
float alpha = sys->alpha[id];
const float dalpha = (sys->bAlpha[id] - dlambda1);
alpha += dalpha / sys->weight[id] - pre;
sys->alpha[id] = clamp_f(alpha, 0.0f, float(M_PI));
}
static bool p_abf_matrix_invert(PAbfSystem *sys, PChart *chart)
{
int ninterior = sys->ninterior;
int nvar = 2 * ninterior;
LinearSolver *context = EIG_linear_solver_new(0, nvar, 1);
for (int i = 0; i < nvar; i++) {
EIG_linear_solver_right_hand_side_add(context, 0, i, sys->bInterior[i]);
}
for (PFace *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 (int i = 0; i < 3; i++) {
int r = vid[i];
if (r == -1) {
continue;
}
for (int 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]);
}
}
}
}
bool success = EIG_linear_solver_solve(context);
if (success) {
for (PFace *f = chart->faces; f; f = f->nextlink) {
float pre[3];
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.0f;
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;
}
float 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;
p_abf_adjust_alpha(sys, e1->u.id, dlambda1, pre[0]);
p_abf_adjust_alpha(sys, e2->u.id, dlambda1, pre[1]);
p_abf_adjust_alpha(sys, e3->u.id, dlambda1, pre[2]);
}
for (int 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 bool 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) {
double a1, a2, a3;
e1 = f->edge;
e2 = e1->next;
e3 = e2->next;
p_face_angles(f, &a1, &a2, &a3);
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 false;
}
p_abf_compute_sines(&sys);
}
if (i == ABF_MAX_ITER) {
param_warning("ABF maximum iterations reached");
p_abf_free_system(&sys);
return false;
}
}
chart->abf_alpha = (float *)MEM_dupallocN(sys.alpha);
p_abf_free_system(&sys);
return 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 bool p_chart_symmetry_pins(PChart *chart, PEdge *outer, PVert **pin1, PVert **pin2)
{
PEdge *be, *lastbe = nullptr, *maxe1 = nullptr, *maxe2 = nullptr, *be1, *be2;
PEdge *cure = nullptr, *firste1 = nullptr, *firste2 = nullptr, *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 = nullptr;
}
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 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] = nullptr;
maxvert[0] = maxvert[1] = maxvert[2] = nullptr;
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_begin(PChart *chart, bool live, bool abf)
{
BLI_assert(chart->context == nullptr);
bool select = false;
bool deselect = false;
int npins = 0;
/* Give vertices matrix indices, count pins and check selections. */
for (PVert *v = chart->verts; v; v = v->nextlink) {
if (v->flag & PVERT_PIN) {
npins++;
if (v->flag & PVERT_SELECT) {
select = true;
}
}
if (!(v->flag & PVERT_SELECT)) {
deselect = true;
}
}
if (live && (!select || !deselect)) {
chart->skip_flush = true;
return;
}
#if 0
p_chart_simplify_compute(chart, p_collapse_cost, p_collapse_allowed);
p_chart_topological_sanity_check(chart);
#endif
if (npins == 1) {
chart->area_uv = p_chart_uv_area(chart);
for (PVert *v = chart->verts; v; v = v->nextlink) {
if (v->flag & PVERT_PIN) {
chart->single_pin = v;
break;
}
}
}
if (abf) {
if (!p_chart_abf_solve(chart)) {
param_warning("ABF solving failed: falling back to LSCM.\n");
}
}
/* ABF uses these indices for it's internal references.
* Set the indices afterwards. */
int id = 0;
for (PVert *v = chart->verts; v; v = v->nextlink) {
v->u.id = id++;
}
if (npins <= 1) {
/* No pins, let's find some ourself. */
PEdge *outer;
p_chart_boundaries(chart, &outer);
PVert *pin1, *pin2;
/* Outer can be null with non-finite coordinates. */
if (!(outer && p_chart_symmetry_pins(chart, outer, &pin1, &pin2))) {
p_chart_extrema_verts(chart, &pin1, &pin2);
}
chart->pin1 = pin1;
chart->pin2 = pin2;
}
chart->context = EIG_linear_least_squares_solver_new(2 * chart->nfaces, 2 * chart->nverts, 1);
}
static bool p_chart_lscm_solve(ParamHandle *handle, PChart *chart)
{
LinearSolver *context = chart->context;
for (PVert *v = chart->verts; v; v = v->nextlink) {
if (v->flag & PVERT_PIN) {
p_vert_load_pin_select_uvs(handle, v); /* Reload for Live Unwrap. */
}
}
if (chart->single_pin) {
/* If only one pin, save location as origin. */
copy_v2_v2(chart->origin, chart->single_pin->uv);
}
if (chart->pin1) {
PVert *pin1 = chart->pin1;
PVert *pin2 = chart->pin2;
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 (PVert *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. */
float area_pinned_up = 0.0f;
float area_pinned_down = 0.0f;
for (PFace *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)) {
const float area = p_face_uv_area_signed(f);
if (area > 0.0f) {
area_pinned_up += area;
}
else {
area_pinned_down -= area;
}
}
}
const bool flip_faces = (area_pinned_down > area_pinned_up);
/* Construct matrix. */
const float *alpha = chart->abf_alpha;
int row = 0;
for (PFace *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;
double a1, a2, a3;
if (alpha) {
/* Use abf angles if present. */
a1 = *(alpha++);
a2 = *(alpha++);
a3 = *(alpha++);
}
else {
p_face_angles(f, &a1, &a2, &a3);
}
if (flip_faces) {
std::swap(a2, a3);
std::swap(e2, e3);
std::swap(v2, v3);
}
double sina1 = sin(a1);
double sina2 = sin(a2);
double sina3 = sin(a3);
const double sinmax = max_ddd(sina1, sina2, sina3);
/* Shift vertices to find most stable order. */
if (sina3 != sinmax) {
SHIFT3(PVert *, v1, v2, v3);
SHIFT3(double, a1, a2, a3);
SHIFT3(double, sina1, sina2, sina3);
if (sina2 == sinmax) {
SHIFT3(PVert *, v1, v2, v3);
SHIFT3(double, a1, a2, a3);
SHIFT3(double, sina1, sina2, sina3);
}
}
/* Angle based lscm formulation. */
const double ratio = (sina3 == 0.0f) ? 1.0f : sina2 / sina3;
const double cosine = cos(a1) * ratio;
const double 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)) {
for (PVert *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);
}
return true;
}
for (PVert *v = chart->verts; v; v = v->nextlink) {
v->uv[0] = 0.0f;
v->uv[1] = 0.0f;
}
return false;
}
static void p_chart_lscm_transform_single_pin(PChart *chart)
{
PVert *pin = chart->single_pin;
/* If only one pin, keep UV area the same. */
const float new_area = p_chart_uv_area(chart);
if (new_area > 0.0f) {
const float scale = chart->area_uv / new_area;
if (scale > 0.0f) {
p_chart_uv_scale(chart, sqrtf(scale));
}
}
/* Translate to keep the pinned UV in place. */
float offset[2];
sub_v2_v2v2(offset, chart->origin, pin->uv);
p_chart_uv_translate(chart, offset);
}
static void p_chart_lscm_end(PChart *chart)
{
EIG_linear_solver_delete(chart->context);
chart->context = nullptr;
MEM_SAFE_FREE(chart->abf_alpha);
chart->pin1 = nullptr;
chart->pin2 = nullptr;
chart->single_pin = nullptr;
}
/* 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 == nullptr) {
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) {
/* When a face is flipped, provide a large penalty.
* Add on a slight gradient to unflip the face, see also: #99781. */
return 1e8f * (1.0f + p_edge_uv_length(e1) + p_edge_uv_length(e2) + p_edge_uv_length(e3));
}
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;
}
if (v1->uv[0] == v2->uv[0]) {
if (v1->uv[1] < v2->uv[1]) {
return -1;
}
if (v1->uv[1] == v2->uv[1]) {
return 0;
}
return 1;
}
return 1;
}
static bool 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, &be);
if (!be) {
return 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 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 = (float *)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) - angle_v2v2v2(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] = angle_v2v2v2(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] = angle_v2v2v2(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] = angle_v2v2v2(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] = angle_v2v2v2(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)) { /* 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);
}
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;
}
}
static void p_chart_rotate_fit_aabb(PChart *chart)
{
float(*points)[2] = static_cast<float(*)[2]>(
MEM_mallocN(sizeof(*points) * chart->nverts, __func__));
p_chart_uv_to_array(chart, points);
float 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);
}
}
ParamHandle::ParamHandle()
{
arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 16), "param construct arena");
polyfill_arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "param polyfill arena");
polyfill_heap = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
construction_chart = (PChart *)MEM_callocN(sizeof(PChart), "PChart");
hash_verts = phash_new((PHashLink **)&construction_chart->verts, 1);
hash_edges = phash_new((PHashLink **)&construction_chart->edges, 1);
hash_faces = phash_new((PHashLink **)&construction_chart->faces, 1);
}
ParamHandle::~ParamHandle()
{
BLI_memarena_free(arena);
arena = nullptr;
BLI_memarena_free(polyfill_arena);
polyfill_arena = nullptr;
BLI_heap_free(polyfill_heap, nullptr);
polyfill_heap = nullptr;
MEM_SAFE_FREE(construction_chart);
phash_safe_delete(&hash_verts);
phash_safe_delete(&hash_edges);
phash_safe_delete(&hash_faces);
if (pin_hash) {
BLI_ghash_free(pin_hash, nullptr, nullptr);
pin_hash = nullptr;
}
for (int i = 0; i < ncharts; i++) {
MEM_SAFE_FREE(charts[i]);
}
MEM_SAFE_FREE(charts);
if (rng) {
BLI_rng_free(rng);
rng = nullptr;
}
}
void uv_parametrizer_aspect_ratio(ParamHandle *phandle, const float aspect_y)
{
BLI_assert(aspect_y > 0.0f);
phandle->aspect_y = aspect_y;
}
struct GeoUVPinIndex {
GeoUVPinIndex *next;
float uv[2];
ParamKey reindex;
};
ParamKey uv_find_pin_index(ParamHandle *handle, const int bmvertindex, const float uv[2])
{
if (!handle->pin_hash) {
return bmvertindex; /* No verts pinned. */
}
const GeoUVPinIndex *pinuvlist = (const GeoUVPinIndex *)BLI_ghash_lookup(
handle->pin_hash, POINTER_FROM_INT(bmvertindex));
if (!pinuvlist) {
return bmvertindex; /* Vert not pinned. */
}
/* At least one of the UVs associated with bmvertindex is pinned. Find the best one. */
float bestdistsquared = len_squared_v2v2(pinuvlist->uv, uv);
ParamKey bestkey = pinuvlist->reindex;
pinuvlist = pinuvlist->next;
while (pinuvlist) {
const float distsquared = len_squared_v2v2(pinuvlist->uv, uv);
if (bestdistsquared > distsquared) {
bestdistsquared = distsquared;
bestkey = pinuvlist->reindex;
}
pinuvlist = pinuvlist->next;
}
return bestkey;
}
static GeoUVPinIndex *new_geo_uv_pinindex(ParamHandle *handle, const float uv[2])
{
GeoUVPinIndex *pinuv = (GeoUVPinIndex *)BLI_memarena_alloc(handle->arena, sizeof(*pinuv));
pinuv->next = nullptr;
copy_v2_v2(pinuv->uv, uv);
pinuv->reindex = PARAM_KEY_MAX - (handle->unique_pin_count++);
return pinuv;
}
void uv_prepare_pin_index(ParamHandle *handle, const int bmvertindex, const float uv[2])
{
if (!handle->pin_hash) {
handle->pin_hash = BLI_ghash_int_new("uv pin reindex");
}
GeoUVPinIndex *pinuvlist = (GeoUVPinIndex *)BLI_ghash_lookup(handle->pin_hash,
POINTER_FROM_INT(bmvertindex));
if (!pinuvlist) {
BLI_ghash_insert(
handle->pin_hash, POINTER_FROM_INT(bmvertindex), new_geo_uv_pinindex(handle, uv));
return;
}
while (true) {
if (equals_v2v2(pinuvlist->uv, uv)) {
return;
}
if (!pinuvlist->next) {
pinuvlist->next = new_geo_uv_pinindex(handle, uv);
return;
}
pinuvlist = pinuvlist->next;
}
}
static void p_add_ngon(ParamHandle *handle,
const ParamKey key,
const int nverts,
const ParamKey *vkeys,
const float **co,
float **uv, /* Output will eventually be written to `uv`. */
const float *weight,
const bool *pin,
const bool *select)
{
/* Allocate memory for polyfill. */
MemArena *arena = handle->polyfill_arena;
Heap *heap = handle->polyfill_heap;
uint nfilltri = nverts - 2;
uint(*tris)[3] = static_cast<uint(*)[3]>(
BLI_memarena_alloc(arena, sizeof(*tris) * size_t(nfilltri)));
float(*projverts)[2] = static_cast<float(*)[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 (uint j = 0; j < nfilltri; j++) {
uint *tri = tris[j];
uint v0 = tri[0];
uint v1 = tri[1];
uint v2 = tri[2];
const ParamKey tri_vkeys[3] = {vkeys[v0], vkeys[v1], vkeys[v2]};
const float *tri_co[3] = {co[v0], co[v1], co[v2]};
float *tri_uv[3] = {uv[v0], uv[v1], uv[v2]};
float *tri_weight = nullptr;
float tri_weight_tmp[3];
if (weight) {
tri_weight_tmp[0] = weight[v0];
tri_weight_tmp[1] = weight[v1];
tri_weight_tmp[2] = weight[v2];
tri_weight = tri_weight_tmp;
};
const bool tri_pin[3] = {pin[v0], pin[v1], pin[v2]};
const bool tri_select[3] = {select[v0], select[v1], select[v2]};
uv_parametrizer_face_add(
handle, key, 3, tri_vkeys, tri_co, tri_uv, tri_weight, tri_pin, tri_select);
}
BLI_memarena_clear(arena);
}
void uv_parametrizer_face_add(ParamHandle *phandle,
const ParamKey key,
const int nverts,
const ParamKey *vkeys,
const float **co,
float **uv,
float *weight,
const bool *pin,
const bool *select)
{
BLI_assert(nverts >= 3);
BLI_assert(phandle->state == PHANDLE_STATE_ALLOCATED);
if (nverts > 3) {
/* Protect against (manifold) geometry which has a non-manifold triangulation.
* See #102543. */
Vector<int, 32> permute;
permute.reserve(nverts);
for (int i = 0; i < nverts; i++) {
permute.append_unchecked(i);
}
int i = nverts - 1;
while (i >= 0) {
/* Just check the "ears" of the n-gon.
* For quads, this is sufficient.
* For pentagons and higher, we might miss internal duplicate triangles, but note
* that such cases are rare if the source geometry is manifold and non-intersecting. */
const int pm = int(permute.size());
BLI_assert(pm > 3);
int i0 = permute[i];
int i1 = permute[(i + 1) % pm];
int i2 = permute[(i + 2) % pm];
if (!p_face_exists(phandle, vkeys, i0, i1, i2)) {
i--; /* All good. */
continue;
}
/* An existing triangle has already been inserted.
* As a heuristic, attempt to add the *previous* triangle.
* NOTE: Should probably call `uv_parametrizer_face_add`
* instead of `p_face_add_construct`. */
int iprev = permute[(i + pm - 1) % pm];
p_face_add_construct(phandle, key, vkeys, co, uv, weight, iprev, i0, i1, pin, select);
permute.remove(i);
if (permute.size() == 3) {
break;
}
}
if (permute.size() != nverts) {
const int pm = int(permute.size());
/* Add the remaining `pm-gon` data. */
Array<ParamKey> vkeys_sub(pm);
Array<const float *> co_sub(pm);
Array<float *> uv_sub(pm);
Array<float> weight_sub(pm);
Array<bool> pin_sub(pm);
Array<bool> select_sub(pm);
for (int i = 0; i < pm; i++) {
int j = permute[i];
vkeys_sub[i] = vkeys[j];
co_sub[i] = co[j];
uv_sub[i] = uv[j];
weight_sub[i] = weight[j];
pin_sub[i] = pin && pin[j];
select_sub[i] = select && select[j];
}
p_add_ngon(phandle,
key,
pm,
&vkeys_sub.first(),
&co_sub.first(),
&uv_sub.first(),
&weight_sub.first(),
&pin_sub.first(),
&select_sub.first());
return; /* Nothing more to do. */
}
/* No "ears" have previously been inserted. Continue as normal. */
}
if (nverts > 3) {
/* ngon */
p_add_ngon(phandle, key, nverts, vkeys, co, uv, weight, pin, select);
}
else if (!p_face_exists(phandle, vkeys, 0, 1, 2)) {
/* triangle */
p_face_add_construct(phandle, key, vkeys, co, uv, weight, 0, 1, 2, pin, select);
}
}
void uv_parametrizer_edge_set_seam(ParamHandle *phandle, ParamKey *vkeys)
{
BLI_assert(phandle->state == PHANDLE_STATE_ALLOCATED);
PEdge *e = p_edge_lookup(phandle, vkeys);
if (e) {
e->flag |= PEDGE_SEAM;
}
}
void uv_parametrizer_construct_end(ParamHandle *phandle,
bool fill_holes,
bool topology_from_uvs,
int *r_count_failed)
{
int i, j;
BLI_assert(phandle->state == PHANDLE_STATE_ALLOCATED);
phandle->ncharts = p_connect_pairs(phandle, topology_from_uvs);
phandle->charts = p_split_charts(phandle, phandle->construction_chart, phandle->ncharts);
MEM_freeN(phandle->construction_chart);
phandle->construction_chart = nullptr;
phash_safe_delete(&phandle->hash_verts);
phash_safe_delete(&phandle->hash_edges);
phash_safe_delete(&phandle->hash_faces);
for (i = j = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
PEdge *outer;
p_chart_boundaries(chart, &outer);
if (!topology_from_uvs && chart->nboundaries == 0) {
MEM_freeN(chart);
if (r_count_failed) {
*r_count_failed += 1;
}
continue;
}
phandle->charts[j++] = chart;
if (fill_holes && chart->nboundaries > 1) {
p_chart_fill_boundaries(phandle, chart, outer);
}
for (PVert *v = chart->verts; v; v = v->nextlink) {
p_vert_load_pin_select_uvs(phandle, v);
}
}
phandle->ncharts = j;
phandle->state = PHANDLE_STATE_CONSTRUCTED;
}
void uv_parametrizer_lscm_begin(ParamHandle *phandle, bool live, bool abf)
{
BLI_assert(phandle->state == PHANDLE_STATE_CONSTRUCTED);
phandle->state = PHANDLE_STATE_LSCM;
for (int i = 0; i < phandle->ncharts; i++) {
for (PFace *f = phandle->charts[i]->faces; f; f = f->nextlink) {
p_face_backup_uvs(f);
}
p_chart_lscm_begin(phandle->charts[i], live, abf);
}
}
void uv_parametrizer_lscm_solve(ParamHandle *phandle, int *count_changed, int *count_failed)
{
BLI_assert(phandle->state == PHANDLE_STATE_LSCM);
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
if (!chart->context) {
continue;
}
const bool result = p_chart_lscm_solve(phandle, chart);
if (result && !chart->has_pins) {
/* Every call to LSCM will eventually call uv_pack, so rotating here might be redundant. */
p_chart_rotate_minimum_area(chart);
}
else if (result && chart->single_pin) {
p_chart_rotate_fit_aabb(chart);
p_chart_lscm_transform_single_pin(chart);
}
if (!result || !chart->has_pins) {
p_chart_lscm_end(chart);
}
if (result) {
if (count_changed != nullptr) {
*count_changed += 1;
}
}
else {
if (count_failed != nullptr) {
*count_failed += 1;
}
}
}
}
void uv_parametrizer_lscm_end(ParamHandle *phandle)
{
BLI_assert(phandle->state == PHANDLE_STATE_LSCM);
for (int 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 uv_parametrizer_stretch_begin(ParamHandle *phandle)
{
BLI_assert(phandle->state == PHANDLE_STATE_CONSTRUCTED);
phandle->state = PHANDLE_STATE_STRETCH;
phandle->rng = BLI_rng_new(31415926);
phandle->blend = 0.0f;
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
for (PVert *v = chart->verts; v; v = v->nextlink) {
v->flag &= ~PVERT_PIN; /* don't use user-defined pins */
}
p_stretch_pin_boundary(chart);
for (PFace *f = chart->faces; f; f = f->nextlink) {
p_face_backup_uvs(f);
f->u.area3d = p_face_area(f);
}
}
}
void uv_parametrizer_stretch_blend(ParamHandle *phandle, float blend)
{
BLI_assert(phandle->state == PHANDLE_STATE_STRETCH);
phandle->blend = blend;
}
void uv_parametrizer_stretch_iter(ParamHandle *phandle)
{
BLI_assert(phandle->state == PHANDLE_STATE_STRETCH);
for (int i = 0; i < phandle->ncharts; i++) {
p_chart_stretch_minimize(phandle->charts[i], phandle->rng);
}
}
void uv_parametrizer_stretch_end(ParamHandle *phandle)
{
BLI_assert(phandle->state == PHANDLE_STATE_STRETCH);
phandle->state = PHANDLE_STATE_CONSTRUCTED;
}
void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, bool ignore_pinned)
{
if (handle->ncharts == 0) {
return;
}
uv_parametrizer_scale_x(handle, 1.0f / handle->aspect_y);
Vector<PackIsland *> pack_island_vector;
UVPackIsland_Params params;
params.rotate_method = do_rotate ? ED_UVPACK_ROTATION_ANY : ED_UVPACK_ROTATION_NONE;
params.margin = margin;
params.margin_method = ED_UVPACK_MARGIN_SCALED;
for (int i = 0; i < handle->ncharts; i++) {
PChart *chart = handle->charts[i];
if (ignore_pinned && chart->has_pins) {
continue;
}
geometry::PackIsland *pack_island = new geometry::PackIsland();
pack_island->caller_index = i;
pack_island->aspect_y = handle->aspect_y;
pack_island->pinned = chart->has_pins;
for (PFace *f = chart->faces; f; f = f->nextlink) {
PVert *v0 = f->edge->vert;
PVert *v1 = f->edge->next->vert;
PVert *v2 = f->edge->next->next->vert;
pack_island->add_triangle(v0->uv, v1->uv, v2->uv);
}
pack_island_vector.append(pack_island);
}
const float scale = pack_islands(pack_island_vector, params);
for (const int64_t i : pack_island_vector.index_range()) {
PackIsland *pack_island = pack_island_vector[i];
const float island_scale = pack_island->can_scale_(params) ? scale : 1.0f;
PChart *chart = handle->charts[pack_island->caller_index];
float matrix[2][2];
pack_island->build_transformation(island_scale, pack_island->angle, matrix);
for (PVert *v = chart->verts; v; v = v->nextlink) {
geometry::mul_v2_m2_add_v2v2(v->uv, matrix, v->uv, pack_island->pre_translate);
}
pack_island_vector[i] = nullptr;
delete pack_island;
}
uv_parametrizer_scale_x(handle, handle->aspect_y);
}
void uv_parametrizer_average(ParamHandle *phandle, bool ignore_pinned, bool scale_uv, bool shear)
{
int i;
float tot_area_3d = 0.0f;
float tot_area_uv = 0.0f;
float minv[2], maxv[2], trans[2];
if (phandle->ncharts == 0) {
return;
}
for (i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
if (ignore_pinned && chart->has_pins) {
continue;
}
/* Store original bounding box midpoint. */
p_chart_uv_bbox(chart, minv, maxv);
mid_v2_v2v2(chart->origin, minv, maxv);
if (scale_uv || shear) {
/* It's possible that for some "bad" inputs, the following iteration will converge slowly or
* perhaps even diverge. Rather than infinite loop, we only iterate a maximum of `max_iter`
* times. (Also useful when making changes to the calculation.) */
int max_iter = 10;
for (int j = 0; j < max_iter; j++) {
/* An island could contain millions of polygons. When summing many small values, we need to
* use double precision in the accumulator to maintain accuracy. Note that the individual
* calculations only need to be at single precision. */
double scale_cou = 0;
double scale_cov = 0;
double scale_cross = 0;
double weight_sum = 0;
for (PFace *f = chart->faces; f; f = f->nextlink) {
float m[2][2], s[2][2];
PVert *va = f->edge->vert;
PVert *vb = f->edge->next->vert;
PVert *vc = f->edge->next->next->vert;
s[0][0] = va->uv[0] - vc->uv[0];
s[0][1] = va->uv[1] - vc->uv[1];
s[1][0] = vb->uv[0] - vc->uv[0];
s[1][1] = vb->uv[1] - vc->uv[1];
/* Find the "U" axis and "V" axis in triangle co-ordinates. Normally this would require
* SVD, but in 2D we can use a cheaper matrix inversion instead. */
if (!invert_m2_m2(m, s)) {
continue;
}
float cou[3], cov[3]; /* i.e. Texture "U" and texture "V" in 3D co-ordinates. */
for (int k = 0; k < 3; k++) {
cou[k] = m[0][0] * (va->co[k] - vc->co[k]) + m[0][1] * (vb->co[k] - vc->co[k]);
cov[k] = m[1][0] * (va->co[k] - vc->co[k]) + m[1][1] * (vb->co[k] - vc->co[k]);
}
const float weight = p_face_area(f);
scale_cou += len_v3(cou) * weight;
scale_cov += len_v3(cov) * weight;
if (shear) {
normalize_v3(cov);
normalize_v3(cou);
/* Why is scale_cross called `cross` when we call `dot`? The next line calculates:
* `scale_cross += length(cross(cross(cou, face_normal), cov))`
* By construction, both `cou` and `cov` are orthogonal to the face normal.
* By definition, the normal vector has unit length. */
scale_cross += dot_v3v3(cou, cov) * weight;
}
weight_sum += weight;
}
if (scale_cou * scale_cov < 1e-10f) {
break;
}
const float scale_factor_u = scale_uv ? sqrtf(scale_cou / scale_cov) : 1.0f;
/* Compute correction transform. */
float t[2][2];
t[0][0] = scale_factor_u;
t[1][0] = clamp_f(float(scale_cross / weight_sum), -0.5f, 0.5f);
t[0][1] = 0;
t[1][1] = 1.0f / scale_factor_u;
/* Apply the correction. */
p_chart_uv_transform(chart, t);
/* How far from the identity transform are we? [[1,0],[0,1]] */
const float err = fabsf(t[0][0] - 1.0f) + fabsf(t[1][0]) + fabsf(t[0][1]) +
fabsf(t[1][1] - 1.0f);
const float tolerance = 1e-6f; /* Trade accuracy for performance. */
if (err < tolerance) {
/* Too slow? Use Richardson Extrapolation to accelerate the convergence. */
break;
}
}
}
chart->area_3d = 0.0f;
chart->area_uv = 0.0f;
for (PFace *f = chart->faces; f; f = f->nextlink) {
chart->area_3d += p_face_area(f);
chart->area_uv += fabsf(p_face_uv_area_signed(f));
}
tot_area_3d += chart->area_3d;
tot_area_uv += chart->area_uv;
}
if (tot_area_3d == 0.0f || tot_area_uv == 0.0f) {
/* Prevent divide by zero. */
return;
}
const float tot_fac = tot_area_3d / tot_area_uv;
for (i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
if (ignore_pinned && chart->has_pins) {
continue;
}
if (chart->area_3d != 0.0f && chart->area_uv != 0.0f) {
const float fac = chart->area_3d / chart->area_uv;
/* Average scale. */
p_chart_uv_scale(chart, sqrtf(fac / tot_fac));
/* Get the current island bounding box. */
p_chart_uv_bbox(chart, minv, maxv);
/* Move back to original midpoint. */
mid_v2_v2v2(trans, minv, maxv);
sub_v2_v2v2(trans, chart->origin, trans);
p_chart_uv_translate(chart, trans);
}
}
}
void uv_parametrizer_flush(ParamHandle *phandle)
{
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
if (chart->skip_flush) {
continue;
}
p_flush_uvs(phandle, chart);
}
}
void uv_parametrizer_flush_restore(ParamHandle *phandle)
{
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
for (PFace *f = chart->faces; f; f = f->nextlink) {
p_face_restore_uvs(f);
}
}
}
/* -------------------------------------------------------------------- */
/** \name Degenerate Geometry Fixing
* \{ */
static bool p_collapse_doubles_allowed(PEdge *edge, PEdge *pair, float threshold_squared)
{
PVert *oldv, *keepv;
p_collapsing_verts(edge, pair, &oldv, &keepv);
/* Do not collapse a pinned vertex unless the target vertex
* is also pinned. */
if ((oldv->flag & PVERT_PIN) && !(keepv->flag & PVERT_PIN)) {
return false;
}
if (!p_collapse_allowed_topologic(edge, pair)) {
return false;
}
PEdge *collapse_e = edge ? edge : pair;
return p_edge_length_squared(collapse_e) < threshold_squared;
}
static float p_collapse_doubles_cost(PEdge *edge, PEdge *pair)
{
PEdge *collapse_e = edge ? edge : pair;
return p_edge_length_squared(collapse_e);
}
static void UNUSED_FUNCTION(p_chart_collapse_doubles)(PChart *chart, const float threshold)
{
const float threshold_squared = threshold * threshold;
p_chart_simplify_compute(chart, p_collapse_doubles_cost, [=](PEdge *e, PEdge *pair) {
return p_collapse_doubles_allowed(e, pair, threshold_squared);
});
}
static void p_chart_flush_collapsed_uvs(PChart *chart)
{
PEdge *e, *pair, *edge;
PVert *newv, *keepv;
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) {
std::swap(edge, pair);
}
p_collapsing_verts(edge, pair, &newv, &keepv);
if (!(newv->flag & PVERT_PIN)) {
newv->uv[0] = keepv->uv[0];
newv->uv[1] = keepv->uv[1];
}
}
}
static bool p_validate_corrected_coords_point(const PEdge *corr_e,
const float corr_co1[3],
const float corr_co2[3],
const float min_area,
const float min_angle_cos)
{
/* Check whether the given corrected coordinates don't result in any other triangle with area
* lower than min_area. */
const PVert *corr_v = corr_e->vert;
const PEdge *e = corr_v->edge;
do {
float area;
if (e == corr_e) {
continue;
}
if (!(e->face->flag & PFACE_DONE) && (e != corr_e)) {
continue;
}
if (e->next->next == corr_e->pair) {
PVert *other_v = e->next->next->vert;
area = area_tri_v3(corr_co1, corr_co2, other_v->co);
}
else {
const PVert *other_v1 = e->next->vert;
const PVert *other_v2 = e->next->next->vert;
area = area_tri_v3(corr_co1, other_v1->co, other_v2->co);
}
if (area < min_area) {
return false;
}
float f_cos[3];
if (e->next->next == corr_e->pair) {
PVert *other_v = e->next->next->vert;
p_triangle_cos(corr_co1, corr_co2, other_v->co, f_cos, f_cos + 1, f_cos + 2);
}
else {
const PVert *other_v1 = e->next->vert;
const PVert *other_v2 = e->next->next->vert;
p_triangle_cos(corr_co1, other_v1->co, other_v2->co, f_cos, f_cos + 1, f_cos + 2);
}
for (int i = 0; i < 3; i++) {
if (f_cos[i] > min_angle_cos) {
return false;
}
}
} while ((e = p_wheel_edge_next(e)) && (e != corr_v->edge));
return true;
}
static bool p_validate_corrected_coords(const PEdge *corr_e,
const float corr_co[3],
float min_area,
float min_angle_cos)
{
/* Check whether the given corrected coordinates don't result in any other triangle with area
* lower than min_area. */
const PVert *corr_v = corr_e->vert;
const PEdge *e = corr_v->edge;
do {
if (!(e->face->flag & PFACE_DONE) && (e != corr_e)) {
continue;
}
const PVert *other_v1 = e->next->vert;
const PVert *other_v2 = e->next->next->vert;
const float area = area_tri_v3(corr_co, other_v1->co, other_v2->co);
if (area < min_area) {
return false;
}
float f_cos[3];
p_triangle_cos(corr_co, other_v1->co, other_v2->co, f_cos, f_cos + 1, f_cos + 2);
for (int i = 0; i < 3; i++) {
if (f_cos[i] > min_angle_cos) {
return false;
}
}
} while ((e = p_wheel_edge_next(e)) && (e != corr_v->edge));
return true;
}
static bool p_edge_matrix(float R[3][3], const float edge_dir[3])
{
static const constexpr float eps = 1.0e-5;
static const constexpr float n1[3] = {0.0f, 0.0f, 1.0f};
static const constexpr float n2[3] = {0.0f, 1.0f, 0.0f};
float edge_len = len_v3(edge_dir);
if (edge_len < eps) {
return false;
}
float edge_dir_norm[3];
copy_v3_v3(edge_dir_norm, edge_dir);
mul_v3_fl(edge_dir_norm, 1.0f / edge_len);
float normal_dir[3];
cross_v3_v3v3(normal_dir, edge_dir_norm, n1);
float normal_len = len_v3(normal_dir);
if (normal_len < eps) {
cross_v3_v3v3(normal_dir, edge_dir_norm, n2);
normal_len = len_v3(normal_dir);
if (normal_len < eps) {
return false;
}
}
mul_v3_fl(normal_dir, 1.0f / normal_len);
float tangent_dir[3];
cross_v3_v3v3(tangent_dir, edge_dir_norm, normal_dir);
R[0][0] = edge_dir_norm[0];
R[1][0] = edge_dir_norm[1];
R[2][0] = edge_dir_norm[2];
R[0][1] = normal_dir[0];
R[1][1] = normal_dir[1];
R[2][1] = normal_dir[2];
R[0][2] = tangent_dir[0];
R[1][2] = tangent_dir[1];
R[2][2] = tangent_dir[2];
return true;
}
static bool p_edge_matrix(float R[3][3], const PEdge *e)
{
float edge_dir[3];
copy_v3_v3(edge_dir, e->next->vert->co);
sub_v3_v3(edge_dir, e->vert->co);
return p_edge_matrix(R, edge_dir);
}
static const float CORR_ZERO_AREA_EPS = 1.0e-10f;
static bool p_chart_correct_degenerate_triangle_point(PFace *f,
float min_area,
float min_angle_cos)
{
static const float3 ref_edges[] = {
{1.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 1.0f, 1.0f},
{1.0f, 0.0f, 1.0f},
{1.0f, 1.0f, 0.0f},
{0.0f, 0.5f, 1.0f},
{0.5f, 0.0f, 1.0f},
{0.5f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.5f},
{1.0f, 0.0f, 0.5f},
{1.0f, 0.5f, 0.0f},
};
static const int ref_edge_count = ARRAY_SIZE(ref_edges);
static const int LEN_MULTIPLIER_COUNT = 3;
bool corr_co_found = false;
float corr_len = 2.0f * std::sqrt((min_area + CORR_ZERO_AREA_EPS) / 3.0f / std::sqrt(3.0f));
for (int m = 0; m < LEN_MULTIPLIER_COUNT; m++) {
for (int re = 0; re < ref_edge_count; re++) {
float M[3][3];
if (!p_edge_matrix(M, ref_edges[re])) {
continue;
}
float corr_co[3][3];
PEdge *e = f->edge;
for (int i = 0; i < 3; i++) {
const float angle = (float(i) / 3.0f) * float(2.0 * M_PI);
float corr_dir[3] = {0.0f, cos(angle), sin(angle)};
float corr_len_multiplied = corr_len * (m + 1);
mul_m3_v3(M, corr_dir);
mul_v3_fl(corr_dir, corr_len_multiplied);
copy_v3_v3(corr_co[i], e->vert->co);
add_v3_v3(corr_co[i], corr_dir);
e = e->next;
}
e = f->edge;
for (int i = 0; i < 3; i++) {
if (!p_validate_corrected_coords_point(
e, corr_co[i], corr_co[(i + 1) % 3], min_area, min_angle_cos))
{
return false;
}
e = e->next;
}
e = f->edge;
for (int i = 0; i < 3; i++) {
copy_v3_v3(e->vert->co, corr_co[i]);
e = e->next;
}
corr_co_found = true;
break;
}
if (corr_co_found) {
break;
}
}
if (!corr_co_found) {
return false;
}
f->flag |= PFACE_DONE;
return true;
}
static bool p_chart_correct_degenerate_triangles2(PChart *chart, float min_area, float min_angle)
{
static const float eps = 1.0e-6;
float min_angle_cos = std::cos(min_angle);
float min_angle_sin = std::sin(min_angle + CORR_ZERO_AREA_EPS);
for (PFace *f = chart->faces; f; f = f->nextlink) {
if (f->flag & PFACE_DONE) {
continue;
}
float face_area = p_face_area(f);
PEdge *max_edge = nullptr;
float max_edge_len = -std::numeric_limits<float>::infinity();
PEdge *min_edge = nullptr;
float min_edge_len = std::numeric_limits<float>::infinity();
PEdge *middle_edge = nullptr;
PEdge *e = f->edge;
do {
float len = p_edge_length(e);
if (len > max_edge_len) {
max_edge = e;
max_edge_len = len;
middle_edge = max_edge->next == min_edge ? min_edge->next : max_edge->next;
}
if (len < min_edge_len) {
min_edge = e;
min_edge_len = len;
middle_edge = min_edge->next == max_edge ? max_edge->next : min_edge->next;
}
e = e->next;
} while (e != f->edge);
BLI_assert(max_edge);
BLI_assert(min_edge);
BLI_assert(middle_edge);
bool small_uniside_tri = (face_area <= min_area) && (min_edge == max_edge);
if ((max_edge_len < eps) || small_uniside_tri) {
p_chart_correct_degenerate_triangle_point(f, min_area, min_angle_cos);
continue;
}
if (min_edge == max_edge) {
BLI_assert(face_area > min_area);
f->flag |= PFACE_DONE;
continue;
}
BLI_assert(middle_edge != max_edge);
BLI_assert(middle_edge != min_edge);
float M[3][3];
if (!p_edge_matrix(M, max_edge)) {
continue;
}
float max_face_cos =
middle_edge->next == max_edge ?
p_vec_cos(middle_edge->vert->co, max_edge->vert->co, min_edge->vert->co) :
p_vec_cos(max_edge->vert->co, middle_edge->vert->co, min_edge->vert->co);
float angle_corr_len = max_face_cos > min_angle_cos ?
p_edge_length(middle_edge) * min_angle_sin :
0.0f;
if ((face_area > min_area) && (angle_corr_len == 0.0f)) {
f->flag |= PFACE_DONE;
continue;
}
float corr_len = (min_area + CORR_ZERO_AREA_EPS) * 2.0f / max_edge_len;
corr_len = std::max(corr_len, angle_corr_len);
PEdge *corr_e = max_edge->next->next;
PVert *corr_v = corr_e->vert;
/* Check 4 distinct directions. */
static const constexpr int DIR_COUNT = 16;
static const constexpr int LEN_MULTIPLIER_COUNT = 2;
float corr_co[3];
bool corr_co_found = false;
for (int m = 0; m < LEN_MULTIPLIER_COUNT; m++) {
for (int d = 0; d < DIR_COUNT; d++) {
const float angle = (float(d) / DIR_COUNT) * float(2.0 * M_PI);
float corr_dir[3] = {0.0f, cos(angle), sin(angle)};
const float corr_len_multiplied = corr_len * (m + 1);
mul_m3_v3(M, corr_dir);
mul_v3_fl(corr_dir, corr_len_multiplied);
copy_v3_v3(corr_co, corr_v->co);
add_v3_v3(corr_co, corr_dir);
if (p_validate_corrected_coords(corr_e, corr_co, min_area, min_angle_cos)) {
corr_co_found = true;
break;
}
}
if (corr_co_found) {
break;
}
}
if (!corr_co_found) {
continue;
}
f->flag |= PFACE_DONE;
copy_v3_v3(corr_v->co, corr_co);
}
return true;
}
#ifndef NDEBUG
static bool p_validate_triangle_angles(const PVert *vert1,
const PVert *vert2,
const PVert *vert3,
const float min_angle_cos)
{
float t_cos[3];
p_triangle_cos(vert1->co, vert2->co, vert3->co, t_cos, t_cos + 1, t_cos + 2);
for (int i = 0; i < 3; i++) {
if (t_cos[i] > min_angle_cos) {
return false;
}
}
return true;
}
#endif
static bool UNUSED_FUNCTION_NO_SLIM(p_chart_correct_degenerate_triangles)(PChart *chart,
const float min_area,
const float min_angle)
{
/* Look for degenerate triangles: triangles with angles lower than `min_angle` or having area
* lower than `min_area` and try to correct vertex coordinates so that the resulting triangle is
* not degenerate.
*
* The return value indicates whether all triangles could be corrected.
*/
bool ret = p_chart_correct_degenerate_triangles2(chart, min_area, min_angle);
#ifndef NDEBUG
float min_angle_cos = std::cos(min_angle - CORR_ZERO_AREA_EPS);
#endif
for (PFace *f = chart->faces; f; f = f->nextlink) {
if (!(f->flag & PFACE_DONE)) {
ret = false;
}
else {
#ifndef NDEBUG
float f_area = p_face_area(f);
BLI_assert(f_area > (min_area - CORR_ZERO_AREA_EPS));
PVert *vert1 = f->edge->vert;
PVert *vert2 = f->edge->next->vert;
PVert *vert3 = f->edge->next->next->vert;
BLI_assert(p_validate_triangle_angles(vert1, vert2, vert3, min_angle_cos));
#endif
}
f->flag &= ~PFACE_DONE;
}
return ret;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name SLIM Implementation (Private API)
* \{ */
#ifdef WITH_UV_SLIM
/**
* Get SLIM parameters from the scene.
*/
static slim::MatrixTransfer *slim_matrix_transfer(const ParamSlimOptions *slim_options)
{
slim::MatrixTransfer *mt = new slim::MatrixTransfer();
mt->use_weights = slim_options->weight_influence > 0.0f;
mt->weight_influence = slim_options->weight_influence;
mt->n_iterations = slim_options->iterations;
mt->reflection_mode = slim_options->no_flip;
mt->skip_initialization = slim_options->skip_init;
return mt;
}
/**
* For one chart, allocate memory. If no accurate estimate
* (e.g. for number of pinned vertices) overestimate and correct later.
*/
static void slim_allocate_matrices(const PChart *chart, slim::MatrixTransferChart *mt_chart)
{
mt_chart->verts_num = chart->nverts;
mt_chart->faces_num = chart->nfaces;
mt_chart->edges_num = chart->nedges;
mt_chart->v_matrices.resize(mt_chart->verts_num * 3);
mt_chart->uv_matrices.resize(mt_chart->verts_num * 2);
mt_chart->pp_matrices.resize(mt_chart->verts_num * 2);
mt_chart->f_matrices.resize(mt_chart->faces_num * 3);
mt_chart->p_matrices.resize(mt_chart->verts_num);
mt_chart->b_vectors.resize(mt_chart->verts_num);
/* Also clear memory for weight vectors, hence 'new' followed by `()`. */
mt_chart->w_vectors.resize(mt_chart->verts_num, 0.0);
mt_chart->e_matrices.resize(mt_chart->edges_num * 2 * 2);
mt_chart->el_vectors.resize(mt_chart->edges_num * 2);
}
/**
* Transfer edges and edge lengths.
*/
static void slim_transfer_edges(PChart *chart, slim::MatrixTransferChart *mt_chart)
{
std::vector<int> &E = mt_chart->e_matrices;
std::vector<double> &EL = mt_chart->el_vectors;
PEdge *outer;
p_chart_boundaries(chart, &outer);
PEdge *be = outer;
int eid = 0;
static const float DOUBLED_VERT_THRESHOLD = 1.0e-5;
do {
E[eid] = be->vert->slim_id;
const float edge_len = p_edge_length(be);
EL[eid] = edge_len;
/* Temporary solution : SLIM doesn't support doubled vertices for now. */
if (edge_len < DOUBLED_VERT_THRESHOLD) {
mt_chart->succeeded = false;
}
be = p_boundary_edge_next(be);
E[eid + mt_chart->edges_num + mt_chart->boundary_vertices_num] = be->vert->slim_id;
eid++;
} while (be != outer);
for (PEdge *e = chart->edges; e; e = e->nextlink) {
PEdge *e1 = e->next;
E[eid] = e->vert->slim_id;
const float edge_len = p_edge_length(e);
EL[eid] = edge_len;
/* Temporary solution : SLIM doesn't support doubled vertices for now. */
if (edge_len < DOUBLED_VERT_THRESHOLD) {
mt_chart->succeeded = false;
}
E[eid + mt_chart->edges_num + mt_chart->boundary_vertices_num] = e1->vert->slim_id;
eid++;
}
}
/**
* Transfer vertices and pinned information.
*/
static void slim_transfer_vertices(const PChart *chart,
slim::MatrixTransferChart *mt_chart,
slim::MatrixTransfer *mt)
{
int r = mt_chart->verts_num;
std::vector<double> &v_mat = mt_chart->v_matrices;
std::vector<double> &uv_mat = mt_chart->uv_matrices;
std::vector<int> &p_mat = mt_chart->p_matrices;
std::vector<double> &pp_mat = mt_chart->pp_matrices;
std::vector<float> &w_vec = mt_chart->w_vectors;
int p_vid = 0;
int vid = mt_chart->boundary_vertices_num;
/* For every vertex, fill up V matrix and P matrix (pinned vertices) */
for (PVert *v = chart->verts; v; v = v->nextlink) {
if (!v->on_boundary_flag) {
if (mt->use_weights) {
w_vec[vid] = v->weight;
}
v->slim_id = vid;
vid++;
}
v_mat[v->slim_id] = v->co[0];
v_mat[r + v->slim_id] = v->co[1];
v_mat[2 * r + v->slim_id] = v->co[2];
uv_mat[v->slim_id] = v->uv[0];
uv_mat[r + v->slim_id] = v->uv[1];
if (v->flag & PVERT_PIN || (mt->is_minimize_stretch && !(v->flag & PVERT_SELECT))) {
mt_chart->pinned_vertices_num += 1;
p_mat[p_vid] = v->slim_id;
pp_mat[2 * p_vid] = double(v->uv[0]);
pp_mat[2 * p_vid + 1] = double(v->uv[1]);
p_vid += 1;
}
}
}
/**
* Transfer boundary vertices.
*/
static void slim_transfer_boundary_vertices(PChart *chart,
slim::MatrixTransferChart *mt_chart,
const slim::MatrixTransfer *mt)
{
std::vector<int> &b_vec = mt_chart->b_vectors;
std::vector<float> &w_vec = mt_chart->w_vectors;
/* For every vertex, set slim_flag to 0 */
for (PVert *v = chart->verts; v; v = v->nextlink) {
v->on_boundary_flag = false;
}
/* Find vertices on boundary and save into vector B */
PEdge *outer;
p_chart_boundaries(chart, &outer);
PEdge *be = outer;
int vid = 0;
do {
if (mt->use_weights) {
w_vec[vid] = be->vert->weight;
}
mt_chart->boundary_vertices_num += 1;
be->vert->slim_id = vid;
be->vert->on_boundary_flag = true;
b_vec[vid] = vid;
vid += 1;
be = p_boundary_edge_next(be);
} while (be != outer);
}
/**
* Transfer faces.
*/
static void slim_transfer_faces(const PChart *chart, slim::MatrixTransferChart *mt_chart)
{
int fid = 0;
for (PFace *f = chart->faces; f; f = f->nextlink) {
PEdge *e = f->edge;
PEdge *e1 = e->next;
PEdge *e2 = e1->next;
int r = mt_chart->faces_num;
std::vector<int> &F = mt_chart->f_matrices;
F[fid] = e->vert->slim_id;
F[r + fid] = e1->vert->slim_id;
F[2 * r + fid] = e2->vert->slim_id;
fid++;
}
}
/**
* Conversion Function to build matrix for SLIM Parametrization.
*/
static void slim_convert_blender(ParamHandle *phandle, slim::MatrixTransfer *mt)
{
static const float SLIM_CORR_MIN_AREA = 1.0e-8;
static const float SLIM_CORR_MIN_ANGLE = DEG2RADF(1.0);
mt->charts.resize(phandle->ncharts);
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
slim::MatrixTransferChart *mt_chart = &mt->charts[i];
p_chart_correct_degenerate_triangles(chart, SLIM_CORR_MIN_AREA, SLIM_CORR_MIN_ANGLE);
mt_chart->succeeded = true;
mt_chart->pinned_vertices_num = 0;
mt_chart->boundary_vertices_num = 0;
/* Allocate memory for matrices of Vertices,Faces etc. for each chart. */
slim_allocate_matrices(chart, mt_chart);
/* For each chart, fill up matrices. */
slim_transfer_boundary_vertices(chart, mt_chart, mt);
slim_transfer_vertices(chart, mt_chart, mt);
slim_transfer_edges(chart, mt_chart);
slim_transfer_faces(chart, mt_chart);
mt_chart->pp_matrices.resize(mt_chart->pinned_vertices_num * 2);
mt_chart->pp_matrices.shrink_to_fit();
mt_chart->p_matrices.resize(mt_chart->pinned_vertices_num);
mt_chart->p_matrices.shrink_to_fit();
mt_chart->b_vectors.resize(mt_chart->boundary_vertices_num);
mt_chart->b_vectors.shrink_to_fit();
mt_chart->e_matrices.resize((mt_chart->edges_num + mt_chart->boundary_vertices_num) * 2);
mt_chart->e_matrices.shrink_to_fit();
}
}
static void slim_transfer_data_to_slim(ParamHandle *phandle, const ParamSlimOptions *slim_options)
{
slim::MatrixTransfer *mt = slim_matrix_transfer(slim_options);
slim_convert_blender(phandle, mt);
phandle->slim_mt = mt;
}
/**
* Set UV on each vertex after SLIM parametrization, for each chart.
*/
static void slim_flush_uvs(ParamHandle *phandle,
slim::MatrixTransfer *mt,
int *count_changed,
int *count_failed,
bool live = false)
{
int vid;
PVert *v;
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
slim::MatrixTransferChart *mt_chart = &mt->charts[i];
if (mt_chart->succeeded) {
if (count_changed) {
(*count_changed)++;
}
std::vector<double> &UV = mt_chart->uv_matrices;
for (v = chart->verts; v; v = v->nextlink) {
if (v->flag & PVERT_PIN) {
continue;
}
vid = v->slim_id;
v->uv[0] = UV[vid];
v->uv[1] = UV[mt_chart->verts_num + vid];
}
}
else {
if (count_failed) {
(*count_failed)++;
}
if (!live) {
for (v = chart->verts; v; v = v->nextlink) {
v->uv[0] = 0.0f;
v->uv[1] = 0.0f;
}
}
}
}
}
/**
* Cleanup memory.
*/
static void slim_free_matrix_transfer(ParamHandle *phandle)
{
delete phandle->slim_mt;
phandle->slim_mt = nullptr;
}
static void slim_get_pinned_vertex_data(ParamHandle *phandle,
PChart *chart,
slim::MatrixTransferChart &mt_chart,
slim::PinnedVertexData &pinned_vertex_data)
{
std::vector<int> &pinned_vertex_indices = pinned_vertex_data.pinned_vertex_indices;
std::vector<double> &pinned_vertex_positions_2D = pinned_vertex_data.pinned_vertex_positions_2D;
std::vector<int> &selected_pins = pinned_vertex_data.selected_pins;
pinned_vertex_indices.clear();
pinned_vertex_positions_2D.clear();
selected_pins.clear();
/* Boundary vertices have lower slim_ids, process them first. */
PEdge *outer;
p_chart_boundaries(chart, &outer);
PEdge *be = outer;
do {
if (be->vert->flag & PVERT_PIN) {
/* Reload vertex position. */
p_vert_load_pin_select_uvs(phandle, be->vert);
if (be->vert->flag & PVERT_SELECT) {
selected_pins.push_back(be->vert->slim_id);
}
pinned_vertex_indices.push_back(be->vert->slim_id);
pinned_vertex_positions_2D.push_back(be->vert->uv[0]);
pinned_vertex_positions_2D.push_back(be->vert->uv[1]);
}
be = p_boundary_edge_next(be);
} while (be != outer);
PVert *v;
for (v = chart->verts; v; v = v->nextlink) {
if (!v->on_boundary_flag && (v->flag & PVERT_PIN)) {
/* Reload `v`. */
p_vert_load_pin_select_uvs(phandle, v);
if (v->flag & PVERT_SELECT) {
selected_pins.push_back(v->slim_id);
}
pinned_vertex_indices.push_back(v->slim_id);
pinned_vertex_positions_2D.push_back(v->uv[0]);
pinned_vertex_positions_2D.push_back(v->uv[1]);
}
}
mt_chart.pinned_vertices_num = pinned_vertex_indices.size();
}
#endif /* WITH_UV_SLIM */
/** \} */
/* -------------------------------------------------------------------- */
/** \name SLIM Integration (Public API)
* \{ */
void uv_parametrizer_slim_solve(ParamHandle *phandle,
const ParamSlimOptions *slim_options,
int *count_changed,
int *count_failed)
{
#ifdef WITH_UV_SLIM
slim_transfer_data_to_slim(phandle, slim_options);
slim::MatrixTransfer *mt = phandle->slim_mt;
mt->parametrize();
slim_flush_uvs(phandle, mt, count_changed, count_failed);
slim_free_matrix_transfer(phandle);
#else
*count_changed = 0;
*count_failed = 0;
UNUSED_VARS(phandle, slim_options, count_changed, count_failed);
#endif /* !WITH_UV_SLIM */
}
void uv_parametrizer_slim_live_begin(ParamHandle *phandle, const ParamSlimOptions *slim_options)
{
#ifdef WITH_UV_SLIM
slim_transfer_data_to_slim(phandle, slim_options);
slim::MatrixTransfer *mt = phandle->slim_mt;
for (int i = 0; i < phandle->ncharts; i++) {
for (PFace *f = phandle->charts[i]->faces; f; f = f->nextlink) {
p_face_backup_uvs(f);
}
}
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
slim::MatrixTransferChart &mt_chart = mt->charts[i];
bool select = false, deselect = false;
/* Give vertices matrix indices and count pins. */
for (PVert *v = chart->verts; v; v = v->nextlink) {
if (v->flag & PVERT_PIN) {
if (v->flag & PVERT_SELECT) {
select = true;
}
}
if (!(v->flag & PVERT_SELECT)) {
deselect = true;
}
}
if (!select || !deselect) {
chart->skip_flush = true;
mt_chart.succeeded = false;
continue;
}
mt->setup_slim_data(mt_chart);
}
#else
UNUSED_VARS(phandle, slim_options);
#endif /* !WITH_UV_SLIM */
}
void uv_parametrizer_slim_stretch_iteration(ParamHandle *phandle, const float blend)
{
#ifdef WITH_UV_SLIM
slim::MatrixTransfer *mt = phandle->slim_mt;
/* Do one iteration and transfer UVs. */
for (int i = 0; i < phandle->ncharts; i++) {
mt->charts[i].parametrize_single_iteration();
mt->charts[i].transfer_uvs_blended(blend);
}
/* Assign new UVs back to each vertex. */
slim_flush_uvs(phandle, mt, nullptr, nullptr);
#else
UNUSED_VARS(phandle, blend);
#endif /* !WITH_UV_SLIM */
}
void uv_parametrizer_slim_live_solve_iteration(ParamHandle *phandle)
{
#ifdef WITH_UV_SLIM
slim::MatrixTransfer *mt = phandle->slim_mt;
/* Do one iteration and transfer UVs */
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
slim::MatrixTransferChart &mt_chart = mt->charts[i];
if (!mt_chart.data) {
continue;
}
slim_get_pinned_vertex_data(phandle, chart, mt_chart, mt->pinned_vertex_data);
mt->parametrize_live(mt_chart, mt->pinned_vertex_data);
}
/* Assign new UVs back to each vertex. */
const bool live = true;
slim_flush_uvs(phandle, mt, nullptr, nullptr, live);
#else
UNUSED_VARS(phandle);
#endif /* !WITH_UV_SLIM */
}
void uv_parametrizer_slim_live_end(ParamHandle *phandle)
{
#ifdef WITH_UV_SLIM
slim::MatrixTransfer *mt = phandle->slim_mt;
for (int i = 0; i < phandle->ncharts; i++) {
slim::MatrixTransferChart *mt_chart = &mt->charts[i];
mt_chart->free_slim_data();
}
slim_free_matrix_transfer(phandle);
#else
UNUSED_VARS(phandle);
#endif /* WITH_UV_SLIM */
}
bool uv_parametrizer_is_slim(const ParamHandle *phandle)
{
#ifdef WITH_UV_SLIM
return phandle->slim_mt != nullptr;
#else
UNUSED_VARS(phandle);
return false;
#endif
}
/** \} */
} // namespace blender::geometry