diff options
Diffstat (limited to 'source/blender/blenkernel/intern/gpencil_geom.c')
-rw-r--r-- | source/blender/blenkernel/intern/gpencil_geom.c | 406 |
1 files changed, 404 insertions, 2 deletions
diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index d200e4e3a15..542f80aa9b5 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -32,15 +32,22 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_ghash.h" #include "BLI_math_vector.h" #include "BLI_polyfill_2d.h" +#include "BLT_translation.h" + #include "DNA_gpencil_types.h" +#include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" #include "BKE_deform.h" #include "BKE_gpencil.h" #include "BKE_gpencil_geom.h" +#include "BKE_main.h" +#include "BKE_material.h" #include "BKE_object.h" #include "DEG_depsgraph_query.h" @@ -1244,7 +1251,8 @@ bool BKE_gpencil_stroke_trim(bGPDstroke *gps) return false; } bool intersect = false; - int start, end; + int start = 0; + int end = 0; float point[3]; /* loop segments from start until we have an intersection */ for (int i = 0; i < gps->totpoints - 2; i++) { @@ -1500,7 +1508,9 @@ void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short ta } /* Merge by distance ------------------------------------- */ -/* Reduce a series of points when the distance is below a threshold. + +/** + * Reduce a series of points when the distance is below a threshold. * Special case for first and last points (both are keeped) for other points, * the merge point always is at first point. * \param gpf: Grease Pencil frame @@ -1580,6 +1590,398 @@ void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf, BKE_gpencil_stroke_geometry_update(gps); } +typedef struct GpEdge { + uint v1, v2; + /* Coordinates. */ + float v1_co[3], v2_co[3]; + /* Normals. */ + float n1[3], n2[3]; + /* Direction of the segment. */ + float vec[3]; + int flag; +} GpEdge; + +static int gpencil_next_edge( + GpEdge *gp_edges, int totedges, GpEdge *gped_init, const float threshold, const bool reverse) +{ + int edge = -1; + float last_angle = 999999.0f; + for (int i = 0; i < totedges; i++) { + GpEdge *gped = &gp_edges[i]; + if (gped->flag != 0) { + continue; + } + if (reverse) { + if (gped_init->v1 != gped->v2) { + continue; + } + } + else { + if (gped_init->v2 != gped->v1) { + continue; + } + } + /* Look for straight lines. */ + float angle = angle_v3v3(gped->vec, gped_init->vec); + if ((angle < threshold) && (angle <= last_angle)) { + edge = i; + last_angle = angle; + } + } + + return edge; +} + +static int gpencil_walk_edge(GHash *v_table, + GpEdge *gp_edges, + int totedges, + uint *stroke_array, + int init_idx, + const float angle, + const bool reverse) +{ + GpEdge *gped_init = &gp_edges[init_idx]; + int idx = 1; + int edge = 0; + while (edge > -1) { + edge = gpencil_next_edge(gp_edges, totedges, gped_init, angle, reverse); + if (edge > -1) { + GpEdge *gped = &gp_edges[edge]; + stroke_array[idx] = edge; + gped->flag = 1; + gped_init = &gp_edges[edge]; + idx++; + + /* Avoid to follow already visited vertice. */ + if (reverse) { + if (BLI_ghash_haskey(v_table, POINTER_FROM_INT(gped->v1))) { + edge = -1; + } + else { + BLI_ghash_insert(v_table, POINTER_FROM_INT(gped->v1), POINTER_FROM_INT(gped->v1)); + } + } + else { + if (BLI_ghash_haskey(v_table, POINTER_FROM_INT(gped->v2))) { + edge = -1; + } + else { + BLI_ghash_insert(v_table, POINTER_FROM_INT(gped->v2), POINTER_FROM_INT(gped->v2)); + } + } + } + } + + return idx; +} + +static void gpencil_generate_edgeloops(Object *ob, + bGPDframe *gpf_stroke, + const float angle, + const int thickness, + const float offset, + const float matrix[4][4], + const bool use_seams) +{ + Mesh *me = (Mesh *)ob->data; + if (me->totedge == 0) { + return; + } + + /* Arrays for all edge vertices (forward and backward) that form a edge loop. + * This is reused for each edgeloop to create gpencil stroke. */ + uint *stroke = MEM_callocN(sizeof(uint) * me->totedge * 2, __func__); + uint *stroke_fw = MEM_callocN(sizeof(uint) * me->totedge, __func__); + uint *stroke_bw = MEM_callocN(sizeof(uint) * me->totedge, __func__); + + /* Create array with all edges. */ + GpEdge *gp_edges = MEM_callocN(sizeof(GpEdge) * me->totedge, __func__); + GpEdge *gped = NULL; + for (int i = 0; i < me->totedge; i++) { + MEdge *ed = &me->medge[i]; + gped = &gp_edges[i]; + MVert *mv1 = &me->mvert[ed->v1]; + normal_short_to_float_v3(gped->n1, mv1->no); + + gped->v1 = ed->v1; + copy_v3_v3(gped->v1_co, mv1->co); + + MVert *mv2 = &me->mvert[ed->v2]; + normal_short_to_float_v3(gped->n2, mv2->no); + gped->v2 = ed->v2; + copy_v3_v3(gped->v2_co, mv2->co); + + sub_v3_v3v3(gped->vec, mv1->co, mv2->co); + + /* If use seams, mark as done if not a seam. */ + if ((use_seams) && ((ed->flag & ME_SEAM) == 0)) { + gped->flag = 1; + } + } + + /* Loop edges to find edgeloops */ + bool pending = true; + int e = 0; + while (pending) { + /* Clear arrays of stroke. */ + memset(stroke_fw, 0, sizeof(uint) * me->totedge); + memset(stroke_bw, 0, sizeof(uint) * me->totedge); + memset(stroke, 0, sizeof(uint) * me->totedge * 2); + + gped = &gp_edges[e]; + /* Look first unused edge. */ + if (gped->flag != 0) { + e++; + if (e == me->totedge) { + pending = false; + } + continue; + } + /* Add current edge to arrays. */ + stroke_fw[0] = e; + stroke_bw[0] = e; + gped->flag = 1; + + /* Hash used to avoid loop over same vertice. */ + GHash *v_table = BLI_ghash_int_new(__func__); + /* Look forward edges. */ + int totedges = gpencil_walk_edge(v_table, gp_edges, me->totedge, stroke_fw, e, angle, false); + /* Look backward edges. */ + int totbw = gpencil_walk_edge(v_table, gp_edges, me->totedge, stroke_bw, e, angle, true); + + BLI_ghash_free(v_table, NULL, NULL); + + /* Join both arrays. */ + int array_len = 0; + for (int i = totbw - 1; i > 0; i--) { + stroke[array_len] = stroke_bw[i]; + array_len++; + } + for (int i = 0; i < totedges; i++) { + stroke[array_len] = stroke_fw[i]; + array_len++; + } + + /* Create Stroke. */ + bGPDstroke *gps_stroke = BKE_gpencil_stroke_add( + gpf_stroke, 0, array_len + 1, thickness * thickness, false); + + /* Create first segment. */ + float fpt[3]; + uint v = stroke[0]; + gped = &gp_edges[v]; + bGPDspoint *pt = &gps_stroke->points[0]; + mul_v3_v3fl(fpt, gped->n1, offset); + add_v3_v3v3(&pt->x, gped->v1_co, fpt); + mul_m4_v3(matrix, &pt->x); + + pt->pressure = 1.0f; + pt->strength = 1.0f; + + pt = &gps_stroke->points[1]; + mul_v3_v3fl(fpt, gped->n2, offset); + add_v3_v3v3(&pt->x, gped->v2_co, fpt); + mul_m4_v3(matrix, &pt->x); + + pt->pressure = 1.0f; + pt->strength = 1.0f; + + /* Add next segments. */ + for (int i = 1; i < array_len; i++) { + v = stroke[i]; + gped = &gp_edges[v]; + + pt = &gps_stroke->points[i + 1]; + mul_v3_v3fl(fpt, gped->n2, offset); + add_v3_v3v3(&pt->x, gped->v2_co, fpt); + mul_m4_v3(matrix, &pt->x); + + pt->pressure = 1.0f; + pt->strength = 1.0f; + } + + BKE_gpencil_stroke_geometry_update(gps_stroke); + } + + /* Free memory. */ + MEM_SAFE_FREE(stroke); + MEM_SAFE_FREE(stroke_fw); + MEM_SAFE_FREE(stroke_bw); + MEM_SAFE_FREE(gp_edges); +} + +/* Helper: Add gpencil material using material as base. */ +static Material *gpencil_add_material(Main *bmain, + Object *ob_gp, + const char *name, + const float color[4], + const bool use_stroke, + const bool use_fill, + int *r_idx) +{ + Material *mat_gp = BKE_gpencil_object_material_new(bmain, ob_gp, name, r_idx); + MaterialGPencilStyle *gp_style = mat_gp->gp_style; + + /* Stroke color. */ + if (use_stroke) { + ARRAY_SET_ITEMS(gp_style->stroke_rgba, 0.0f, 0.0f, 0.0f, 1.0f); + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + } + else { + linearrgb_to_srgb_v4(gp_style->stroke_rgba, color); + gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW; + } + + /* Fill color. */ + linearrgb_to_srgb_v4(gp_style->fill_rgba, color); + if (use_fill) { + gp_style->flag |= GP_MATERIAL_FILL_SHOW; + } + + /* Check at least one is enabled. */ + if (((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0) && + ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + } + + return mat_gp; +} + +/** + * Convert a mesh object to grease pencil stroke. + * + * \param bmain: Main thread pointer. + * \param depsgraph: Original depsgraph. + * \param scene: Original scene. + * \param ob_gp: Grease pencil object to add strokes. + * \param ob_mesh: Mesh to convert. + * \param angle: Limit angle to consider a edgeloop ends. + * \param thickness: Thickness of the strokes. + * \param offset: Offset along the normals. + * \param matrix: Transformation matrix. + * \param frame_offset: Destination frame number offset. + * \param use_seams: Only export seam edges. + * \param use_faces: Export faces as filled strokes. + */ +void BKE_gpencil_convert_mesh(Main *bmain, + Depsgraph *depsgraph, + Scene *scene, + Object *ob_gp, + Object *ob_mesh, + const float angle, + const int thickness, + const float offset, + const float matrix[4][4], + const int frame_offset, + const bool use_seams, + const bool use_faces) +{ + if (ELEM(NULL, ob_gp, ob_mesh) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == NULL)) { + return; + } + + bGPdata *gpd = (bGPdata *)ob_gp->data; + + /* Use evaluated data to get mesh with all modifiers on top. */ + Object *ob_eval = (Object *)DEG_get_evaluated_object(depsgraph, ob_mesh); + Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval); + MPoly *mp, *mpoly = me_eval->mpoly; + MLoop *mloop = me_eval->mloop; + int mpoly_len = me_eval->totpoly; + int i; + + /* If the object has enough materials means it was created in a previous step. */ + const bool create_mat = ((ob_gp->totcol > 0) && (ob_gp->totcol >= ob_mesh->totcol)) ? false : + true; + + /* Need at least an edge. */ + if (me_eval->totvert < 2) { + return; + } + + int r_idx; + const float default_colors[2][4] = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.7f, 0.7f, 0.7f, 1.0f}}; + /* Create stroke material. */ + if (create_mat) { + gpencil_add_material(bmain, ob_gp, "Stroke", default_colors[0], true, false, &r_idx); + } + /* Export faces as filled strokes. */ + if (use_faces) { + if (create_mat) { + /* Find a material slot with material assigned */ + bool material_found = false; + for (i = 0; i < ob_mesh->totcol; i++) { + Material *ma = BKE_object_material_get(ob_mesh, i + 1); + if (ma != NULL) { + material_found = true; + break; + } + } + + /* If no materials, create a simple fill. */ + if (!material_found) { + gpencil_add_material(bmain, ob_gp, "Fill", default_colors[1], false, true, &r_idx); + } + else { + /* Create all materials for fill. */ + for (i = 0; i < ob_mesh->totcol; i++) { + Material *ma = BKE_object_material_get(ob_mesh, i + 1); + if (ma == NULL) { + continue; + } + float color[4]; + copy_v3_v3(color, &ma->r); + color[3] = 1.0f; + gpencil_add_material(bmain, ob_gp, ma->id.name + 2, color, false, true, &r_idx); + } + } + } + + /* Read all polygons and create fill for each. */ + if (mpoly_len > 0) { + bGPDlayer *gpl_fill = BKE_gpencil_layer_named_get(gpd, DATA_("Fills")); + if (gpl_fill == NULL) { + gpl_fill = BKE_gpencil_layer_addnew(gpd, DATA_("Fills"), true); + } + bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get( + gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); + for (i = 0, mp = mpoly; i < mpoly_len; i++, mp++) { + MLoop *ml = &mloop[mp->loopstart]; + /* Create fill stroke. */ + bGPDstroke *gps_fill = BKE_gpencil_stroke_add( + gpf_fill, mp->mat_nr + 1, mp->totloop, 10, false); + gps_fill->flag |= GP_STROKE_CYCLIC; + + /* Add points to strokes. */ + int j; + for (j = 0; j < mp->totloop; j++, ml++) { + MVert *mv = &me_eval->mvert[ml->v]; + + bGPDspoint *pt = &gps_fill->points[j]; + copy_v3_v3(&pt->x, mv->co); + mul_m4_v3(matrix, &pt->x); + pt->pressure = 1.0f; + pt->strength = 1.0f; + } + + BKE_gpencil_stroke_geometry_update(gps_fill); + } + } + } + + /* Create stroke from edges. */ + bGPDlayer *gpl_stroke = BKE_gpencil_layer_named_get(gpd, DATA_("Lines")); + if (gpl_stroke == NULL) { + gpl_stroke = BKE_gpencil_layer_addnew(gpd, DATA_("Lines"), true); + } + bGPDframe *gpf_stroke = BKE_gpencil_layer_frame_get( + gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); + gpencil_generate_edgeloops(ob_eval, gpf_stroke, angle, thickness, offset, matrix, use_seams); + + /* Tag for recalculation */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); +} + /* Apply Transforms */ void BKE_gpencil_transform(bGPdata *gpd, float mat[4][4]) { |