Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/blenkernel/intern/gpencil_geom.c')
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.c1050
1 files changed, 1022 insertions, 28 deletions
diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c
index d200e4e3a15..53bfd3aa8ff 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.c
+++ b/source/blender/blenkernel/intern/gpencil_geom.c
@@ -32,23 +32,36 @@
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
+#include "BLI_ghash.h"
+#include "BLI_hash.h"
#include "BLI_math_vector.h"
#include "BLI_polyfill_2d.h"
+#include "BLT_translation.h"
+
+#include "DNA_gpencil_modifier_types.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"
/* GP Object - Boundbox Support */
/**
- * Get min/max coordinate bounds for single stroke
- * \return Returns whether we found any selected points
+ *Get min/max coordinate bounds for single stroke.
+ * \param gps: Grease pencil stroke
+ * \param use_select: Include only selected points
+ * \param r_min: Result minimum coordinates
+ * \param r_max: Result maximum coordinates
+ * \return True if it was possible to calculate
*/
bool BKE_gpencil_stroke_minmax(const bGPDstroke *gps,
const bool use_select,
@@ -72,7 +85,13 @@ bool BKE_gpencil_stroke_minmax(const bGPDstroke *gps,
return changed;
}
-/* get min/max bounds of all strokes in GP datablock */
+/**
+ * Get min/max bounds of all strokes in grease pencil data-block.
+ * \param gpd: Grease pencil datablock
+ * \param r_min: Result minimum coordinates
+ * \param r_max: Result maximum coordinates
+ * \return True if it was possible to calculate
+ */
bool BKE_gpencil_data_minmax(const bGPdata *gpd, float r_min[3], float r_max[3])
{
bool changed = false;
@@ -88,7 +107,7 @@ bool BKE_gpencil_data_minmax(const bGPdata *gpd, float r_min[3], float r_max[3])
if (gpf != NULL) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
- changed = BKE_gpencil_stroke_minmax(gps, false, r_min, r_max);
+ changed |= BKE_gpencil_stroke_minmax(gps, false, r_min, r_max);
}
}
}
@@ -96,7 +115,11 @@ bool BKE_gpencil_data_minmax(const bGPdata *gpd, float r_min[3], float r_max[3])
return changed;
}
-/* compute center of bounding box */
+/**
+ * Compute center of bounding box.
+ * \param gpd: Grease pencil data-block
+ * \param r_centroid: Location of the center
+ */
void BKE_gpencil_centroid_3d(bGPdata *gpd, float r_centroid[3])
{
float min[3], max[3], tot[3];
@@ -107,14 +130,20 @@ void BKE_gpencil_centroid_3d(bGPdata *gpd, float r_centroid[3])
mul_v3_v3fl(r_centroid, tot, 0.5f);
}
-/* Compute stroke bounding box. */
+/**
+ * Compute stroke bounding box.
+ * \param gps: Grease pencil Stroke
+ */
void BKE_gpencil_stroke_boundingbox_calc(bGPDstroke *gps)
{
INIT_MINMAX(gps->boundbox_min, gps->boundbox_max);
BKE_gpencil_stroke_minmax(gps, false, gps->boundbox_min, gps->boundbox_max);
}
-/* create bounding box values */
+/**
+ * Create bounding box values.
+ * \param ob: Grease pencil object
+ */
static void boundbox_gpencil(Object *ob)
{
BoundBox *bb;
@@ -138,7 +167,11 @@ static void boundbox_gpencil(Object *ob)
bb->flag &= ~BOUNDBOX_DIRTY;
}
-/* get bounding box */
+/**
+ * Get grease pencil object bounding box.
+ * \param ob: Grease pencil object
+ * \return Bounding box
+ */
BoundBox *BKE_gpencil_boundbox_get(Object *ob)
{
if (ELEM(NULL, ob, ob->data)) {
@@ -585,6 +618,14 @@ bool BKE_gpencil_stroke_trim_points(bGPDstroke *gps, const int index_from, const
return true;
}
+/**
+ * Split stroke.
+ * \param gpf: Grease pencil frame
+ * \param gps: Grease pencil original stroke
+ * \param before_index: Position of the point to split
+ * \param remaining_gps: Secondary stroke after split.
+ * \return True if the split was done
+ */
bool BKE_gpencil_stroke_split(bGPDframe *gpf,
bGPDstroke *gps,
const int before_index,
@@ -711,7 +752,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist)
}
/**
- * Apply smooth to stroke point
+ * Apply smooth position to stroke point.
* \param gps: Stroke to smooth
* \param i: Point index
* \param inf: Amount of smoothing to apply
@@ -774,7 +815,11 @@ bool BKE_gpencil_stroke_smooth(bGPDstroke *gps, int i, float inf)
}
/**
- * Apply smooth for strength to stroke point */
+ * Apply smooth strength to stroke point.
+ * \param gps: Stroke to smooth
+ * \param point_index: Point index
+ * \param influence: Amount of smoothing to apply
+ */
bool BKE_gpencil_stroke_smooth_strength(bGPDstroke *gps, int point_index, float influence)
{
bGPDspoint *ptb = &gps->points[point_index];
@@ -834,7 +879,11 @@ bool BKE_gpencil_stroke_smooth_strength(bGPDstroke *gps, int point_index, float
}
/**
- * Apply smooth for thickness to stroke point (use pressure) */
+ * Apply smooth for thickness to stroke point (use pressure).
+ * \param gps: Stroke to smooth
+ * \param point_index: Point index
+ * \param influence: Amount of smoothing to apply
+ */
bool BKE_gpencil_stroke_smooth_thickness(bGPDstroke *gps, int point_index, float influence)
{
bGPDspoint *ptb = &gps->points[point_index];
@@ -894,6 +943,9 @@ bool BKE_gpencil_stroke_smooth_thickness(bGPDstroke *gps, int point_index, float
/**
* Apply smooth for UV rotation to stroke point (use pressure).
+ * \param gps: Stroke to smooth
+ * \param point_index: Point index
+ * \param influence: Amount of smoothing to apply
*/
bool BKE_gpencil_stroke_smooth_uv(bGPDstroke *gps, int point_index, float influence)
{
@@ -931,7 +983,15 @@ bool BKE_gpencil_stroke_smooth_uv(bGPDstroke *gps, int point_index, float influe
return true;
}
-/* Get points of stroke always flat to view not affected by camera view or view position */
+
+/**
+ * Get points of stroke always flat to view not affected
+ * by camera view or view position.
+ * \param points: Array of grease pencil points (3D)
+ * \param totpoints: Total of points
+ * \param points2d: Result array of 2D points
+ * \param r_direction: Return Concave (-1), Convex (1), or Auto-detect (0)
+ */
void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points,
int totpoints,
float (*points2d)[2],
@@ -984,12 +1044,20 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points,
points2d[i][1] = dot_v3v3(loc, locy);
}
- /* Concave (-1), Convex (1), or Autodetect (0)? */
+ /* Concave (-1), Convex (1), or Auto-detect (0)? */
*r_direction = (int)locy[2];
}
-/* Get points of stroke always flat to view not affected by camera view or view position
- * using another stroke as reference
+/**
+ * Get points of stroke always flat to view not affected by camera view or view position
+ * using another stroke as reference.
+ * \param ref_points: Array of reference points (3D)
+ * \param ref_totpoints: Total reference points
+ * \param points: Array of points to flat (3D)
+ * \param totpoints: Total points
+ * \param points2d: Result array of 2D points
+ * \param scale: Scale factor
+ * \param r_direction: Return Concave (-1), Convex (1), or Auto-detect (0)
*/
void BKE_gpencil_stroke_2d_flat_ref(const bGPDspoint *ref_points,
int ref_totpoints,
@@ -1071,15 +1139,15 @@ void BKE_gpencil_stroke_2d_flat_ref(const bGPDspoint *ref_points,
points2d[i][1] = dot_v3v3(loc, locy);
}
- /* Concave (-1), Convex (1), or Autodetect (0)? */
+ /* Concave (-1), Convex (1), or Auto-detect (0)? */
*r_direction = (int)locy[2];
}
-/* calc texture coordinates using flat projected points */
+/* Calc texture coordinates using flat projected points. */
static void gpencil_calc_stroke_fill_uv(const float (*points2d)[2],
bGPDstroke *gps,
const float minv[2],
- float maxv[2],
+ const float maxv[2],
float (*r_uv)[2])
{
const float s = sin(gps->uv_rotation);
@@ -1114,8 +1182,10 @@ static void gpencil_calc_stroke_fill_uv(const float (*points2d)[2],
}
}
-/* Triangulate stroke for high quality fill (this is done only if cache is null or stroke was
- * modified) */
+/**
+ * Triangulate stroke to generate data for filling areas.
+ * \param gps: Grease pencil stroke
+ */
void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps)
{
BLI_assert(gps->totpoints >= 3);
@@ -1174,7 +1244,10 @@ void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps)
MEM_SAFE_FREE(uv);
}
-/* texture coordinate utilities */
+/**
+ * Update Stroke UV data.
+ * \param gps: Grease pencil stroke
+ */
void BKE_gpencil_stroke_uv_update(bGPDstroke *gps)
{
if (gps == NULL || gps->totpoints == 0) {
@@ -1190,7 +1263,10 @@ void BKE_gpencil_stroke_uv_update(bGPDstroke *gps)
}
}
-/* Recalc the internal geometry caches for fill and uvs. */
+/**
+ * Recalc all internal geometry data for the stroke
+ * \param gps: Grease pencil stroke
+ */
void BKE_gpencil_stroke_geometry_update(bGPDstroke *gps)
{
if (gps == NULL) {
@@ -1212,6 +1288,12 @@ void BKE_gpencil_stroke_geometry_update(bGPDstroke *gps)
BKE_gpencil_stroke_boundingbox_calc(gps);
}
+/**
+ * Calculate grease pencil stroke length.
+ * \param gps: Grease pencil stroke
+ * \param use_3d: Set to true to use 3D points
+ * \return Length of the stroke
+ */
float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d)
{
if (!gps->points || gps->totpoints < 2) {
@@ -1235,7 +1317,7 @@ float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d)
}
/**
- * Trim stroke to the first intersection or loop
+ * Trim stroke to the first intersection or loop.
* \param gps: Stroke data
*/
bool BKE_gpencil_stroke_trim(bGPDstroke *gps)
@@ -1244,7 +1326,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++) {
@@ -1331,7 +1414,7 @@ bool BKE_gpencil_stroke_trim(bGPDstroke *gps)
}
/**
- * Close stroke
+ * Close grease pencil stroke.
* \param gps: Stroke to close
*/
bool BKE_gpencil_stroke_close(bGPDstroke *gps)
@@ -1418,7 +1501,13 @@ bool BKE_gpencil_stroke_close(bGPDstroke *gps)
return true;
}
-/* Dissolve points in stroke */
+
+/**
+ * Dissolve points in stroke.
+ * \param gpf: Grease pencil frame
+ * \param gps: Grease pencil stroke
+ * \param tag: Type of tag for point
+ */
void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short tag)
{
bGPDspoint *pt;
@@ -1499,8 +1588,341 @@ void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short ta
}
}
+/**
+ * Calculate stroke normals.
+ * \param gps: Grease pencil stroke
+ * \param r_normal: Return Normal vector normalized
+ */
+void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3])
+{
+ if (gps->totpoints < 3) {
+ zero_v3(r_normal);
+ return;
+ }
+
+ bGPDspoint *points = gps->points;
+ int totpoints = gps->totpoints;
+
+ const bGPDspoint *pt0 = &points[0];
+ const bGPDspoint *pt1 = &points[1];
+ const bGPDspoint *pt3 = &points[(int)(totpoints * 0.75)];
+
+ float vec1[3];
+ float vec2[3];
+
+ /* initial vector (p0 -> p1) */
+ sub_v3_v3v3(vec1, &pt1->x, &pt0->x);
+
+ /* point vector at 3/4 */
+ sub_v3_v3v3(vec2, &pt3->x, &pt0->x);
+
+ /* vector orthogonal to polygon plane */
+ cross_v3_v3v3(r_normal, vec1, vec2);
+
+ /* Normalize vector */
+ normalize_v3(r_normal);
+}
+
+/* Stroke Simplify ------------------------------------- */
+
+/** Reduce a series of points to a simplified version, but
+ * maintains the general shape of the series
+ *
+ * Ramer - Douglas - Peucker algorithm
+ * by http ://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
+ * \param gps: Grease pencil stroke
+ * \param epsilon: Epsilon value to define precision of the algorithm
+ */
+void BKE_gpencil_stroke_simplify_adaptive(bGPDstroke *gps, float epsilon)
+{
+ bGPDspoint *old_points = MEM_dupallocN(gps->points);
+ int totpoints = gps->totpoints;
+ char *marked = NULL;
+ char work;
+
+ int start = 0;
+ int end = gps->totpoints - 1;
+
+ marked = MEM_callocN(totpoints, "GP marked array");
+ marked[start] = 1;
+ marked[end] = 1;
+
+ work = 1;
+ int totmarked = 0;
+ /* while still reducing */
+ while (work) {
+ int ls, le;
+ work = 0;
+
+ ls = start;
+ le = start + 1;
+
+ /* while not over interval */
+ while (ls < end) {
+ int max_i = 0;
+ /* divided to get more control */
+ float max_dist = epsilon / 10.0f;
+
+ /* find the next marked point */
+ while (marked[le] == 0) {
+ le++;
+ }
+
+ for (int i = ls + 1; i < le; i++) {
+ float point_on_line[3];
+ float dist;
+
+ closest_to_line_segment_v3(
+ point_on_line, &old_points[i].x, &old_points[ls].x, &old_points[le].x);
+
+ dist = len_v3v3(point_on_line, &old_points[i].x);
+
+ if (dist > max_dist) {
+ max_dist = dist;
+ max_i = i;
+ }
+ }
+
+ if (max_i != 0) {
+ work = 1;
+ marked[max_i] = 1;
+ totmarked++;
+ }
+
+ ls = le;
+ le = ls + 1;
+ }
+ }
+
+ /* adding points marked */
+ MDeformVert *old_dvert = NULL;
+ MDeformVert *dvert_src = NULL;
+
+ if (gps->dvert != NULL) {
+ old_dvert = MEM_dupallocN(gps->dvert);
+ }
+ /* resize gps */
+ int j = 0;
+ for (int i = 0; i < totpoints; i++) {
+ bGPDspoint *pt_src = &old_points[i];
+ bGPDspoint *pt = &gps->points[j];
+
+ if ((marked[i]) || (i == 0) || (i == totpoints - 1)) {
+ memcpy(pt, pt_src, sizeof(bGPDspoint));
+ if (gps->dvert != NULL) {
+ dvert_src = &old_dvert[i];
+ MDeformVert *dvert = &gps->dvert[j];
+ memcpy(dvert, dvert_src, sizeof(MDeformVert));
+ if (dvert_src->dw) {
+ memcpy(dvert->dw, dvert_src->dw, sizeof(MDeformWeight));
+ }
+ }
+ j++;
+ }
+ else {
+ if (gps->dvert != NULL) {
+ dvert_src = &old_dvert[i];
+ BKE_gpencil_free_point_weights(dvert_src);
+ }
+ }
+ }
+
+ gps->totpoints = j;
+
+ /* Calc geometry data. */
+ BKE_gpencil_stroke_geometry_update(gps);
+
+ MEM_SAFE_FREE(old_points);
+ MEM_SAFE_FREE(old_dvert);
+ MEM_SAFE_FREE(marked);
+}
+
+/**
+ * Simplify alternate vertex of stroke except extremes.
+ * \param gps: Grease pencil stroke
+ */
+void BKE_gpencil_stroke_simplify_fixed(bGPDstroke *gps)
+{
+ if (gps->totpoints < 5) {
+ return;
+ }
+
+ /* save points */
+ bGPDspoint *old_points = MEM_dupallocN(gps->points);
+ MDeformVert *old_dvert = NULL;
+ MDeformVert *dvert_src = NULL;
+
+ if (gps->dvert != NULL) {
+ old_dvert = MEM_dupallocN(gps->dvert);
+ }
+
+ /* resize gps */
+ int newtot = (gps->totpoints - 2) / 2;
+ if (((gps->totpoints - 2) % 2) > 0) {
+ newtot++;
+ }
+ newtot += 2;
+
+ gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
+ if (gps->dvert != NULL) {
+ gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
+ }
+
+ int j = 0;
+ for (int i = 0; i < gps->totpoints; i++) {
+ bGPDspoint *pt_src = &old_points[i];
+ bGPDspoint *pt = &gps->points[j];
+
+ if ((i == 0) || (i == gps->totpoints - 1) || ((i % 2) > 0.0)) {
+ memcpy(pt, pt_src, sizeof(bGPDspoint));
+ if (gps->dvert != NULL) {
+ dvert_src = &old_dvert[i];
+ MDeformVert *dvert = &gps->dvert[j];
+ memcpy(dvert, dvert_src, sizeof(MDeformVert));
+ if (dvert_src->dw) {
+ memcpy(dvert->dw, dvert_src->dw, sizeof(MDeformWeight));
+ }
+ }
+ j++;
+ }
+ else {
+ if (gps->dvert != NULL) {
+ dvert_src = &old_dvert[i];
+ BKE_gpencil_free_point_weights(dvert_src);
+ }
+ }
+ }
+
+ gps->totpoints = j;
+ /* Calc geometry data. */
+ BKE_gpencil_stroke_geometry_update(gps);
+
+ MEM_SAFE_FREE(old_points);
+ MEM_SAFE_FREE(old_dvert);
+}
+
+/**
+ * Subdivide grease pencil stroke.
+ * \param gps: Grease pencil stroke
+ * \param level: Level of subdivision
+ * \param type: Type of subdivision
+ */
+void BKE_gpencil_stroke_subdivide(bGPDstroke *gps, int level, int type)
+{
+ bGPDspoint *temp_points;
+ MDeformVert *temp_dverts = NULL;
+ MDeformVert *dvert = NULL;
+ MDeformVert *dvert_final = NULL;
+ MDeformVert *dvert_next = NULL;
+ int totnewpoints, oldtotpoints;
+ int i2;
+
+ for (int s = 0; s < level; s++) {
+ totnewpoints = gps->totpoints - 1;
+ /* duplicate points in a temp area */
+ temp_points = MEM_dupallocN(gps->points);
+ oldtotpoints = gps->totpoints;
+
+ /* resize the points arrays */
+ gps->totpoints += totnewpoints;
+ gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
+ if (gps->dvert != NULL) {
+ temp_dverts = MEM_dupallocN(gps->dvert);
+ gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
+ }
+
+ /* move points from last to first to new place */
+ i2 = gps->totpoints - 1;
+ for (int i = oldtotpoints - 1; i > 0; i--) {
+ bGPDspoint *pt = &temp_points[i];
+ bGPDspoint *pt_final = &gps->points[i2];
+
+ copy_v3_v3(&pt_final->x, &pt->x);
+ pt_final->pressure = pt->pressure;
+ pt_final->strength = pt->strength;
+ pt_final->time = pt->time;
+ pt_final->flag = pt->flag;
+ pt_final->runtime.pt_orig = pt->runtime.pt_orig;
+ pt_final->runtime.idx_orig = pt->runtime.idx_orig;
+ copy_v4_v4(pt_final->vert_color, pt->vert_color);
+
+ if (gps->dvert != NULL) {
+ dvert = &temp_dverts[i];
+ dvert_final = &gps->dvert[i2];
+ dvert_final->totweight = dvert->totweight;
+ dvert_final->dw = dvert->dw;
+ }
+ i2 -= 2;
+ }
+ /* interpolate mid points */
+ i2 = 1;
+ for (int i = 0; i < oldtotpoints - 1; i++) {
+ bGPDspoint *pt = &temp_points[i];
+ bGPDspoint *next = &temp_points[i + 1];
+ bGPDspoint *pt_final = &gps->points[i2];
+
+ /* add a half way point */
+ interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f);
+ pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f);
+ pt_final->strength = interpf(pt->strength, next->strength, 0.5f);
+ CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f);
+ pt_final->time = interpf(pt->time, next->time, 0.5f);
+ pt_final->runtime.pt_orig = NULL;
+ pt_final->flag = 0;
+ interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f);
+
+ if (gps->dvert != NULL) {
+ dvert = &temp_dverts[i];
+ dvert_next = &temp_dverts[i + 1];
+ dvert_final = &gps->dvert[i2];
+
+ dvert_final->totweight = dvert->totweight;
+ dvert_final->dw = MEM_dupallocN(dvert->dw);
+
+ /* interpolate weight values */
+ for (int d = 0; d < dvert->totweight; d++) {
+ MDeformWeight *dw_a = &dvert->dw[d];
+ if (dvert_next->totweight > d) {
+ MDeformWeight *dw_b = &dvert_next->dw[d];
+ MDeformWeight *dw_final = &dvert_final->dw[d];
+ dw_final->weight = interpf(dw_a->weight, dw_b->weight, 0.5f);
+ }
+ }
+ }
+
+ i2 += 2;
+ }
+
+ MEM_SAFE_FREE(temp_points);
+ MEM_SAFE_FREE(temp_dverts);
+
+ /* move points to smooth stroke (not simple type )*/
+ if (type != GP_SUBDIV_SIMPLE) {
+ /* duplicate points in a temp area with the new subdivide data */
+ temp_points = MEM_dupallocN(gps->points);
+
+ /* extreme points are not changed */
+ for (int i = 0; i < gps->totpoints - 2; i++) {
+ bGPDspoint *pt = &temp_points[i];
+ bGPDspoint *next = &temp_points[i + 1];
+ bGPDspoint *pt_final = &gps->points[i + 1];
+
+ /* move point */
+ interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f);
+ }
+ /* free temp memory */
+ MEM_SAFE_FREE(temp_points);
+ }
+ }
+
+ /* Calc geometry data. */
+ BKE_gpencil_stroke_geometry_update(gps);
+}
+
/* 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,7 +2002,431 @@ void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf,
BKE_gpencil_stroke_geometry_update(gps);
}
-/* Apply Transforms */
+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,
+ int stroke_mat_index,
+ 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, MAX2(stroke_mat_index, 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;
+}
+
+static int gpencil_material_find_index_by_name_prefix(Object *ob, const char *name_prefix)
+{
+ const int name_prefix_len = strlen(name_prefix);
+ for (int i = 0; i < ob->totcol; i++) {
+ Material *ma = BKE_object_material_get(ob, i + 1);
+ if ((ma != NULL) && (ma->gp_style != NULL) &&
+ (STREQLEN(ma->id.name + 2, name_prefix, name_prefix_len))) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+/**
+ * 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.
+ * \simple_material: Create only 2 materials (stroke and fill)
+ */
+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,
+ const bool simple_material)
+{
+ 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;
+ int stroke_mat_index = gpencil_material_find_index_by_name_prefix(ob_gp, "Stroke");
+ int fill_mat_index = gpencil_material_find_index_by_name_prefix(ob_gp, "Fill");
+
+ /* 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) {
+ if (stroke_mat_index == -1) {
+ gpencil_add_material(bmain, ob_gp, "Stroke", default_colors[0], true, false, &r_idx);
+ stroke_mat_index = ob_gp->totcol - 1;
+ }
+ }
+ /* 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 or use simple materials, create a simple fill. */
+ if ((!material_found) || (simple_material)) {
+ if (fill_mat_index == -1) {
+ gpencil_add_material(bmain, ob_gp, "Fill", default_colors[1], false, true, &r_idx);
+ fill_mat_index = ob_gp->totcol - 1;
+ }
+ }
+ 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. */
+ int mat_idx = (simple_material) || (mp->mat_nr + 1 > ob_gp->totcol - 1) ?
+ MAX2(fill_mat_index, 0) :
+ mp->mat_nr + 1;
+
+ bGPDstroke *gps_fill = BKE_gpencil_stroke_add(gpf_fill, mat_idx, 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, stroke_mat_index, angle, thickness, offset, matrix, use_seams);
+
+ /* Tag for recalculation */
+ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
+}
+
+/**
+ * Apply grease pencil Transforms.
+ * \param gpd: Grease pencil data-block
+ * \param mat: Transformation matrix
+ */
void BKE_gpencil_transform(bGPdata *gpd, float mat[4][4])
{
if (gpd == NULL) {
@@ -1613,4 +2459,152 @@ void BKE_gpencil_transform(bGPdata *gpd, float mat[4][4])
}
}
}
+
+/* Used for "move only origins" in object_data_transform.c */
+int BKE_gpencil_stroke_point_count(bGPdata *gpd)
+{
+ int total_points = 0;
+
+ if (gpd == NULL) {
+ return 0;
+ }
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ /* FIXME: For now, we just skip parented layers.
+ * Otherwise, we have to update each frame to find
+ * the current parent position/effects.
+ */
+ if (gpl->parent) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ total_points += gps->totpoints;
+ }
+ }
+ }
+ return total_points;
+}
+
+/* Used for "move only origins" in object_data_transform.c */
+void BKE_gpencil_point_coords_get(bGPdata *gpd, GPencilPointCoordinates *elem_data)
+{
+ if (gpd == NULL) {
+ return;
+ }
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ /* FIXME: For now, we just skip parented layers.
+ * Otherwise, we have to update each frame to find
+ * the current parent position/effects.
+ */
+ if (gpl->parent) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ bGPDspoint *pt;
+ int i;
+
+ for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) {
+ copy_v3_v3(elem_data->co, &pt->x);
+ elem_data->pressure = pt->pressure;
+ elem_data++;
+ }
+ }
+ }
+ }
+}
+
+/* Used for "move only origins" in object_data_transform.c */
+void BKE_gpencil_point_coords_apply(bGPdata *gpd, const GPencilPointCoordinates *elem_data)
+{
+ if (gpd == NULL) {
+ return;
+ }
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ /* FIXME: For now, we just skip parented layers.
+ * Otherwise, we have to update each frame to find
+ * the current parent position/effects.
+ */
+ if (gpl->parent) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ bGPDspoint *pt;
+ int i;
+
+ for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) {
+ copy_v3_v3(&pt->x, elem_data->co);
+ pt->pressure = elem_data->pressure;
+ elem_data++;
+ }
+
+ /* Distortion may mean we need to re-triangulate. */
+ BKE_gpencil_stroke_geometry_update(gps);
+ }
+ }
+ }
+}
+
+/* Used for "move only origins" in object_data_transform.c */
+void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd,
+ const GPencilPointCoordinates *elem_data,
+ const float mat[4][4])
+{
+ if (gpd == NULL) {
+ return;
+ }
+
+ const float scalef = mat4_to_scale(mat);
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ /* FIXME: For now, we just skip parented layers.
+ * Otherwise, we have to update each frame to find
+ * the current parent position/effects.
+ */
+ if (gpl->parent) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ bGPDspoint *pt;
+ int i;
+
+ for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) {
+ mul_v3_m4v3(&pt->x, mat, elem_data->co);
+ pt->pressure = elem_data->pressure * scalef;
+ elem_data++;
+ }
+
+ /* Distortion may mean we need to re-triangulate. */
+ BKE_gpencil_stroke_geometry_update(gps);
+ }
+ }
+ }
+}
+
+/**
+ * Set a random color to stroke using vertex color.
+ * \param gps: Stroke
+ */
+void BKE_gpencil_stroke_set_random_color(bGPDstroke *gps)
+{
+ BLI_assert(gps->totpoints > 0);
+
+ float color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+ bGPDspoint *pt = &gps->points[0];
+ color[0] *= BLI_hash_int_01(BLI_hash_int_2d(gps->totpoints / 5, pt->x + pt->z));
+ color[1] *= BLI_hash_int_01(BLI_hash_int_2d(gps->totpoints + pt->x, pt->y * pt->z + pt->x));
+ color[2] *= BLI_hash_int_01(BLI_hash_int_2d(gps->totpoints - pt->x, pt->z * pt->x + pt->y));
+ for (int i = 0; i < gps->totpoints; i++) {
+ pt = &gps->points[i];
+ copy_v4_v4(pt->vert_color, color);
+ }
+}
/** \} */