Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenkernel/intern/mesh_validate.c
| Context not available. | |||||
| #include <string.h> | #include <string.h> | ||||
| #include <limits.h> | #include <limits.h> | ||||
| #include "CLG_log.h" | |||||
| #include "DNA_mesh_types.h" | #include "DNA_mesh_types.h" | ||||
| #include "DNA_meshdata_types.h" | #include "DNA_meshdata_types.h" | ||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| Context not available. | |||||
| /* loop v/e are unsigned, so using max uint_32 value as invalid marker... */ | /* loop v/e are unsigned, so using max uint_32 value as invalid marker... */ | ||||
| #define INVALID_LOOP_EDGE_MARKER 4294967295u | #define INVALID_LOOP_EDGE_MARKER 4294967295u | ||||
| static CLG_LogRef LOG = { "bke.mesh" }; | |||||
| /** \name Internal functions | /** \name Internal functions | ||||
| * \{ */ | * \{ */ | ||||
| Context not available. | |||||
| /** \name Mesh Validation | /** \name Mesh Validation | ||||
| * \{ */ | * \{ */ | ||||
| #define PRINT_MSG(...) (void) \ | #define PRINT_MSG(...) if(do_verbose) CLOG_INFO(&LOG, 1, __VA_ARGS__) | ||||
| ( \ | |||||
| ((do_verbose) ? printf(__VA_ARGS__) : 0)) | |||||
| #define PRINT_ERR(...) (void) \ | #define PRINT_ERR(...) do { \ | ||||
| (is_valid = false, \ | is_valid = false; \ | ||||
| ((do_verbose) ? printf(__VA_ARGS__) : 0)) | if (do_verbose) { CLOG_ERROR(&LOG, __VA_ARGS__); } \ | ||||
| } while(0) | |||||
| /** | /** | ||||
| * Validate the mesh, \a do_fixes requires \a mesh to be non-null. | * Validate the mesh, \a do_fixes requires \a mesh to be non-null. | ||||
| Context not available. | |||||
| free_flag.as_flag = 0; | free_flag.as_flag = 0; | ||||
| recalc_flag.as_flag = 0; | recalc_flag.as_flag = 0; | ||||
| PRINT_MSG("%s: verts(%u), edges(%u), loops(%u), polygons(%u)\n", | PRINT_MSG("verts(%u), edges(%u), loops(%u), polygons(%u)", | ||||
| __func__, totvert, totedge, totloop, totpoly); | totvert, totedge, totloop, totpoly); | ||||
| if (totedge == 0 && totpoly != 0) { | if (totedge == 0 && totpoly != 0) { | ||||
| PRINT_ERR("\tLogical error, %u polygons and 0 edges\n", totpoly); | PRINT_ERR("Logical error, %u polygons and 0 edges", totpoly); | ||||
| recalc_flag.edges = do_fixes; | recalc_flag.edges = do_fixes; | ||||
| } | } | ||||
| Context not available. | |||||
| for (j = 0; j < 3; j++) { | for (j = 0; j < 3; j++) { | ||||
| if (!isfinite(mv->co[j])) { | if (!isfinite(mv->co[j])) { | ||||
| PRINT_ERR("\tVertex %u: has invalid coordinate\n", i); | PRINT_ERR("Vertex %u: has invalid coordinate", i); | ||||
| if (do_fixes) { | if (do_fixes) { | ||||
| zero_v3(mv->co); | zero_v3(mv->co); | ||||
| Context not available. | |||||
| } | } | ||||
| if (fix_normal) { | if (fix_normal) { | ||||
| PRINT_ERR("\tVertex %u: has zero normal, assuming Z-up normal\n", i); | PRINT_ERR("\tVertex %u: has zero normal, assuming Z-up normal", i); | ||||
| if (do_fixes) { | if (do_fixes) { | ||||
| mv->no[2] = SHRT_MAX; | mv->no[2] = SHRT_MAX; | ||||
| fix_flag.verts = true; | fix_flag.verts = true; | ||||
| Context not available. | |||||
| bool remove = false; | bool remove = false; | ||||
| if (me->v1 == me->v2) { | if (me->v1 == me->v2) { | ||||
| PRINT_ERR("\tEdge %u: has matching verts, both %u\n", i, me->v1); | PRINT_ERR("\tEdge %u: has matching verts, both %u", i, me->v1); | ||||
| remove = do_fixes; | remove = do_fixes; | ||||
| } | } | ||||
| if (me->v1 >= totvert) { | if (me->v1 >= totvert) { | ||||
| PRINT_ERR("\tEdge %u: v1 index out of range, %u\n", i, me->v1); | PRINT_ERR("\tEdge %u: v1 index out of range, %u", i, me->v1); | ||||
| remove = do_fixes; | remove = do_fixes; | ||||
| } | } | ||||
| if (me->v2 >= totvert) { | if (me->v2 >= totvert) { | ||||
| PRINT_ERR("\tEdge %u: v2 index out of range, %u\n", i, me->v2); | PRINT_ERR("\tEdge %u: v2 index out of range, %u", i, me->v2); | ||||
| remove = do_fixes; | remove = do_fixes; | ||||
| } | } | ||||
| if ((me->v1 != me->v2) && BLI_edgehash_haskey(edge_hash, me->v1, me->v2)) { | if ((me->v1 != me->v2) && BLI_edgehash_haskey(edge_hash, me->v1, me->v2)) { | ||||
| PRINT_ERR("\tEdge %u: is a duplicate of %d\n", i, | PRINT_ERR("\tEdge %u: is a duplicate of %d", i, | ||||
| POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, me->v1, me->v2))); | POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, me->v1, me->v2))); | ||||
| remove = do_fixes; | remove = do_fixes; | ||||
| } | } | ||||
| Context not available. | |||||
| # define REMOVE_FACE_TAG(_mf) { _mf->v3 = 0; free_flag.faces = do_fixes; } (void)0 | # define REMOVE_FACE_TAG(_mf) { _mf->v3 = 0; free_flag.faces = do_fixes; } (void)0 | ||||
| # define CHECK_FACE_VERT_INDEX(a, b) \ | # define CHECK_FACE_VERT_INDEX(a, b) \ | ||||
| if (mf->a == mf->b) { \ | if (mf->a == mf->b) { \ | ||||
| PRINT_ERR(" face %u: verts invalid, " STRINGIFY(a) "/" STRINGIFY(b) " both %u\n", i, mf->a); \ | PRINT_ERR(" face %u: verts invalid, " STRINGIFY(a) "/" STRINGIFY(b) " both %u", i, mf->a); \ | ||||
| remove = do_fixes; \ | remove = do_fixes; \ | ||||
| } (void)0 | } (void)0 | ||||
| # define CHECK_FACE_EDGE(a, b) \ | # define CHECK_FACE_EDGE(a, b) \ | ||||
| if (!BLI_edgehash_haskey(edge_hash, mf->a, mf->b)) { \ | if (!BLI_edgehash_haskey(edge_hash, mf->a, mf->b)) { \ | ||||
| PRINT_ERR(" face %u: edge " STRINGIFY(a) "/" STRINGIFY(b) \ | PRINT_ERR(" face %u: edge " STRINGIFY(a) "/" STRINGIFY(b) \ | ||||
| " (%u,%u) is missing edge data\n", i, mf->a, mf->b); \ | " (%u,%u) is missing edge data", i, mf->a, mf->b); \ | ||||
| recalc_flag.edges = do_fixes; \ | recalc_flag.edges = do_fixes; \ | ||||
| } (void)0 | } (void)0 | ||||
| Context not available. | |||||
| SortFace *sf_prev; | SortFace *sf_prev; | ||||
| unsigned int totsortface = 0; | unsigned int totsortface = 0; | ||||
| PRINT_ERR("No Polys, only tessellated Faces\n"); | PRINT_ERR("No Polys, only tessellated Faces"); | ||||
| for (i = 0, mf = mfaces, sf = sort_faces; i < totface; i++, mf++) { | for (i = 0, mf = mfaces, sf = sort_faces; i < totface; i++, mf++) { | ||||
| bool remove = false; | bool remove = false; | ||||
| Context not available. | |||||
| do { | do { | ||||
| fv[fidx] = *(&(mf->v1) + fidx); | fv[fidx] = *(&(mf->v1) + fidx); | ||||
| if (fv[fidx] >= totvert) { | if (fv[fidx] >= totvert) { | ||||
| PRINT_ERR("\tFace %u: 'v%d' index out of range, %u\n", i, fidx + 1, fv[fidx]); | PRINT_ERR("\tFace %u: 'v%d' index out of range, %u", i, fidx + 1, fv[fidx]); | ||||
| remove = do_fixes; | remove = do_fixes; | ||||
| } | } | ||||
| } while (fidx--); | } while (fidx--); | ||||
| Context not available. | |||||
| mf_prev = mfaces + sf_prev->index; | mf_prev = mfaces + sf_prev->index; | ||||
| if (mf->v4) { | if (mf->v4) { | ||||
| PRINT_ERR("\tFace %u & %u: are duplicates (%u,%u,%u,%u) (%u,%u,%u,%u)\n", | PRINT_ERR("\tFace %u & %u: are duplicates (%u,%u,%u,%u) (%u,%u,%u,%u)", | ||||
| sf->index, sf_prev->index, mf->v1, mf->v2, mf->v3, mf->v4, | sf->index, sf_prev->index, mf->v1, mf->v2, mf->v3, mf->v4, | ||||
| mf_prev->v1, mf_prev->v2, mf_prev->v3, mf_prev->v4); | mf_prev->v1, mf_prev->v2, mf_prev->v3, mf_prev->v4); | ||||
| } | } | ||||
| else { | else { | ||||
| PRINT_ERR("\tFace %u & %u: are duplicates (%u,%u,%u) (%u,%u,%u)\n", | PRINT_ERR("\tFace %u & %u: are duplicates (%u,%u,%u) (%u,%u,%u)", | ||||
| sf->index, sf_prev->index, mf->v1, mf->v2, mf->v3, | sf->index, sf_prev->index, mf->v1, mf->v2, mf->v3, | ||||
| mf_prev->v1, mf_prev->v2, mf_prev->v3); | mf_prev->v1, mf_prev->v2, mf_prev->v3); | ||||
| } | } | ||||
| Context not available. | |||||
| if (mp->loopstart < 0 || mp->totloop < 3) { | if (mp->loopstart < 0 || mp->totloop < 3) { | ||||
| /* Invalid loop data. */ | /* Invalid loop data. */ | ||||
| PRINT_ERR("\tPoly %u is invalid (loopstart: %d, totloop: %d)\n", | PRINT_ERR("\tPoly %u is invalid (loopstart: %d, totloop: %d)", | ||||
| sp->index, mp->loopstart, mp->totloop); | sp->index, mp->loopstart, mp->totloop); | ||||
| sp->invalid = true; | sp->invalid = true; | ||||
| } | } | ||||
| else if (mp->loopstart + mp->totloop > totloop) { | else if (mp->loopstart + mp->totloop > totloop) { | ||||
| /* Invalid loop data. */ | /* Invalid loop data. */ | ||||
| PRINT_ERR("\tPoly %u uses loops out of range (loopstart: %d, loopend: %d, max nbr of loops: %u)\n", | PRINT_ERR("\tPoly %u uses loops out of range (loopstart: %d, loopend: %d, max nbr of loops: %u)", | ||||
| sp->index, mp->loopstart, mp->loopstart + mp->totloop - 1, totloop - 1); | sp->index, mp->loopstart, mp->loopstart + mp->totloop - 1, totloop - 1); | ||||
| sp->invalid = true; | sp->invalid = true; | ||||
| } | } | ||||
| Context not available. | |||||
| for (j = 0, ml = &mloops[sp->loopstart]; j < mp->totloop; j++, ml++, v++) { | for (j = 0, ml = &mloops[sp->loopstart]; j < mp->totloop; j++, ml++, v++) { | ||||
| if (ml->v >= totvert) { | if (ml->v >= totvert) { | ||||
| /* Invalid vert idx. */ | /* Invalid vert idx. */ | ||||
| PRINT_ERR("\tLoop %u has invalid vert reference (%u)\n", sp->loopstart + j, ml->v); | PRINT_ERR("\tLoop %u has invalid vert reference (%u)", sp->loopstart + j, ml->v); | ||||
| sp->invalid = true; | sp->invalid = true; | ||||
| } | } | ||||
| else if (mverts[ml->v].flag & ME_VERT_TMP_TAG) { | else if (mverts[ml->v].flag & ME_VERT_TMP_TAG) { | ||||
| PRINT_ERR("\tPoly %u has duplicated vert reference at corner (%u)\n", i, j); | PRINT_ERR("\tPoly %u has duplicated vert reference at corner (%u)", i, j); | ||||
| sp->invalid = true; | sp->invalid = true; | ||||
| } | } | ||||
| else { | else { | ||||
| Context not available. | |||||
| v2 = mloops[sp->loopstart + (j + 1) % mp->totloop].v; | v2 = mloops[sp->loopstart + (j + 1) % mp->totloop].v; | ||||
| if (!BLI_edgehash_haskey(edge_hash, v1, v2)) { | if (!BLI_edgehash_haskey(edge_hash, v1, v2)) { | ||||
| /* Edge not existing. */ | /* Edge not existing. */ | ||||
| PRINT_ERR("\tPoly %u needs missing edge (%d, %d)\n", sp->index, v1, v2); | PRINT_ERR("\tPoly %u needs missing edge (%d, %d)", sp->index, v1, v2); | ||||
| if (do_fixes) | if (do_fixes) | ||||
| recalc_flag.edges = true; | recalc_flag.edges = true; | ||||
| else | else | ||||
| Context not available. | |||||
| int prev_e = ml->e; | int prev_e = ml->e; | ||||
| ml->e = POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, v1, v2)); | ml->e = POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, v1, v2)); | ||||
| fix_flag.loops_edge = true; | fix_flag.loops_edge = true; | ||||
| PRINT_ERR("\tLoop %u has invalid edge reference (%d), fixed using edge %u\n", | PRINT_ERR("\tLoop %u has invalid edge reference (%d), fixed using edge %u", | ||||
| sp->loopstart + j, prev_e, ml->e); | sp->loopstart + j, prev_e, ml->e); | ||||
| } | } | ||||
| else { | else { | ||||
| PRINT_ERR("\tLoop %u has invalid edge reference (%u)\n", sp->loopstart + j, ml->e); | PRINT_ERR("\tLoop %u has invalid edge reference (%u)", sp->loopstart + j, ml->e); | ||||
| sp->invalid = true; | sp->invalid = true; | ||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| int prev_e = ml->e; | int prev_e = ml->e; | ||||
| ml->e = POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, v1, v2)); | ml->e = POINTER_AS_INT(BLI_edgehash_lookup(edge_hash, v1, v2)); | ||||
| fix_flag.loops_edge = true; | fix_flag.loops_edge = true; | ||||
| PRINT_ERR("\tPoly %u has invalid edge reference (%d, is_removed: %d), fixed using edge %u\n", | PRINT_ERR("\tPoly %u has invalid edge reference (%d, is_removed: %d), fixed using edge %u", | ||||
| sp->index, prev_e, IS_REMOVED_EDGE(me), ml->e); | sp->index, prev_e, IS_REMOVED_EDGE(me), ml->e); | ||||
| } | } | ||||
| else { | else { | ||||
| PRINT_ERR("\tPoly %u has invalid edge reference (%u)\n", sp->index, ml->e); | PRINT_ERR("\tPoly %u has invalid edge reference (%u)", sp->index, ml->e); | ||||
| sp->invalid = true; | sp->invalid = true; | ||||
| } | } | ||||
| } | } | ||||
| Context not available. | |||||
| /* Test same polys. */ | /* Test same polys. */ | ||||
| if ((p1_nv == p2_nv) && (memcmp(p1_v, p2_v, p1_nv * sizeof(*p1_v)) == 0)) { | if ((p1_nv == p2_nv) && (memcmp(p1_v, p2_v, p1_nv * sizeof(*p1_v)) == 0)) { | ||||
| if (do_verbose) { | if (do_verbose) { | ||||
| // TODO: convert list to string | |||||
| PRINT_ERR("\tPolys %u and %u use same vertices (%d", | PRINT_ERR("\tPolys %u and %u use same vertices (%d", | ||||
| prev_sp->index, sp->index, *p1_v); | prev_sp->index, sp->index, *p1_v); | ||||
| for (j = 1; j < p1_nv; j++) | for (j = 1; j < p1_nv; j++) | ||||
| PRINT_ERR(", %d", p1_v[j]); | PRINT_ERR(", %d", p1_v[j]); | ||||
| PRINT_ERR("), considering poly %u as invalid.\n", sp->index); | PRINT_ERR("), considering poly %u as invalid.", sp->index); | ||||
| } | } | ||||
| else { | else { | ||||
| is_valid = false; | is_valid = false; | ||||
| Context not available. | |||||
| /* Unused loops. */ | /* Unused loops. */ | ||||
| if (prev_end < sp->loopstart) { | if (prev_end < sp->loopstart) { | ||||
| for (j = prev_end, ml = &mloops[prev_end]; j < sp->loopstart; j++, ml++) { | for (j = prev_end, ml = &mloops[prev_end]; j < sp->loopstart; j++, ml++) { | ||||
| PRINT_ERR("\tLoop %u is unused.\n", j); | PRINT_ERR("\tLoop %u is unused.", j); | ||||
| if (do_fixes) | if (do_fixes) | ||||
| REMOVE_LOOP_TAG(ml); | REMOVE_LOOP_TAG(ml); | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| /* Multi-used loops. */ | /* Multi-used loops. */ | ||||
| else if (prev_end > sp->loopstart) { | else if (prev_end > sp->loopstart) { | ||||
| PRINT_ERR("\tPolys %u and %u share loops from %d to %d, considering poly %u as invalid.\n", | PRINT_ERR("\tPolys %u and %u share loops from %d to %d, considering poly %u as invalid.", | ||||
| prev_sp->index, sp->index, sp->loopstart, prev_end, sp->index); | prev_sp->index, sp->index, sp->loopstart, prev_end, sp->index); | ||||
| if (do_fixes) { | if (do_fixes) { | ||||
| REMOVE_POLY_TAG((&mpolys[sp->index])); | REMOVE_POLY_TAG((&mpolys[sp->index])); | ||||
| Context not available. | |||||
| /* We may have some remaining unused loops to get rid of! */ | /* We may have some remaining unused loops to get rid of! */ | ||||
| if (prev_end < totloop) { | if (prev_end < totloop) { | ||||
| for (j = prev_end, ml = &mloops[prev_end]; j < totloop; j++, ml++) { | for (j = prev_end, ml = &mloops[prev_end]; j < totloop; j++, ml++) { | ||||
| PRINT_ERR("\tLoop %u is unused.\n", j); | PRINT_ERR("\tLoop %u is unused.", j); | ||||
| if (do_fixes) | if (do_fixes) | ||||
| REMOVE_LOOP_TAG(ml); | REMOVE_LOOP_TAG(ml); | ||||
| } | } | ||||
| Context not available. | |||||
| for (j = 0, dw = dv->dw; j < dv->totweight; j++, dw++) { | for (j = 0, dw = dv->dw; j < dv->totweight; j++, dw++) { | ||||
| /* note, greater than max defgroups is accounted for in our code, but not < 0 */ | /* note, greater than max defgroups is accounted for in our code, but not < 0 */ | ||||
| if (!isfinite(dw->weight)) { | if (!isfinite(dw->weight)) { | ||||
| PRINT_ERR("\tVertex deform %u, group %d has weight: %f\n", i, dw->def_nr, dw->weight); | PRINT_ERR("\tVertex deform %u, group %d has weight: %f", i, dw->def_nr, dw->weight); | ||||
| if (do_fixes) { | if (do_fixes) { | ||||
| dw->weight = 0.0f; | dw->weight = 0.0f; | ||||
| fix_flag.verts_weight = true; | fix_flag.verts_weight = true; | ||||
| } | } | ||||
| } | } | ||||
| else if (dw->weight < 0.0f || dw->weight > 1.0f) { | else if (dw->weight < 0.0f || dw->weight > 1.0f) { | ||||
| PRINT_ERR("\tVertex deform %u, group %d has weight: %f\n", i, dw->def_nr, dw->weight); | PRINT_ERR("\tVertex deform %u, group %d has weight: %f", i, dw->def_nr, dw->weight); | ||||
| if (do_fixes) { | if (do_fixes) { | ||||
| CLAMP(dw->weight, 0.0f, 1.0f); | CLAMP(dw->weight, 0.0f, 1.0f); | ||||
| fix_flag.verts_weight = true; | fix_flag.verts_weight = true; | ||||
| Context not available. | |||||
| } | } | ||||
| if (dw->def_nr < 0) { | if (dw->def_nr < 0) { | ||||
| PRINT_ERR("\tVertex deform %u, has invalid group %d\n", i, dw->def_nr); | PRINT_ERR("\tVertex deform %u, has invalid group %d", i, dw->def_nr); | ||||
| if (do_fixes) { | if (do_fixes) { | ||||
| defvert_remove_group(dv, dw); | defvert_remove_group(dv, dw); | ||||
| fix_flag.verts_weight = true; | fix_flag.verts_weight = true; | ||||
| Context not available. | |||||
| bool changed; | bool changed; | ||||
| if (do_verbose) { | if (do_verbose) { | ||||
| printf("MESH: %s\n", me->id.name + 2); | CLOG_INFO(&LOG, 2, "MESH: %s", me->id.name + 2); | ||||
| } | } | ||||
| is_valid &= BKE_mesh_validate_all_customdata( | is_valid &= BKE_mesh_validate_all_customdata( | ||||
| Context not available. | |||||