366 lines
9.7 KiB
C++
366 lines
9.7 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bmesh
|
|
*
|
|
* Convert triangle to quads.
|
|
*
|
|
* TODO
|
|
* - convert triangles to any sided faces, not just quads.
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_math_geom.h"
|
|
#include "BLI_math_rotation.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_sort_utils.h"
|
|
|
|
#include "BKE_customdata.hh"
|
|
|
|
#include "bmesh.hh"
|
|
|
|
#include "intern/bmesh_operators_private.hh" /* own include */
|
|
|
|
/**
|
|
* \note Assumes edges are validated before reaching this point.
|
|
*/
|
|
static float quad_calc_error(const float v1[3],
|
|
const float v2[3],
|
|
const float v3[3],
|
|
const float v4[3])
|
|
{
|
|
/* Gives a 'weight' to a pair of triangles that join an edge
|
|
* to decide how good a join they would make. */
|
|
/* NOTE: this is more complicated than it needs to be and should be cleaned up. */
|
|
float error = 0.0f;
|
|
|
|
/* Normal difference */
|
|
{
|
|
float n1[3], n2[3];
|
|
float angle_a, angle_b;
|
|
float diff;
|
|
|
|
normal_tri_v3(n1, v1, v2, v3);
|
|
normal_tri_v3(n2, v1, v3, v4);
|
|
angle_a = compare_v3v3(n1, n2, FLT_EPSILON) ? 0.0f : angle_normalized_v3v3(n1, n2);
|
|
|
|
normal_tri_v3(n1, v2, v3, v4);
|
|
normal_tri_v3(n2, v4, v1, v2);
|
|
angle_b = compare_v3v3(n1, n2, FLT_EPSILON) ? 0.0f : angle_normalized_v3v3(n1, n2);
|
|
|
|
diff = (angle_a + angle_b) / float(M_PI * 2);
|
|
|
|
error += diff;
|
|
}
|
|
|
|
/* Co-linearity */
|
|
{
|
|
float edge_vecs[4][3];
|
|
float diff;
|
|
|
|
sub_v3_v3v3(edge_vecs[0], v1, v2);
|
|
sub_v3_v3v3(edge_vecs[1], v2, v3);
|
|
sub_v3_v3v3(edge_vecs[2], v3, v4);
|
|
sub_v3_v3v3(edge_vecs[3], v4, v1);
|
|
|
|
normalize_v3(edge_vecs[0]);
|
|
normalize_v3(edge_vecs[1]);
|
|
normalize_v3(edge_vecs[2]);
|
|
normalize_v3(edge_vecs[3]);
|
|
|
|
/* a completely skinny face is 'pi' after halving */
|
|
diff = (fabsf(angle_normalized_v3v3(edge_vecs[0], edge_vecs[1]) - float(M_PI_2)) +
|
|
fabsf(angle_normalized_v3v3(edge_vecs[1], edge_vecs[2]) - float(M_PI_2)) +
|
|
fabsf(angle_normalized_v3v3(edge_vecs[2], edge_vecs[3]) - float(M_PI_2)) +
|
|
fabsf(angle_normalized_v3v3(edge_vecs[3], edge_vecs[0]) - float(M_PI_2))) /
|
|
float(M_PI * 2);
|
|
|
|
error += diff;
|
|
}
|
|
|
|
/* Concavity */
|
|
{
|
|
float area_min, area_max, area_a, area_b;
|
|
float diff;
|
|
|
|
area_a = area_tri_v3(v1, v2, v3) + area_tri_v3(v1, v3, v4);
|
|
area_b = area_tri_v3(v2, v3, v4) + area_tri_v3(v4, v1, v2);
|
|
|
|
area_min = min_ff(area_a, area_b);
|
|
area_max = max_ff(area_a, area_b);
|
|
|
|
diff = area_max ? (1.0f - (area_min / area_max)) : 1.0f;
|
|
|
|
error += diff;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static void bm_edge_to_quad_verts(const BMEdge *e, const BMVert *r_v_quad[4])
|
|
{
|
|
BLI_assert(e->l->f->len == 3 && e->l->radial_next->f->len == 3);
|
|
BLI_assert(BM_edge_is_manifold(e));
|
|
r_v_quad[0] = e->l->v;
|
|
r_v_quad[1] = e->l->prev->v;
|
|
r_v_quad[2] = e->l->next->v;
|
|
r_v_quad[3] = e->l->radial_next->prev->v;
|
|
}
|
|
|
|
/* cache customdata delimiters */
|
|
struct DelimitData_CD {
|
|
int cd_type;
|
|
int cd_size;
|
|
int cd_offset;
|
|
int cd_offset_end;
|
|
};
|
|
|
|
struct DelimitData {
|
|
uint do_seam : 1;
|
|
uint do_sharp : 1;
|
|
uint do_mat : 1;
|
|
uint do_angle_face : 1;
|
|
uint do_angle_shape : 1;
|
|
|
|
float angle_face;
|
|
float angle_face__cos;
|
|
|
|
float angle_shape;
|
|
|
|
DelimitData_CD cdata[4];
|
|
int cdata_len;
|
|
};
|
|
|
|
static bool bm_edge_is_contiguous_loop_cd_all(const BMEdge *e, const DelimitData_CD *delimit_data)
|
|
{
|
|
int cd_offset;
|
|
for (cd_offset = delimit_data->cd_offset; cd_offset < delimit_data->cd_offset_end;
|
|
cd_offset += delimit_data->cd_size)
|
|
{
|
|
if (BM_edge_is_contiguous_loop_cd(e, delimit_data->cd_type, cd_offset) == false) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool bm_edge_delimit_cdata(CustomData *ldata,
|
|
eCustomDataType type,
|
|
DelimitData_CD *r_delim_cd)
|
|
{
|
|
const int layer_len = CustomData_number_of_layers(ldata, type);
|
|
r_delim_cd->cd_type = type;
|
|
r_delim_cd->cd_size = CustomData_sizeof(eCustomDataType(r_delim_cd->cd_type));
|
|
r_delim_cd->cd_offset = CustomData_get_n_offset(ldata, type, 0);
|
|
r_delim_cd->cd_offset_end = r_delim_cd->cd_offset + (r_delim_cd->cd_size * layer_len);
|
|
return (r_delim_cd->cd_offset != -1);
|
|
}
|
|
|
|
static float bm_edge_is_delimit(const BMEdge *e, const DelimitData *delimit_data)
|
|
{
|
|
BMFace *f_a = e->l->f, *f_b = e->l->radial_next->f;
|
|
#if 0
|
|
const bool is_contig = BM_edge_is_contiguous(e);
|
|
float angle;
|
|
#endif
|
|
|
|
if (delimit_data->do_seam && BM_elem_flag_test(e, BM_ELEM_SEAM)) {
|
|
goto fail;
|
|
}
|
|
|
|
if (delimit_data->do_sharp && (BM_elem_flag_test(e, BM_ELEM_SMOOTH) == 0)) {
|
|
goto fail;
|
|
}
|
|
|
|
if (delimit_data->do_mat && (f_a->mat_nr != f_b->mat_nr)) {
|
|
goto fail;
|
|
}
|
|
|
|
if (delimit_data->do_angle_face) {
|
|
if (dot_v3v3(f_a->no, f_b->no) < delimit_data->angle_face__cos) {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (delimit_data->do_angle_shape) {
|
|
const BMVert *verts[4];
|
|
bm_edge_to_quad_verts(e, verts);
|
|
|
|
/* if we're checking the shape at all, a flipped face is out of the question */
|
|
if (is_quad_flip_v3(verts[0]->co, verts[1]->co, verts[2]->co, verts[3]->co)) {
|
|
goto fail;
|
|
}
|
|
else {
|
|
float edge_vecs[4][3];
|
|
|
|
sub_v3_v3v3(edge_vecs[0], verts[0]->co, verts[1]->co);
|
|
sub_v3_v3v3(edge_vecs[1], verts[1]->co, verts[2]->co);
|
|
sub_v3_v3v3(edge_vecs[2], verts[2]->co, verts[3]->co);
|
|
sub_v3_v3v3(edge_vecs[3], verts[3]->co, verts[0]->co);
|
|
|
|
normalize_v3(edge_vecs[0]);
|
|
normalize_v3(edge_vecs[1]);
|
|
normalize_v3(edge_vecs[2]);
|
|
normalize_v3(edge_vecs[3]);
|
|
|
|
if ((fabsf(angle_normalized_v3v3(edge_vecs[0], edge_vecs[1]) - float(M_PI_2)) >
|
|
delimit_data->angle_shape) ||
|
|
(fabsf(angle_normalized_v3v3(edge_vecs[1], edge_vecs[2]) - float(M_PI_2)) >
|
|
delimit_data->angle_shape) ||
|
|
(fabsf(angle_normalized_v3v3(edge_vecs[2], edge_vecs[3]) - float(M_PI_2)) >
|
|
delimit_data->angle_shape) ||
|
|
(fabsf(angle_normalized_v3v3(edge_vecs[3], edge_vecs[0]) - float(M_PI_2)) >
|
|
delimit_data->angle_shape))
|
|
{
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (delimit_data->cdata_len) {
|
|
int i;
|
|
for (i = 0; i < delimit_data->cdata_len; i++) {
|
|
if (!bm_edge_is_contiguous_loop_cd_all(e, &delimit_data->cdata[i])) {
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
fail:
|
|
return true;
|
|
}
|
|
|
|
#define EDGE_MARK (1 << 0)
|
|
|
|
#define FACE_OUT (1 << 0)
|
|
#define FACE_INPUT (1 << 2)
|
|
|
|
void bmo_join_triangles_exec(BMesh *bm, BMOperator *op)
|
|
{
|
|
float angle_face, angle_shape;
|
|
|
|
BMIter iter;
|
|
BMOIter siter;
|
|
BMFace *f;
|
|
BMEdge *e;
|
|
/* data: edge-to-join, sort_value: error weight */
|
|
SortPtrByFloat *jedges;
|
|
uint i, totedge;
|
|
uint totedge_tag = 0;
|
|
|
|
DelimitData delimit_data = {0};
|
|
|
|
delimit_data.do_seam = BMO_slot_bool_get(op->slots_in, "cmp_seam");
|
|
delimit_data.do_sharp = BMO_slot_bool_get(op->slots_in, "cmp_sharp");
|
|
delimit_data.do_mat = BMO_slot_bool_get(op->slots_in, "cmp_materials");
|
|
|
|
angle_face = BMO_slot_float_get(op->slots_in, "angle_face_threshold");
|
|
if (angle_face < DEG2RADF(180.0f)) {
|
|
delimit_data.angle_face = angle_face;
|
|
delimit_data.angle_face__cos = cosf(angle_face);
|
|
delimit_data.do_angle_face = true;
|
|
}
|
|
else {
|
|
delimit_data.do_angle_face = false;
|
|
}
|
|
|
|
angle_shape = BMO_slot_float_get(op->slots_in, "angle_shape_threshold");
|
|
if (angle_shape < DEG2RADF(180.0f)) {
|
|
delimit_data.angle_shape = angle_shape;
|
|
delimit_data.do_angle_shape = true;
|
|
}
|
|
else {
|
|
delimit_data.do_angle_shape = false;
|
|
}
|
|
|
|
if (BMO_slot_bool_get(op->slots_in, "cmp_uvs") &&
|
|
bm_edge_delimit_cdata(
|
|
&bm->ldata, CD_PROP_FLOAT2, &delimit_data.cdata[delimit_data.cdata_len]))
|
|
{
|
|
delimit_data.cdata_len += 1;
|
|
}
|
|
|
|
delimit_data.cdata[delimit_data.cdata_len].cd_offset = -1;
|
|
if (BMO_slot_bool_get(op->slots_in, "cmp_vcols") &&
|
|
bm_edge_delimit_cdata(
|
|
&bm->ldata, CD_PROP_BYTE_COLOR, &delimit_data.cdata[delimit_data.cdata_len]))
|
|
{
|
|
delimit_data.cdata_len += 1;
|
|
}
|
|
|
|
/* flag all edges of all input face */
|
|
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
|
|
if (f->len == 3) {
|
|
BMO_face_flag_enable(bm, f, FACE_INPUT);
|
|
}
|
|
}
|
|
|
|
/* flag edges surrounded by 2 flagged triangles */
|
|
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
|
|
BMFace *f_a, *f_b;
|
|
if (BM_edge_face_pair(e, &f_a, &f_b) &&
|
|
(BMO_face_flag_test(bm, f_a, FACE_INPUT) && BMO_face_flag_test(bm, f_b, FACE_INPUT)))
|
|
{
|
|
if (!bm_edge_is_delimit(e, &delimit_data)) {
|
|
BMO_edge_flag_enable(bm, e, EDGE_MARK);
|
|
totedge_tag++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (totedge_tag == 0) {
|
|
return;
|
|
}
|
|
|
|
/* over alloc, some of the edges will be delimited */
|
|
jedges = static_cast<SortPtrByFloat *>(MEM_mallocN(sizeof(*jedges) * totedge_tag, __func__));
|
|
|
|
i = 0;
|
|
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
|
|
const BMVert *verts[4];
|
|
float error;
|
|
|
|
if (!BMO_edge_flag_test(bm, e, EDGE_MARK)) {
|
|
continue;
|
|
}
|
|
|
|
bm_edge_to_quad_verts(e, verts);
|
|
|
|
error = quad_calc_error(verts[0]->co, verts[1]->co, verts[2]->co, verts[3]->co);
|
|
|
|
jedges[i].data = e;
|
|
jedges[i].sort_value = error;
|
|
i++;
|
|
}
|
|
|
|
totedge = i;
|
|
qsort(jedges, totedge, sizeof(*jedges), BLI_sortutil_cmp_float);
|
|
|
|
for (i = 0; i < totedge; i++) {
|
|
BMLoop *l_a, *l_b;
|
|
|
|
e = static_cast<BMEdge *>(jedges[i].data);
|
|
l_a = e->l;
|
|
l_b = e->l->radial_next;
|
|
|
|
/* check if another edge already claimed this face */
|
|
if ((l_a->f->len == 3) && (l_b->f->len == 3)) {
|
|
BMFace *f_new;
|
|
f_new = BM_faces_join_pair(bm, l_a, l_b, true);
|
|
if (f_new) {
|
|
BMO_face_flag_enable(bm, f_new, FACE_OUT);
|
|
}
|
|
}
|
|
}
|
|
|
|
MEM_freeN(jedges);
|
|
|
|
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
|
|
}
|