diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index 694b66b..2a10fca 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -65,6 +65,8 @@ #include #include +#include "PIL_time.h" + /* Due to cyclic dependencies it's possible that curve used for * deformation here is not evaluated at the time of evaluating * this modifier. @@ -208,6 +210,162 @@ static int *find_doubles_index_map(BMesh *bm, BMOperator *dupe_op, return index_map; } +BLI_INLINE float sum_v3(const float v[3]) +{ + return v[0] + v[1] + v[2]; +} + +/* Little structure used for sorting vertices, when processing doubles */ +typedef struct SortVertsElem { + int vertex_num; /* The original index of the vertex, prior to sorting */ + float co[3]; /* Its coordinates */ +} SortVertsElem; + + +static int vergaverco(const void *e1, const void *e2) +{ + const SortVertsElem *sv1 = (SortVertsElem *)e1; + const SortVertsElem *sv2 = (SortVertsElem *)e2; + const float x1 = sum_v3(sv1->co); + const float x2 = sum_v3(sv2->co); + + if (x1 > x2) return 1; + else if (x1 < x2) return -1; + else return 0; +} + +/* map_doubles takes as inputs two sets of verts, to be processed for detection of doubles and mapping. + * Each set of verts is defined by its start within mverts array and its num_verts; + * It builds a mapping for all vertices within source, to vertices within target, or -1 if no double found + * The int doubles_map[num_verts_source] array must have been allocated by caller. +*/ +static void map_doubles( + int *doubles_map, /* TODO returns time for the moment, should be void */ + MVert *mverts, + int target_start, + int target_num_verts, + int source_start, + int source_num_verts, + float dist, + float *duration) +{ + const float dist3 = 3 * dist; + double t1, t2; + int i, i_source, i_target, i_target_low_bound, target_end, source_end; + SortVertsElem *sorted_verts_target, *sorted_verts_source, *svt, *svs; + SortVertsElem *sve_source, *sve_target, *sve_target_low_bound; + MVert *vt, *vs; + bool target_scan_completed; + + t1 = PIL_check_seconds_timer(); + + target_end = target_start + target_num_verts; + source_end = source_start + source_num_verts; + + /* build array of MVerts to be tested for merging */ + sorted_verts_target = MEM_callocN(sizeof(SortVertsElem) * target_num_verts, "MOD array map doubles 1"); + sorted_verts_source = MEM_callocN(sizeof(SortVertsElem) * source_num_verts, "MOD array map doubles 2"); + + /* Copy target vertices index and cos into SortVertsElem array */ + vt = mverts + target_start; + svt = sorted_verts_target; + for (i = target_start; i < target_end; i++, vt++, svt++) { + svt->vertex_num = i; + copy_v3_v3(svt->co, vt->co); + } + + /* Copy source vertices index and cos into SortVertsElem array */ + vs = mverts + source_start; + svs = sorted_verts_source; + for (i = source_start; i < source_end; i++, vs++, svs++) { + svs->vertex_num = i; + copy_v3_v3(svt->co, vs->co); + } + + /* sort arrays according to sum of vertex coordinates (sumco) */ + qsort(sorted_verts_target, target_num_verts, sizeof(SortVertsElem), vergaverco); + qsort(sorted_verts_source, source_num_verts, sizeof(SortVertsElem), vergaverco); + + sve_target_low_bound = sorted_verts_target; + i_target_low_bound = 0; + target_scan_completed = false; + + /* Scan source vertices, in SortVertsElem sorted array, */ + /* all the while maintaining the lower bound of possible doubles in target vertices */ + for (i_source = 0, sve_source = sorted_verts_source; + i_source < source_num_verts; + i_source++, sve_source++) + { + bool double_found; + float sve_source_sumco; + + /* If source has already been assigned to a target (in an earlier call, with other chunks) */ + if (doubles_map[sve_source->vertex_num] != -1) { + continue; + } + + /* If target fully scanned already, then all remaining source vertices cannot have a double */ + if (target_scan_completed) { + doubles_map[sve_source->vertex_num] = -1; + continue; + } + + sve_source_sumco = sum_v3(sve_source->co); + + /* Skip all target vertices that are more than dist3 lower in terms of sumco */ + /* and advance the overall lower bound, applicable to all remaining vertices as well. */ + while ((i_target_low_bound < target_num_verts) && + (sum_v3(sve_target_low_bound->co) < sve_source_sumco - dist3)) + { + i_target_low_bound++; + sve_target_low_bound++; + } + /* If end of target list reached, then no more possible doubles */ + if (i_target_low_bound >= target_num_verts) { + doubles_map[sve_source->vertex_num] = -1; + target_scan_completed = true; + continue; + } + /* Test target candidates starting at the low bound of possible doubles, ordered in terms of sumco */ + i_target = i_target_low_bound; + sve_target = sve_target_low_bound; + + /* i_target will scan vertices in the [v_source_sumco - dist3; v_source_sumco + dist3] range */ + + double_found = false; + while ((i_target < target_num_verts) && + (sum_v3(sve_target->co) <= sve_source_sumco + dist3)) + { + /* Testing distance for candidate double in target */ + /* v_target is within dist3 of v_source in terms of sumco; check real distance */ + if (compare_len_v3v3(sve_source->co, sve_target->co, dist)) { + /* Double found */ + /* If double target is itself already mapped to other vertex, then skip to earliest target */ + int target_vertex = sve_target->vertex_num; + if (doubles_map[target_vertex] != -1) + target_vertex = doubles_map[target_vertex]; + doubles_map[sve_source->vertex_num] = target_vertex; + double_found = true; + break; + } + i_target++; + sve_target++; + } + /* End of candidate scan : if none found then no doubles */ + if (!double_found) { + doubles_map[sve_source->vertex_num] = -1; + } + } + + MEM_freeN(sorted_verts_source); + MEM_freeN(sorted_verts_target); + + t2 = PIL_check_seconds_timer(); + *duration += (float)(t2 - t1); +} + + + /* Used for start/end cap. * * this function expects all existing vertices to be tagged, @@ -215,7 +373,7 @@ static int *find_doubles_index_map(BMesh *bm, BMOperator *dupe_op, * * All verts will be tagged on exit. */ -static void bm_merge_dm_transform(BMesh *bm, DerivedMesh *dm, float mat[4][4], +static void bm_merge_dm_transform_1(BMesh *bm, DerivedMesh *dm, float mat[4][4], const ArrayModifierData *amd, BMOperator *dupe_op, BMOpSlot dupe_op_slot_args[BMO_OP_MAX_SLOTS], const char *dupe_slot_name, @@ -293,6 +451,49 @@ static void bm_merge_dm_transform(BMesh *bm, DerivedMesh *dm, float mat[4][4], } } +static void bm_merge_dm_transform_2( + DerivedMesh *result_dm, DerivedMesh *cap_dm, float cap_offset[4][4], + unsigned int cap_verts_index, unsigned int cap_edges_index, int cap_loops_index, int cap_polys_index, + int cap_nverts, int cap_nedges, int cap_nloops, int cap_npolys) +{ + int i; + MVert *mv; + MEdge *me; + MLoop *ml; + MPoly *mp; + + DM_copy_vert_data(cap_dm, result_dm, 0, cap_verts_index, cap_nverts); + DM_copy_edge_data(cap_dm, result_dm, 0, cap_edges_index, cap_nedges); + DM_copy_loop_data(cap_dm, result_dm, 0, cap_loops_index, cap_nloops); + DM_copy_poly_data(cap_dm, result_dm, 0, cap_polys_index, cap_npolys); + + mv = CDDM_get_verts(result_dm) + cap_verts_index; + + for (i = 0; i < cap_nverts; i++, mv++) { + mul_m4_v3(cap_offset, mv->co); + } + + /* adjust cap edge vertex indices */ + me = CDDM_get_edges(result_dm) + cap_edges_index; + for (i = 0; i < cap_nedges; i++, me++) { + me->v1 += cap_verts_index; + me->v2 += cap_verts_index; + } + + /* adjust cap poly loopstart indices */ + mp = CDDM_get_polys(result_dm) + cap_polys_index; + for (i = 0; i < cap_npolys; i++, mp++) { + mp->loopstart += cap_loops_index; + } + + /* adjust cap loop vertex and edge indices */ + ml = CDDM_get_loops(result_dm) + cap_loops_index; + for (i = 0; i < cap_nloops; i++, ml++) { + ml->v += cap_verts_index; + ml->e += cap_edges_index; + } +} + static void merge_first_last(BMesh *bm, const ArrayModifierData *amd, BMOperator *dupe_first, @@ -327,9 +528,324 @@ static void merge_first_last(BMesh *bm, BMO_op_finish(bm, &find_op); } -static DerivedMesh *arrayModifier_doArray(ArrayModifierData *amd, - Scene *scene, Object *ob, DerivedMesh *dm, - ModifierApplyFlag flag) +static DerivedMesh *arrayModifier_doArray_2( + ArrayModifierData *amd, + Scene *scene, Object *ob, DerivedMesh *dm, + ModifierApplyFlag flag) +{ + MVert *mv, *mv_prev, *result_dm_verts, *src_mvert; + + MEdge *me; + MLoop *ml; + MPoly *mp; + int i, j, c, count; + float length; + /* offset matrix */ + float offset[4][4]; + float current_offset[4][4]; + float final_offset[4][4]; + int *full_doubles_map = NULL; + int tot_doubles; + bool exist_double; + float tot_time_doubles = 1; + + int start_cap_nverts = 0, start_cap_nedges = 0, start_cap_npolys = 0, start_cap_nloops = 0; + int end_cap_nverts = 0, end_cap_nedges = 0, end_cap_npolys = 0, end_cap_nloops = 0; + int result_nverts = 0, result_nedges = 0, result_npolys = 0, result_nloops = 0; + int chunk_nverts, chunk_nedges, chunk_nloops, chunk_npolys; + int first_chunk_start, first_chunk_nverts, last_chunk_start, last_chunk_nverts; + + DerivedMesh *result_dm, *start_cap_dm = NULL, *end_cap_dm = NULL; + + chunk_nverts = dm->getNumVerts(dm); + chunk_nedges = dm->getNumEdges(dm); + chunk_nloops = dm->getNumLoops(dm); + chunk_npolys = dm->getNumPolys(dm); + + + count = amd->count; + + /* need to avoid infinite recursion here */ + if (amd->start_cap && amd->start_cap != ob && amd->start_cap->type == OB_MESH) { + start_cap_dm = get_dm_for_modifier(amd->start_cap, flag); + if (start_cap_dm) { + start_cap_nverts = start_cap_dm->getNumVerts(start_cap_dm); + start_cap_nedges = start_cap_dm->getNumEdges(start_cap_dm); + start_cap_nloops = start_cap_dm->getNumLoops(start_cap_dm); + start_cap_npolys = start_cap_dm->getNumPolys(start_cap_dm); + } + } + if (amd->end_cap && amd->end_cap != ob && amd->end_cap->type == OB_MESH) { + end_cap_dm = get_dm_for_modifier(amd->end_cap, flag); + if (end_cap_dm) { + end_cap_nverts = end_cap_dm->getNumVerts(end_cap_dm); + end_cap_nedges = end_cap_dm->getNumEdges(end_cap_dm); + end_cap_nloops = end_cap_dm->getNumLoops(end_cap_dm); + end_cap_npolys = end_cap_dm->getNumPolys(end_cap_dm); + } + } + + /* Build up offset array, cumulating all settings options */ + + unit_m4(offset); + src_mvert = dm->getVertArray(dm); + + if (amd->offset_type & MOD_ARR_OFF_CONST) + add_v3_v3v3(offset[3], offset[3], amd->offset); + + if (amd->offset_type & MOD_ARR_OFF_RELATIVE) { + for (j = 0; j < 3; j++) + offset[3][j] += amd->scale[j] * vertarray_size(src_mvert, chunk_nverts, j); + } + + if ((amd->offset_type & MOD_ARR_OFF_OBJ) && (amd->offset_ob)) { + float obinv[4][4]; + float result_mat[4][4]; + + if (ob) + invert_m4_m4(obinv, ob->obmat); + else + unit_m4(obinv); + + mul_serie_m4(result_mat, offset, + obinv, amd->offset_ob->obmat, + NULL, NULL, NULL, NULL, NULL); + copy_m4_m4(offset, result_mat); + } + + if (amd->fit_type == MOD_ARR_FITCURVE && amd->curve_ob) { + Curve *cu = amd->curve_ob->data; + if (cu) { +#ifdef CYCLIC_DEPENDENCY_WORKAROUND + if (amd->curve_ob->curve_cache == NULL) { + BKE_displist_make_curveTypes(scene, amd->curve_ob, false); + } +#endif + + if (amd->curve_ob->curve_cache && amd->curve_ob->curve_cache->path) { + float scale = mat4_to_scale(amd->curve_ob->obmat); + length = scale * amd->curve_ob->curve_cache->path->totdist; + } + } + } + + /* calculate the maximum number of copies which will fit within the + * prescribed length */ + if (amd->fit_type == MOD_ARR_FITLENGTH || amd->fit_type == MOD_ARR_FITCURVE) { + float dist = len_v3(offset[3]); + + if (dist > 1e-6f) + /* this gives length = first copy start to last copy end + * add a tiny offset for floating point rounding errors */ + count = (length + 1e-6f) / dist; + else + /* if the offset has no translation, just make one copy */ + count = 1; + } + + if (count < 1) + count = 1; + + /* The number of verts, edges, loops, polys, before eventually merging doubles */ + result_nverts = chunk_nverts * count + start_cap_nverts + end_cap_nverts; + result_nedges = chunk_nedges * count + start_cap_nedges + end_cap_nedges; + result_nloops = chunk_nloops * count + start_cap_nloops + end_cap_nloops; + result_npolys = chunk_npolys * count + start_cap_npolys + end_cap_npolys; + + /* Initialize a result dm */ + result_dm = CDDM_from_template(dm, result_nverts, result_nedges, 0, result_nloops, result_npolys); + result_dm_verts = CDDM_get_verts(result_dm); + + if (amd->flags & MOD_ARR_MERGE) { + /* Will probably need full_doubles_map for handling merge */ + full_doubles_map = MEM_mallocN(sizeof(int) * result_nverts, "mod array doubles map"); + for (i = 0; i < result_nverts; i++) + full_doubles_map[i] = -1; + } + + /*copy customdata to original geometry*/ + DM_copy_vert_data(dm, result_dm, 0, 0, chunk_nverts); + DM_copy_edge_data(dm, result_dm, 0, 0, chunk_nedges); + DM_copy_loop_data(dm, result_dm, 0, 0, chunk_nloops); + DM_copy_poly_data(dm, result_dm, 0, 0, chunk_npolys); + + /* subsurf for eg wont have mesh data in the */ + /* now add mvert/medge/mface layers */ + + if (!CustomData_has_layer(&dm->vertData, CD_MVERT)) { + dm->copyVertArray(dm, result_dm_verts); + } + if (!CustomData_has_layer(&dm->edgeData, CD_MEDGE)) { + dm->copyEdgeArray(dm, CDDM_get_edges(result_dm)); + } + if (!CustomData_has_layer(&dm->polyData, CD_MPOLY)) { + dm->copyLoopArray(dm, CDDM_get_loops(result_dm)); + dm->copyPolyArray(dm, CDDM_get_polys(result_dm)); + } + + /* Remember first chunk, in case of cap merge */ + first_chunk_start = 0; + first_chunk_nverts = chunk_nverts; + + unit_m4(current_offset); + for (c = 1; c < count; c++) { + /* copy customdata to new geometry */ + DM_copy_vert_data(result_dm, result_dm, 0, c * chunk_nverts, chunk_nverts); + DM_copy_edge_data(result_dm, result_dm, 0, c * chunk_nedges, chunk_nedges); + DM_copy_loop_data(result_dm, result_dm, 0, c * chunk_nloops, chunk_nloops); + DM_copy_poly_data(result_dm, result_dm, 0, c * chunk_npolys, chunk_npolys); + + mv_prev = result_dm_verts; + mv = mv_prev + c * chunk_nverts; + + /* recalculate cumulative offset here */ + mul_m4_m4m4(current_offset, current_offset, offset); + + /* apply offset to all new verts */ + for (i = 0; i < chunk_nverts; i++, mv++, mv_prev++) { + mul_m4_v3(current_offset, mv->co); + } + + /* adjust edge vertex indices */ + me = CDDM_get_edges(result_dm) + c * chunk_nedges; + for (i = 0; i < chunk_nedges; i++, me++) { + me->v1 += c * chunk_nverts; + me->v2 += c * chunk_nverts; + } + + mp = CDDM_get_polys(result_dm) + c * chunk_npolys; + for (i = 0; i < chunk_npolys; i++, mp++) + mp->loopstart += c * chunk_nloops; + + /* adjust loop vertex and edge indices */ + ml = CDDM_get_loops(result_dm) + c * chunk_nloops; + for (i = 0; i < chunk_nloops; i++, ml++) { + ml->v += c * chunk_nverts; + ml->e += c * chunk_nedges; + } + + /* Handle merge between chunk n and n-1 */ + if ((amd->flags & MOD_ARR_MERGE) && (c >= 1)) { + map_doubles(full_doubles_map, + result_dm_verts, + (c - 1) * chunk_nverts, + chunk_nverts, + c * chunk_nverts, + chunk_nverts, + amd->merge_dist, + &tot_time_doubles); + } + } + + last_chunk_start = (count -1) * chunk_nverts; + last_chunk_nverts = chunk_nverts; + + copy_m4_m4(final_offset, current_offset); + + if ((amd->flags & MOD_ARR_MERGE) && + (amd->flags & MOD_ARR_MERGEFINAL) && + (count > 1)) + { + /* Merge first and last copies */ + map_doubles(full_doubles_map, + result_dm_verts, + first_chunk_start, + first_chunk_nverts, + last_chunk_start, + last_chunk_nverts, + amd->merge_dist, + &tot_time_doubles); + } + + /* start capping */ + if (start_cap_dm || end_cap_dm) { + + if (start_cap_dm) { + float start_offset[4][4]; + int start_cap_start = result_nverts - start_cap_nverts - end_cap_nverts; + invert_m4_m4(start_offset, offset); + bm_merge_dm_transform_2(result_dm, start_cap_dm, start_offset, + result_nverts - start_cap_nverts - end_cap_nverts, + result_nedges - start_cap_nedges - end_cap_nedges, + result_nloops - start_cap_nloops - end_cap_nloops, + result_npolys - start_cap_npolys - end_cap_npolys, + start_cap_nverts, start_cap_nedges, start_cap_nloops, start_cap_npolys + ); + /* Identify doubles with first chunk */ + if (amd->flags & MOD_ARR_MERGE) { + map_doubles(full_doubles_map, + result_dm_verts, + first_chunk_start, + first_chunk_nverts, + start_cap_start, + start_cap_nverts, + amd->merge_dist, + &tot_time_doubles); + } + } + + if (end_cap_dm) { + float end_offset[4][4]; + int end_cap_start = result_nverts - end_cap_nverts; + mul_m4_m4m4(end_offset, current_offset, offset); + bm_merge_dm_transform_2(result_dm, end_cap_dm, end_offset, + result_nverts - end_cap_nverts, + result_nedges - end_cap_nedges, + result_nloops - end_cap_nloops, + result_npolys - end_cap_npolys, + end_cap_nverts, end_cap_nedges, end_cap_nloops, end_cap_npolys + ); + /* Identify doubles with last chunk */ + if (amd->flags & MOD_ARR_MERGE) { + map_doubles(full_doubles_map, + result_dm_verts, + last_chunk_start, + last_chunk_nverts, + end_cap_start, + end_cap_nverts, + amd->merge_dist, + &tot_time_doubles); + } + } + } /* Done capping */ + + /* Handle merging */ + tot_doubles = 0; + if (full_doubles_map) { + for (i = 0; i < result_nverts; i++) { + if (full_doubles_map[i]!=-1) + tot_doubles++; + } + + exist_double = false; + for (i = 0; i < result_nverts; i++) { + if (full_doubles_map[i] != -1) { + // MVert *mv = result_dm_verts + i; + exist_double = true; + break; + } + } + if (exist_double) { + double t1, t2; + t1 = PIL_check_seconds_timer(); + result_dm = CDDM_merge_verts(result_dm, full_doubles_map, tot_doubles); + t2 = PIL_check_seconds_timer(); + printf("map_doubles: %f\n", tot_time_doubles); + printf("CDDM_merge: %f\n", (float)(t2 - t1)); + } + MEM_freeN(full_doubles_map); + } + return result_dm; +} + +/* TODO +* - Crash, play with fit-curve, change Old/New... +* - Optimize map_doubles by copying pattern +*/ + +static DerivedMesh *arrayModifier_doArray_1(ArrayModifierData *amd, + Scene *scene, Object *ob, DerivedMesh *dm, + ModifierApplyFlag flag) { DerivedMesh *result; BMesh *bm = DM_to_bmesh(dm, false); @@ -533,14 +1049,14 @@ static DerivedMesh *arrayModifier_doArray(ArrayModifierData *amd, if (start_cap) { float startoffset[4][4]; invert_m4_m4(startoffset, offset); - bm_merge_dm_transform(bm, start_cap, startoffset, amd, + bm_merge_dm_transform_1(bm, start_cap, startoffset, amd, &first_dupe_op, first_dupe_op.slots_in, "geom", &weld_op); } if (end_cap) { float endoffset[4][4]; mul_m4_m4m4(endoffset, offset, final_offset); - bm_merge_dm_transform(bm, end_cap, endoffset, amd, + bm_merge_dm_transform_1(bm, end_cap, endoffset, amd, &dupe_op, (count == 1) ? dupe_op.slots_in : dupe_op.slots_out, (count == 1) ? "geom" : "geom.out", &weld_op); } @@ -587,8 +1103,20 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob, DerivedMesh *result; ArrayModifierData *amd = (ArrayModifierData *) md; - result = arrayModifier_doArray(amd, md->scene, ob, dm, flag); + double t1, t2; + t1 = PIL_check_seconds_timer(); + /* In order to compare old and new versions, old is used for odd counts, new for even counts */ + if (amd->count % 2) { + printf("Old Array Modifier: "); + result = arrayModifier_doArray_1(amd, md->scene, ob, dm, flag); + } + else { + printf("New Array Modifier: "); + result = arrayModifier_doArray_2(amd, md->scene, ob, dm, flag); + } + t2 = PIL_check_seconds_timer(); + printf("%f\n", (float)(t2 - t1)); return result; }