@@ -13,12 +13,7 @@
|
||||
#include "BLI_bounds_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
struct Depsgraph;
|
||||
struct Main;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
struct bGPDcurve;
|
||||
struct bGPDlayer;
|
||||
struct bGPDframe;
|
||||
struct bGPDspoint;
|
||||
struct bGPDstroke;
|
||||
@@ -61,19 +56,6 @@ void BKE_gpencil_stroke_boundingbox_calc(struct bGPDstroke *gps);
|
||||
|
||||
/* Stroke geometry utilities. */
|
||||
|
||||
/**
|
||||
* Calculate stroke normals.
|
||||
* \param gps: Grease pencil stroke
|
||||
* \param r_normal: Return Normal vector normalized
|
||||
*/
|
||||
void BKE_gpencil_stroke_normal(const struct bGPDstroke *gps, float r_normal[3]);
|
||||
|
||||
/**
|
||||
* Trim stroke to the first intersection or loop.
|
||||
* \param gps: Stroke data
|
||||
*/
|
||||
bool BKE_gpencil_stroke_trim(struct bGPdata *gpd, struct bGPDstroke *gps);
|
||||
|
||||
/**
|
||||
* Get points of stroke always flat to view not affected
|
||||
* by camera view or view position.
|
||||
@@ -86,24 +68,6 @@ void BKE_gpencil_stroke_2d_flat(const struct bGPDspoint *points,
|
||||
int totpoints,
|
||||
float (*points2d)[2],
|
||||
int *r_direction);
|
||||
/**
|
||||
* 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 struct bGPDspoint *ref_points,
|
||||
int ref_totpoints,
|
||||
const struct bGPDspoint *points,
|
||||
int totpoints,
|
||||
float (*points2d)[2],
|
||||
float scale,
|
||||
int *r_direction);
|
||||
/**
|
||||
* Triangulate stroke to generate data for filling areas.
|
||||
* \param gps: Grease pencil stroke
|
||||
@@ -154,89 +118,6 @@ void BKE_gpencil_point_coords_apply_with_mat4(struct bGPdata *gpd,
|
||||
const GPencilPointCoordinates *elem_data,
|
||||
const float mat[4][4]);
|
||||
|
||||
/**
|
||||
* Apply smooth position to stroke point.
|
||||
* \param gps: Stroke to smooth
|
||||
* \param i: Point index
|
||||
* \param inf: Amount of smoothing to apply
|
||||
* \param iterations: Radius of points to consider, equivalent to iterations
|
||||
* \param smooth_caps: Apply smooth to stroke extremes
|
||||
* \param keep_shape: Smooth out fine details first
|
||||
* \param r_gps: Stroke to put the result into
|
||||
*/
|
||||
bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps,
|
||||
int point_index,
|
||||
float influence,
|
||||
int iterations,
|
||||
bool smooth_caps,
|
||||
bool keep_shape,
|
||||
struct bGPDstroke *r_gps);
|
||||
/**
|
||||
* Apply smooth strength to stroke point.
|
||||
* \param gps: Stroke to smooth
|
||||
* \param point_index: Point index
|
||||
* \param influence: Amount of smoothing to apply
|
||||
* \param iterations: Radius of points to consider, equivalent to iterations
|
||||
* \param r_gps: Stroke to put the result into
|
||||
*/
|
||||
bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps,
|
||||
int point_index,
|
||||
float influence,
|
||||
int iterations,
|
||||
struct bGPDstroke *r_gps);
|
||||
/**
|
||||
* 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
|
||||
* \param iterations: Radius of points to consider, equivalent to iterations
|
||||
* \param r_gps: Stroke to put the result into
|
||||
*/
|
||||
bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps,
|
||||
int point_index,
|
||||
float influence,
|
||||
int iterations,
|
||||
struct bGPDstroke *r_gps);
|
||||
/**
|
||||
* Apply smooth for UV rotation/factor to stroke point.
|
||||
* \param gps: Stroke to smooth
|
||||
* \param point_index: Point index
|
||||
* \param influence: Amount of smoothing to apply
|
||||
* \param iterations: Radius of points to consider, equivalent to iterations
|
||||
* \param r_gps: Stroke to put the result into
|
||||
*/
|
||||
bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps,
|
||||
int point_index,
|
||||
float influence,
|
||||
int iterations,
|
||||
struct bGPDstroke *r_gps);
|
||||
/**
|
||||
* Apply smooth operation to the stroke.
|
||||
* \param gps: Stroke to smooth
|
||||
* \param influence: The interpolation factor for the smooth and the original stroke
|
||||
* \param iterations: Radius of points to consider, equivalent to iterations
|
||||
* \param smooth_position: Smooth point locations
|
||||
* \param smooth_strength: Smooth point strength
|
||||
* \param smooth_thickness: Smooth point thickness
|
||||
* \param smooth_uv: Smooth uv rotation/factor
|
||||
* \param keep_shape: Use different distribution for smooth locations to keep the shape
|
||||
* \param weights: per point weights to multiply influence with (optional, can be null)
|
||||
*/
|
||||
void BKE_gpencil_stroke_smooth(struct bGPDstroke *gps,
|
||||
const float influence,
|
||||
const int iterations,
|
||||
const bool smooth_position,
|
||||
const bool smooth_strength,
|
||||
const bool smooth_thickness,
|
||||
const bool smooth_uv,
|
||||
const bool keep_shape,
|
||||
const float *weights);
|
||||
/**
|
||||
* Close grease pencil stroke.
|
||||
* \param gps: Stroke to close
|
||||
*/
|
||||
bool BKE_gpencil_stroke_close(struct bGPDstroke *gps);
|
||||
|
||||
/**
|
||||
* Split the given stroke into several new strokes, partitioning
|
||||
* it based on whether the stroke points have a particular flag
|
||||
@@ -250,65 +131,3 @@ struct bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(struct bGPdata *gpd,
|
||||
bool select,
|
||||
bool flat_cap,
|
||||
int limit);
|
||||
|
||||
/**
|
||||
* Flip stroke.
|
||||
*/
|
||||
void BKE_gpencil_stroke_flip(struct bGPDstroke *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 struct bGPDstroke *gps, bool use_3d);
|
||||
/** Calculate grease pencil stroke length between points. */
|
||||
float BKE_gpencil_stroke_segment_length(const struct bGPDstroke *gps,
|
||||
int start_index,
|
||||
int end_index,
|
||||
bool use_3d);
|
||||
|
||||
/**
|
||||
* Set a random color to stroke using vertex color.
|
||||
* \param gps: Stroke
|
||||
*/
|
||||
void BKE_gpencil_stroke_set_random_color(struct bGPDstroke *gps);
|
||||
|
||||
/**
|
||||
* Join two strokes using the shortest distance (reorder stroke if necessary).
|
||||
* \param auto_flip: Flip the stroke if the join between two strokes is not end->start points.
|
||||
*/
|
||||
void BKE_gpencil_stroke_join(struct bGPDstroke *gps_a,
|
||||
struct bGPDstroke *gps_b,
|
||||
bool leave_gaps,
|
||||
bool fit_thickness,
|
||||
bool smooth,
|
||||
bool auto_flip);
|
||||
|
||||
/**
|
||||
* Stroke to view space
|
||||
* Transforms a stroke to view space.
|
||||
* This allows for manipulations in 2D but also easy conversion back to 3D.
|
||||
* \note also takes care of parent space transform.
|
||||
*/
|
||||
void BKE_gpencil_stroke_to_view_space(struct bGPDstroke *gps,
|
||||
float viewmat[4][4],
|
||||
const float diff_mat[4][4]);
|
||||
/**
|
||||
* Stroke from view space
|
||||
* Transforms a stroke from view space back to world space.
|
||||
* Inverse of #BKE_gpencil_stroke_to_view_space
|
||||
* \note also takes care of parent space transform.
|
||||
*/
|
||||
void BKE_gpencil_stroke_from_view_space(struct bGPDstroke *gps,
|
||||
float viewinv[4][4],
|
||||
const float diff_mat[4][4]);
|
||||
/**
|
||||
* Get average pressure.
|
||||
*/
|
||||
float BKE_gpencil_stroke_average_pressure_get(struct bGPDstroke *gps);
|
||||
/**
|
||||
* Check if the thickness of the stroke is constant.
|
||||
*/
|
||||
bool BKE_gpencil_stroke_is_pressure_constant(struct bGPDstroke *gps);
|
||||
|
||||
@@ -126,365 +126,6 @@ void BKE_gpencil_stroke_boundingbox_calc(bGPDstroke *gps)
|
||||
/** \name Stroke Smooth Positions
|
||||
* \{ */
|
||||
|
||||
bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
|
||||
int point_index,
|
||||
float influence,
|
||||
int iterations,
|
||||
const bool smooth_caps,
|
||||
const bool keep_shape,
|
||||
bGPDstroke *r_gps)
|
||||
{
|
||||
/* If nothing to do, return early */
|
||||
if (gps->totpoints <= 2 || iterations <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* - Overview of the algorithm here and in the following smooth functions:
|
||||
*
|
||||
* The smooth functions return the new attribute in question for a single point.
|
||||
* The result is stored in r_gps->points[point_index], while the data is read from gps.
|
||||
* To get a correct result, duplicate the stroke point data and read from the copy,
|
||||
* while writing to the real stroke. Not doing that will result in acceptable, but
|
||||
* asymmetric results.
|
||||
*
|
||||
* This algorithm works as long as all points are being smoothed. If there is
|
||||
* points that should not get smoothed, use the old repeat smooth pattern with
|
||||
* the parameter "iterations" set to 1 or 2. (2 matches the old algorithm).
|
||||
*/
|
||||
|
||||
const bGPDspoint *pt = &gps->points[point_index];
|
||||
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
|
||||
/* If smooth_caps is false, the caps will not be translated by smoothing. */
|
||||
if (!smooth_caps && !is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) {
|
||||
copy_v3_v3(&r_gps->points[point_index].x, &pt->x);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* This function uses a binomial kernel, which is the discrete version of gaussian blur.
|
||||
* The weight for a vertex at the relative index point_index is
|
||||
* `w = nCr(n, j + n/2) / 2^n = (n/1 * (n-1)/2 * ... * (n-j-n/2)/(j+n/2)) / 2^n`
|
||||
* All weights together sum up to 1
|
||||
* This is equivalent to doing multiple iterations of averaging neighbors,
|
||||
* where n = iterations * 2 and -n/2 <= j <= n/2
|
||||
*
|
||||
* Now the problem is that `nCr(n, j + n/2)` is very hard to compute for `n > 500`, since even
|
||||
* double precision isn't sufficient. A very good robust approximation for n > 20 is
|
||||
* `nCr(n, j + n/2) / 2^n = sqrt(2/(pi*n)) * exp(-2*j*j/n)`
|
||||
*
|
||||
* There is one more problem left: The old smooth algorithm was doing a more aggressive
|
||||
* smooth. To solve that problem, choose a different n/2, which does not match the range and
|
||||
* normalize the weights on finish. This may cause some artifacts at low values.
|
||||
*
|
||||
* keep_shape is a new option to stop the stroke from severely deforming.
|
||||
* It uses different partially negative weights.
|
||||
* w = `2 * (nCr(n, j + n/2) / 2^n) - (nCr(3*n, j + n) / 2^(3*n))`
|
||||
* ~ `2 * sqrt(2/(pi*n)) * exp(-2*j*j/n) - sqrt(2/(pi*3*n)) * exp(-2*j*j/(3*n))`
|
||||
* All weights still sum up to 1.
|
||||
* Note these weights only work because the averaging is done in relative coordinates.
|
||||
*/
|
||||
float sco[3] = {0.0f, 0.0f, 0.0f};
|
||||
float tmp[3];
|
||||
const int n_half = keep_shape ? (iterations * iterations) / 8 + iterations :
|
||||
(iterations * iterations) / 4 + 2 * iterations + 12;
|
||||
double w = keep_shape ? 2.0 : 1.0;
|
||||
double w2 = keep_shape ?
|
||||
(1.0 / M_SQRT3) * exp((2 * iterations * iterations) / double(n_half * 3)) :
|
||||
0.0;
|
||||
double total_w = 0.0;
|
||||
for (int step = iterations; step > 0; step--) {
|
||||
int before = point_index - step;
|
||||
int after = point_index + step;
|
||||
float w_before = float(w - w2);
|
||||
float w_after = float(w - w2);
|
||||
|
||||
if (is_cyclic) {
|
||||
before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
|
||||
after = after % gps->totpoints;
|
||||
}
|
||||
else {
|
||||
if (before < 0) {
|
||||
if (!smooth_caps) {
|
||||
w_before *= -before / float(point_index);
|
||||
}
|
||||
before = 0;
|
||||
}
|
||||
if (after > gps->totpoints - 1) {
|
||||
if (!smooth_caps) {
|
||||
w_after *= (after - (gps->totpoints - 1)) / float(gps->totpoints - 1 - point_index);
|
||||
}
|
||||
after = gps->totpoints - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add both these points in relative coordinates to the weighted average sum. */
|
||||
sub_v3_v3v3(tmp, &gps->points[before].x, &pt->x);
|
||||
madd_v3_v3fl(sco, tmp, w_before);
|
||||
sub_v3_v3v3(tmp, &gps->points[after].x, &pt->x);
|
||||
madd_v3_v3fl(sco, tmp, w_after);
|
||||
|
||||
total_w += w_before;
|
||||
total_w += w_after;
|
||||
|
||||
w *= (n_half + step) / double(n_half + 1 - step);
|
||||
w2 *= (n_half * 3 + step) / double(n_half * 3 + 1 - step);
|
||||
}
|
||||
total_w += w - w2;
|
||||
/* The accumulated weight total_w should be
|
||||
* `~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100`
|
||||
* here, but sometimes not quite. */
|
||||
mul_v3_fl(sco, float(1.0 / total_w));
|
||||
/* Shift back to global coordinates. */
|
||||
add_v3_v3(sco, &pt->x);
|
||||
|
||||
/* Based on influence factor, blend between original and optimal smoothed coordinate. */
|
||||
interp_v3_v3v3(&r_gps->points[point_index].x, &pt->x, sco, influence);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Stroke Smooth Strength
|
||||
* \{ */
|
||||
|
||||
bool BKE_gpencil_stroke_smooth_strength(
|
||||
bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
|
||||
{
|
||||
/* If nothing to do, return early */
|
||||
if (gps->totpoints <= 2 || iterations <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
|
||||
|
||||
const bGPDspoint *pt = &gps->points[point_index];
|
||||
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
|
||||
float strength = 0.0f;
|
||||
const int n_half = (iterations * iterations) / 4 + iterations;
|
||||
double w = 1.0;
|
||||
double total_w = 0.0;
|
||||
for (int step = iterations; step > 0; step--) {
|
||||
int before = point_index - step;
|
||||
int after = point_index + step;
|
||||
float w_before = float(w);
|
||||
float w_after = float(w);
|
||||
|
||||
if (is_cyclic) {
|
||||
before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
|
||||
after = after % gps->totpoints;
|
||||
}
|
||||
else {
|
||||
CLAMP_MIN(before, 0);
|
||||
CLAMP_MAX(after, gps->totpoints - 1);
|
||||
}
|
||||
|
||||
/* Add both these points in relative coordinates to the weighted average sum. */
|
||||
strength += w_before * (gps->points[before].strength - pt->strength);
|
||||
strength += w_after * (gps->points[after].strength - pt->strength);
|
||||
|
||||
total_w += w_before;
|
||||
total_w += w_after;
|
||||
|
||||
w *= (n_half + step) / double(n_half + 1 - step);
|
||||
}
|
||||
total_w += w;
|
||||
/* The accumulated weight total_w should be
|
||||
* ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
|
||||
* here, but sometimes not quite. */
|
||||
strength /= total_w;
|
||||
|
||||
/* Based on influence factor, blend between original and optimal smoothed value. */
|
||||
r_gps->points[point_index].strength = pt->strength + strength * influence;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Stroke Smooth Thickness
|
||||
* \{ */
|
||||
|
||||
bool BKE_gpencil_stroke_smooth_thickness(
|
||||
bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
|
||||
{
|
||||
/* If nothing to do, return early */
|
||||
if (gps->totpoints <= 2 || iterations <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
|
||||
|
||||
const bGPDspoint *pt = &gps->points[point_index];
|
||||
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
|
||||
float pressure = 0.0f;
|
||||
const int n_half = (iterations * iterations) / 4 + iterations;
|
||||
double w = 1.0;
|
||||
double total_w = 0.0;
|
||||
for (int step = iterations; step > 0; step--) {
|
||||
int before = point_index - step;
|
||||
int after = point_index + step;
|
||||
float w_before = float(w);
|
||||
float w_after = float(w);
|
||||
|
||||
if (is_cyclic) {
|
||||
before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
|
||||
after = after % gps->totpoints;
|
||||
}
|
||||
else {
|
||||
CLAMP_MIN(before, 0);
|
||||
CLAMP_MAX(after, gps->totpoints - 1);
|
||||
}
|
||||
|
||||
/* Add both these points in relative coordinates to the weighted average sum. */
|
||||
pressure += w_before * (gps->points[before].pressure - pt->pressure);
|
||||
pressure += w_after * (gps->points[after].pressure - pt->pressure);
|
||||
|
||||
total_w += w_before;
|
||||
total_w += w_after;
|
||||
|
||||
w *= (n_half + step) / double(n_half + 1 - step);
|
||||
}
|
||||
total_w += w;
|
||||
/* The accumulated weight total_w should be
|
||||
* ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
|
||||
* here, but sometimes not quite. */
|
||||
pressure /= total_w;
|
||||
|
||||
/* Based on influence factor, blend between original and optimal smoothed value. */
|
||||
r_gps->points[point_index].pressure = pt->pressure + pressure * influence;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Stroke Smooth UV
|
||||
* \{ */
|
||||
|
||||
bool BKE_gpencil_stroke_smooth_uv(
|
||||
bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
|
||||
{
|
||||
/* If nothing to do, return early */
|
||||
if (gps->totpoints <= 2 || iterations <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
|
||||
|
||||
const bGPDspoint *pt = &gps->points[point_index];
|
||||
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
|
||||
|
||||
/* If don't change the caps. */
|
||||
if (!is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) {
|
||||
r_gps->points[point_index].uv_rot = pt->uv_rot;
|
||||
r_gps->points[point_index].uv_fac = pt->uv_fac;
|
||||
return true;
|
||||
}
|
||||
|
||||
float uv_rot = 0.0f;
|
||||
float uv_fac = 0.0f;
|
||||
const int n_half = iterations * iterations + iterations;
|
||||
double w = 1.0;
|
||||
double total_w = 0.0;
|
||||
for (int step = iterations; step > 0; step--) {
|
||||
int before = point_index - step;
|
||||
int after = point_index + step;
|
||||
float w_before = float(w);
|
||||
float w_after = float(w);
|
||||
|
||||
if (is_cyclic) {
|
||||
before = (before % gps->totpoints + gps->totpoints) % gps->totpoints;
|
||||
after = after % gps->totpoints;
|
||||
}
|
||||
else {
|
||||
if (before < 0) {
|
||||
w_before *= -before / float(point_index);
|
||||
before = 0;
|
||||
}
|
||||
if (after > gps->totpoints - 1) {
|
||||
w_after *= (after - (gps->totpoints - 1)) / float(gps->totpoints - 1 - point_index);
|
||||
after = gps->totpoints - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add both these points in relative coordinates to the weighted average sum. */
|
||||
uv_rot += w_before * (gps->points[before].uv_rot - pt->uv_rot);
|
||||
uv_rot += w_after * (gps->points[after].uv_rot - pt->uv_rot);
|
||||
uv_fac += w_before * (gps->points[before].uv_fac - pt->uv_fac);
|
||||
uv_fac += w_after * (gps->points[after].uv_fac - pt->uv_fac);
|
||||
|
||||
total_w += w_before;
|
||||
total_w += w_after;
|
||||
|
||||
w *= (n_half + step) / double(n_half + 1 - step);
|
||||
}
|
||||
total_w += w;
|
||||
/* The accumulated weight total_w should be
|
||||
* ~sqrt(M_PI * n_half) * exp((iterations * iterations) / n_half) < 100
|
||||
* here, but sometimes not quite. */
|
||||
uv_rot /= total_w;
|
||||
uv_fac /= total_w;
|
||||
|
||||
/* Based on influence factor, blend between original and optimal smoothed value. */
|
||||
r_gps->points[point_index].uv_rot = pt->uv_rot + uv_rot * influence;
|
||||
r_gps->points[point_index].uv_fac = pt->uv_fac + uv_fac * influence;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BKE_gpencil_stroke_smooth(bGPDstroke *gps,
|
||||
const float influence,
|
||||
const int iterations,
|
||||
const bool smooth_position,
|
||||
const bool smooth_strength,
|
||||
const bool smooth_thickness,
|
||||
const bool smooth_uv,
|
||||
const bool keep_shape,
|
||||
const float *weights)
|
||||
{
|
||||
if (influence <= 0 || iterations <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make a copy of the point data to avoid directionality of the smooth operation. */
|
||||
bGPDstroke gps_old = blender::dna::shallow_copy(*gps);
|
||||
gps_old.points = (bGPDspoint *)MEM_dupallocN(gps->points);
|
||||
|
||||
/* Smooth stroke. */
|
||||
for (int i = 0; i < gps->totpoints; i++) {
|
||||
float val = influence;
|
||||
if (weights != nullptr) {
|
||||
val *= weights[i];
|
||||
if (val <= 0.0f) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Currently the weights only control the influence, but is would be much better if they
|
||||
* would control the distribution used in smooth, similar to how the ends are handled. */
|
||||
|
||||
/* Perform smoothing. */
|
||||
if (smooth_position) {
|
||||
BKE_gpencil_stroke_smooth_point(&gps_old, i, val, iterations, false, keep_shape, gps);
|
||||
}
|
||||
if (smooth_strength) {
|
||||
BKE_gpencil_stroke_smooth_strength(&gps_old, i, val, iterations, gps);
|
||||
}
|
||||
if (smooth_thickness) {
|
||||
BKE_gpencil_stroke_smooth_thickness(&gps_old, i, val, iterations, gps);
|
||||
}
|
||||
if (smooth_uv) {
|
||||
BKE_gpencil_stroke_smooth_uv(&gps_old, i, val, iterations, gps);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the copied points array. */
|
||||
MEM_freeN(gps_old.points);
|
||||
}
|
||||
|
||||
void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points,
|
||||
int totpoints,
|
||||
float (*points2d)[2],
|
||||
@@ -559,90 +200,6 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points,
|
||||
*r_direction = (cross >= 0.0f) ? 1 : -1;
|
||||
}
|
||||
|
||||
void BKE_gpencil_stroke_2d_flat_ref(const bGPDspoint *ref_points,
|
||||
int ref_totpoints,
|
||||
const bGPDspoint *points,
|
||||
int totpoints,
|
||||
float (*points2d)[2],
|
||||
const float scale,
|
||||
int *r_direction)
|
||||
{
|
||||
BLI_assert(totpoints >= 2);
|
||||
|
||||
const bGPDspoint *pt0 = &ref_points[0];
|
||||
const bGPDspoint *pt1 = &ref_points[1];
|
||||
const bGPDspoint *pt3 = &ref_points[int(ref_totpoints * 0.75)];
|
||||
|
||||
float locx[3];
|
||||
float locy[3];
|
||||
float loc3[3];
|
||||
float normal[3];
|
||||
|
||||
/* local X axis (p0 -> p1) */
|
||||
sub_v3_v3v3(locx, &pt1->x, &pt0->x);
|
||||
|
||||
/* point vector at 3/4 */
|
||||
float v3[3];
|
||||
if (totpoints == 2) {
|
||||
mul_v3_v3fl(v3, &pt3->x, 0.001f);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(v3, &pt3->x);
|
||||
}
|
||||
|
||||
sub_v3_v3v3(loc3, v3, &pt0->x);
|
||||
|
||||
/* vector orthogonal to polygon plane */
|
||||
cross_v3_v3v3(normal, locx, loc3);
|
||||
|
||||
/* local Y axis (cross to normal/x axis) */
|
||||
cross_v3_v3v3(locy, normal, locx);
|
||||
|
||||
/* Normalize vectors */
|
||||
normalize_v3(locx);
|
||||
normalize_v3(locy);
|
||||
|
||||
/* Get all points in local space */
|
||||
for (int i = 0; i < totpoints; i++) {
|
||||
const bGPDspoint *pt = &points[i];
|
||||
float loc[3];
|
||||
float v1[3];
|
||||
float vn[3] = {0.0f, 0.0f, 0.0f};
|
||||
|
||||
/* apply scale to extremes of the stroke to get better collision detection
|
||||
* the scale is divided to get more control in the UI parameter
|
||||
*/
|
||||
/* first point */
|
||||
if (i == 0) {
|
||||
const bGPDspoint *pt_next = &points[i + 1];
|
||||
sub_v3_v3v3(vn, &pt->x, &pt_next->x);
|
||||
normalize_v3(vn);
|
||||
mul_v3_fl(vn, scale / 10.0f);
|
||||
add_v3_v3v3(v1, &pt->x, vn);
|
||||
}
|
||||
/* last point */
|
||||
else if (i == totpoints - 1) {
|
||||
const bGPDspoint *pt_prev = &points[i - 1];
|
||||
sub_v3_v3v3(vn, &pt->x, &pt_prev->x);
|
||||
normalize_v3(vn);
|
||||
mul_v3_fl(vn, scale / 10.0f);
|
||||
add_v3_v3v3(v1, &pt->x, vn);
|
||||
}
|
||||
else {
|
||||
copy_v3_v3(v1, &pt->x);
|
||||
}
|
||||
|
||||
/* Get local space using first point as origin (ref stroke) */
|
||||
sub_v3_v3v3(loc, v1, &pt0->x);
|
||||
|
||||
points2d[i][0] = dot_v3v3(loc, locx);
|
||||
points2d[i][1] = dot_v3v3(loc, locy);
|
||||
}
|
||||
|
||||
/* Concave (-1), Convex (1), or Auto-detect (0)? */
|
||||
*r_direction = int(locy[2]);
|
||||
}
|
||||
|
||||
/* Calc texture coordinates using flat projected points. */
|
||||
static void gpencil_calc_stroke_fill_uv(const float (*points2d)[2],
|
||||
bGPDstroke *gps,
|
||||
@@ -783,272 +340,6 @@ void BKE_gpencil_stroke_geometry_update(bGPdata * /*gpd*/, bGPDstroke *gps)
|
||||
BKE_gpencil_stroke_boundingbox_calc(gps);
|
||||
}
|
||||
|
||||
float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d)
|
||||
{
|
||||
if (!gps->points || gps->totpoints < 2) {
|
||||
return 0.0f;
|
||||
}
|
||||
float *last_pt = &gps->points[0].x;
|
||||
float total_length = 0.0f;
|
||||
for (int i = 1; i < gps->totpoints; i++) {
|
||||
bGPDspoint *pt = &gps->points[i];
|
||||
if (use_3d) {
|
||||
total_length += len_v3v3(&pt->x, last_pt);
|
||||
}
|
||||
else {
|
||||
total_length += len_v2v2(&pt->x, last_pt);
|
||||
}
|
||||
last_pt = &pt->x;
|
||||
}
|
||||
return total_length;
|
||||
}
|
||||
|
||||
float BKE_gpencil_stroke_segment_length(const bGPDstroke *gps,
|
||||
const int start_index,
|
||||
const int end_index,
|
||||
bool use_3d)
|
||||
{
|
||||
if (!gps->points || gps->totpoints < 2 || end_index <= start_index) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
int index = std::max(start_index, 0) + 1;
|
||||
int last_index = std::min(end_index, gps->totpoints - 1) + 1;
|
||||
|
||||
float *last_pt = &gps->points[index - 1].x;
|
||||
float total_length = 0.0f;
|
||||
for (int i = index; i < last_index; i++) {
|
||||
bGPDspoint *pt = &gps->points[i];
|
||||
if (use_3d) {
|
||||
total_length += len_v3v3(&pt->x, last_pt);
|
||||
}
|
||||
else {
|
||||
total_length += len_v2v2(&pt->x, last_pt);
|
||||
}
|
||||
last_pt = &pt->x;
|
||||
}
|
||||
return total_length;
|
||||
}
|
||||
|
||||
bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps)
|
||||
{
|
||||
if (gps->totpoints < 4) {
|
||||
return false;
|
||||
}
|
||||
bool intersect = false;
|
||||
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++) {
|
||||
start = i;
|
||||
bGPDspoint *a = &gps->points[start];
|
||||
bGPDspoint *b = &gps->points[start + 1];
|
||||
for (int j = start + 2; j < gps->totpoints - 1; j++) {
|
||||
end = j + 1;
|
||||
bGPDspoint *c = &gps->points[j];
|
||||
bGPDspoint *d = &gps->points[end];
|
||||
float pointb[3];
|
||||
/* get intersection */
|
||||
if (isect_line_line_v3(&a->x, &b->x, &c->x, &d->x, point, pointb)) {
|
||||
if (len_v3(point) > 0.0f) {
|
||||
float closest[3];
|
||||
/* check intersection is on both lines */
|
||||
float lambda = closest_to_line_v3(closest, point, &a->x, &b->x);
|
||||
if ((lambda <= 0.0f) || (lambda >= 1.0f)) {
|
||||
continue;
|
||||
}
|
||||
lambda = closest_to_line_v3(closest, point, &c->x, &d->x);
|
||||
if ((lambda <= 0.0f) || (lambda >= 1.0f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
intersect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intersect) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* trim unwanted points */
|
||||
if (intersect) {
|
||||
|
||||
/* save points */
|
||||
bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points);
|
||||
MDeformVert *old_dvert = nullptr;
|
||||
MDeformVert *dvert_src = nullptr;
|
||||
|
||||
if (gps->dvert != nullptr) {
|
||||
old_dvert = (MDeformVert *)MEM_dupallocN(gps->dvert);
|
||||
}
|
||||
|
||||
/* resize gps */
|
||||
int newtot = end - start + 1;
|
||||
|
||||
gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
|
||||
if (gps->dvert != nullptr) {
|
||||
gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
|
||||
}
|
||||
|
||||
for (int i = 0; i < newtot; i++) {
|
||||
int idx = start + i;
|
||||
bGPDspoint *pt_src = &old_points[idx];
|
||||
bGPDspoint *pt_new = &gps->points[i];
|
||||
*pt_new = blender::dna::shallow_copy(*pt_src);
|
||||
if (gps->dvert != nullptr) {
|
||||
dvert_src = &old_dvert[idx];
|
||||
MDeformVert *dvert = &gps->dvert[i];
|
||||
memcpy(dvert, dvert_src, sizeof(MDeformVert));
|
||||
if (dvert_src->dw) {
|
||||
memcpy(dvert->dw, dvert_src->dw, sizeof(MDeformWeight));
|
||||
}
|
||||
}
|
||||
if (ELEM(idx, start, end)) {
|
||||
copy_v3_v3(&pt_new->x, point);
|
||||
}
|
||||
}
|
||||
|
||||
gps->totpoints = newtot;
|
||||
|
||||
MEM_SAFE_FREE(old_points);
|
||||
MEM_SAFE_FREE(old_dvert);
|
||||
}
|
||||
|
||||
BKE_gpencil_stroke_geometry_update(gpd, gps);
|
||||
|
||||
return intersect;
|
||||
}
|
||||
|
||||
bool BKE_gpencil_stroke_close(bGPDstroke *gps)
|
||||
{
|
||||
bGPDspoint *pt1 = nullptr;
|
||||
bGPDspoint *pt2 = nullptr;
|
||||
|
||||
/* Only can close a stroke with 3 points or more. */
|
||||
if (gps->totpoints < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Calc average distance between points to get same level of sampling. */
|
||||
float dist_tot = 0.0f;
|
||||
for (int i = 0; i < gps->totpoints - 1; i++) {
|
||||
pt1 = &gps->points[i];
|
||||
pt2 = &gps->points[i + 1];
|
||||
dist_tot += len_v3v3(&pt1->x, &pt2->x);
|
||||
}
|
||||
/* Calc the average distance. */
|
||||
float dist_avg = dist_tot / (gps->totpoints - 1);
|
||||
|
||||
/* Calc distance between last and first point. */
|
||||
pt1 = &gps->points[gps->totpoints - 1];
|
||||
pt2 = &gps->points[0];
|
||||
float dist_close = len_v3v3(&pt1->x, &pt2->x);
|
||||
|
||||
/* if the distance to close is very small, don't need add points and just enable cyclic. */
|
||||
if (dist_close <= dist_avg) {
|
||||
gps->flag |= GP_STROKE_CYCLIC;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Calc number of points required using the average distance. */
|
||||
int tot_newpoints = std::max<int>(dist_close / dist_avg, 1);
|
||||
|
||||
/* Resize stroke array. */
|
||||
int old_tot = gps->totpoints;
|
||||
gps->totpoints += tot_newpoints;
|
||||
gps->points = (bGPDspoint *)MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints);
|
||||
if (gps->dvert != nullptr) {
|
||||
gps->dvert = (MDeformVert *)MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints);
|
||||
}
|
||||
|
||||
/* Generate new points */
|
||||
pt1 = &gps->points[old_tot - 1];
|
||||
pt2 = &gps->points[0];
|
||||
bGPDspoint *pt = &gps->points[old_tot];
|
||||
for (int i = 1; i < tot_newpoints + 1; i++, pt++) {
|
||||
float step = (tot_newpoints > 1) ? (float(i) / float(tot_newpoints)) : 0.99f;
|
||||
/* Clamp last point to be near, but not on top of first point. */
|
||||
if ((tot_newpoints > 1) && (i == tot_newpoints)) {
|
||||
step *= 0.99f;
|
||||
}
|
||||
|
||||
/* Average point. */
|
||||
interp_v3_v3v3(&pt->x, &pt1->x, &pt2->x, step);
|
||||
pt->pressure = interpf(pt2->pressure, pt1->pressure, step);
|
||||
pt->strength = interpf(pt2->strength, pt1->strength, step);
|
||||
pt->flag = 0;
|
||||
interp_v4_v4v4(pt->vert_color, pt1->vert_color, pt2->vert_color, step);
|
||||
/* Set point as selected. */
|
||||
if (gps->flag & GP_STROKE_SELECT) {
|
||||
pt->flag |= GP_SPOINT_SELECT;
|
||||
}
|
||||
|
||||
/* Set weights. */
|
||||
if (gps->dvert != nullptr) {
|
||||
MDeformVert *dvert1 = &gps->dvert[old_tot - 1];
|
||||
MDeformWeight *dw1 = BKE_defvert_ensure_index(dvert1, 0);
|
||||
float weight_1 = dw1 ? dw1->weight : 0.0f;
|
||||
|
||||
MDeformVert *dvert2 = &gps->dvert[0];
|
||||
MDeformWeight *dw2 = BKE_defvert_ensure_index(dvert2, 0);
|
||||
float weight_2 = dw2 ? dw2->weight : 0.0f;
|
||||
|
||||
MDeformVert *dvert_final = &gps->dvert[old_tot + i - 1];
|
||||
dvert_final->totweight = 0;
|
||||
MDeformWeight *dw = BKE_defvert_ensure_index(dvert_final, 0);
|
||||
if (dvert_final->dw) {
|
||||
dw->weight = interpf(weight_2, weight_1, step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable cyclic flag. */
|
||||
gps->flag |= GP_STROKE_CYCLIC;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Normal Calculation
|
||||
* \{ */
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
void BKE_gpencil_transform(bGPdata *gpd, const float mat[4][4])
|
||||
{
|
||||
if (gpd == nullptr) {
|
||||
@@ -1207,32 +498,6 @@ void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd,
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_gpencil_stroke_flip(bGPDstroke *gps)
|
||||
{
|
||||
/* Reverse points. */
|
||||
BLI_array_reverse(gps->points, gps->totpoints);
|
||||
|
||||
/* Reverse vertex groups if available. */
|
||||
if (gps->dvert) {
|
||||
BLI_array_reverse(gps->dvert, gps->totpoints);
|
||||
}
|
||||
}
|
||||
|
||||
/* Temp data for storing information about an "island" of points
|
||||
* that should be kept when splitting up a stroke. Used in:
|
||||
* gpencil_stroke_delete_tagged_points()
|
||||
@@ -1498,250 +763,3 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd,
|
||||
|
||||
return new_stroke;
|
||||
}
|
||||
|
||||
/* Helper: copy point between strokes */
|
||||
static void gpencil_stroke_copy_point(bGPDstroke *gps,
|
||||
MDeformVert *dvert,
|
||||
bGPDspoint *point,
|
||||
const float delta[3],
|
||||
float pressure,
|
||||
float strength,
|
||||
float deltatime)
|
||||
{
|
||||
bGPDspoint *newpoint;
|
||||
|
||||
gps->points = (bGPDspoint *)MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1));
|
||||
if (gps->dvert != nullptr) {
|
||||
gps->dvert = (MDeformVert *)MEM_reallocN(gps->dvert,
|
||||
sizeof(MDeformVert) * (gps->totpoints + 1));
|
||||
}
|
||||
else {
|
||||
/* If destination has weight add weight to origin. */
|
||||
if (dvert != nullptr) {
|
||||
gps->dvert = (MDeformVert *)MEM_callocN(sizeof(MDeformVert) * (gps->totpoints + 1),
|
||||
__func__);
|
||||
}
|
||||
}
|
||||
|
||||
gps->totpoints++;
|
||||
newpoint = &gps->points[gps->totpoints - 1];
|
||||
|
||||
newpoint->x = point->x * delta[0];
|
||||
newpoint->y = point->y * delta[1];
|
||||
newpoint->z = point->z * delta[2];
|
||||
newpoint->flag = point->flag;
|
||||
newpoint->pressure = pressure;
|
||||
newpoint->strength = strength;
|
||||
newpoint->time = point->time + deltatime;
|
||||
copy_v4_v4(newpoint->vert_color, point->vert_color);
|
||||
|
||||
if (gps->dvert != nullptr) {
|
||||
MDeformVert *newdvert = &gps->dvert[gps->totpoints - 1];
|
||||
|
||||
if (dvert != nullptr) {
|
||||
newdvert->totweight = dvert->totweight;
|
||||
newdvert->dw = (MDeformWeight *)MEM_dupallocN(dvert->dw);
|
||||
}
|
||||
else {
|
||||
newdvert->totweight = 0;
|
||||
newdvert->dw = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_gpencil_stroke_join(bGPDstroke *gps_a,
|
||||
bGPDstroke *gps_b,
|
||||
const bool leave_gaps,
|
||||
const bool fit_thickness,
|
||||
const bool smooth,
|
||||
bool auto_flip)
|
||||
{
|
||||
bGPDspoint point;
|
||||
bGPDspoint *pt;
|
||||
int i;
|
||||
const float delta[3] = {1.0f, 1.0f, 1.0f};
|
||||
float deltatime = 0.0f;
|
||||
|
||||
/* sanity checks */
|
||||
if (ELEM(nullptr, gps_a, gps_b)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((gps_a->totpoints == 0) || (gps_b->totpoints == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto_flip) {
|
||||
/* define start and end points of each stroke */
|
||||
float start_a[3], start_b[3], end_a[3], end_b[3];
|
||||
pt = &gps_a->points[0];
|
||||
copy_v3_v3(start_a, &pt->x);
|
||||
|
||||
pt = &gps_a->points[gps_a->totpoints - 1];
|
||||
copy_v3_v3(end_a, &pt->x);
|
||||
|
||||
pt = &gps_b->points[0];
|
||||
copy_v3_v3(start_b, &pt->x);
|
||||
|
||||
pt = &gps_b->points[gps_b->totpoints - 1];
|
||||
copy_v3_v3(end_b, &pt->x);
|
||||
|
||||
/* Check if need flip strokes. */
|
||||
float dist = len_squared_v3v3(end_a, start_b);
|
||||
bool flip_a = false;
|
||||
bool flip_b = false;
|
||||
float lowest = dist;
|
||||
|
||||
dist = len_squared_v3v3(end_a, end_b);
|
||||
if (dist < lowest) {
|
||||
lowest = dist;
|
||||
flip_a = false;
|
||||
flip_b = true;
|
||||
}
|
||||
|
||||
dist = len_squared_v3v3(start_a, start_b);
|
||||
if (dist < lowest) {
|
||||
lowest = dist;
|
||||
flip_a = true;
|
||||
flip_b = false;
|
||||
}
|
||||
|
||||
dist = len_squared_v3v3(start_a, end_b);
|
||||
if (dist < lowest) {
|
||||
lowest = dist;
|
||||
flip_a = true;
|
||||
flip_b = true;
|
||||
}
|
||||
|
||||
if (flip_a) {
|
||||
BKE_gpencil_stroke_flip(gps_a);
|
||||
}
|
||||
if (flip_b) {
|
||||
BKE_gpencil_stroke_flip(gps_b);
|
||||
}
|
||||
}
|
||||
|
||||
/* don't visibly link the first and last points? */
|
||||
if (leave_gaps) {
|
||||
/* 1st: add one tail point to start invisible area */
|
||||
point = blender::dna::shallow_copy(gps_a->points[gps_a->totpoints - 1]);
|
||||
deltatime = point.time;
|
||||
|
||||
gpencil_stroke_copy_point(gps_a, nullptr, &point, delta, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
/* 2nd: add one head point to finish invisible area */
|
||||
point = blender::dna::shallow_copy(gps_b->points[0]);
|
||||
gpencil_stroke_copy_point(gps_a, nullptr, &point, delta, 0.0f, 0.0f, deltatime);
|
||||
}
|
||||
|
||||
/* Ratio to apply in the points to keep the same thickness in the joined stroke using the
|
||||
* destination stroke thickness. */
|
||||
const float ratio = (fit_thickness && gps_a->thickness > 0.0f) ?
|
||||
float(gps_b->thickness) / float(gps_a->thickness) :
|
||||
1.0f;
|
||||
|
||||
/* 3rd: add all points */
|
||||
const int totpoints_a = gps_a->totpoints;
|
||||
for (i = 0, pt = gps_b->points; i < gps_b->totpoints && pt; i++, pt++) {
|
||||
MDeformVert *dvert = (gps_b->dvert) ? &gps_b->dvert[i] : nullptr;
|
||||
gpencil_stroke_copy_point(
|
||||
gps_a, dvert, pt, delta, pt->pressure * ratio, pt->strength, deltatime);
|
||||
}
|
||||
/* Smooth the join to avoid hard thickness changes. */
|
||||
if (smooth) {
|
||||
const int sample_points = 8;
|
||||
/* Get the segment to smooth using n points on each side of the join. */
|
||||
int start = std::max(0, totpoints_a - sample_points);
|
||||
int end = std::min(gps_a->totpoints - 1, start + (sample_points * 2));
|
||||
const int len = (end - start);
|
||||
float step = 1.0f / ((len / 2) + 1);
|
||||
|
||||
/* Calc the average pressure. */
|
||||
float avg_pressure = 0.0f;
|
||||
for (i = start; i < end; i++) {
|
||||
pt = &gps_a->points[i];
|
||||
avg_pressure += pt->pressure;
|
||||
}
|
||||
avg_pressure = avg_pressure / len;
|
||||
|
||||
/* Smooth segment thickness and position. */
|
||||
float ratio = step;
|
||||
for (i = start; i < end; i++) {
|
||||
pt = &gps_a->points[i];
|
||||
pt->pressure += (avg_pressure - pt->pressure) * ratio;
|
||||
BKE_gpencil_stroke_smooth_point(gps_a, i, ratio * 0.6f, 2, false, true, gps_a);
|
||||
|
||||
ratio += step;
|
||||
/* In the center, reverse the ratio. */
|
||||
if (ratio > 1.0f) {
|
||||
ratio = ratio - step - step;
|
||||
step *= -1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
void BKE_gpencil_stroke_to_view_space(bGPDstroke *gps,
|
||||
float viewmat[4][4],
|
||||
const float diff_mat[4][4])
|
||||
{
|
||||
for (int i = 0; i < gps->totpoints; i++) {
|
||||
bGPDspoint *pt = &gps->points[i];
|
||||
/* Point to parent space. */
|
||||
mul_v3_m4v3(&pt->x, diff_mat, &pt->x);
|
||||
/* point to view space */
|
||||
mul_m4_v3(viewmat, &pt->x);
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_gpencil_stroke_from_view_space(bGPDstroke *gps,
|
||||
float viewinv[4][4],
|
||||
const float diff_mat[4][4])
|
||||
{
|
||||
float inverse_diff_mat[4][4];
|
||||
invert_m4_m4(inverse_diff_mat, diff_mat);
|
||||
|
||||
for (int i = 0; i < gps->totpoints; i++) {
|
||||
bGPDspoint *pt = &gps->points[i];
|
||||
mul_v3_m4v3(&pt->x, viewinv, &pt->x);
|
||||
mul_m4_v3(inverse_diff_mat, &pt->x);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
float BKE_gpencil_stroke_average_pressure_get(bGPDstroke *gps)
|
||||
{
|
||||
|
||||
if (gps->totpoints == 1) {
|
||||
return gps->points[0].pressure;
|
||||
}
|
||||
|
||||
float tot = 0.0f;
|
||||
for (int i = 0; i < gps->totpoints; i++) {
|
||||
const bGPDspoint *pt = &gps->points[i];
|
||||
tot += pt->pressure;
|
||||
}
|
||||
|
||||
return tot / float(gps->totpoints);
|
||||
}
|
||||
|
||||
bool BKE_gpencil_stroke_is_pressure_constant(bGPDstroke *gps)
|
||||
{
|
||||
if (gps->totpoints == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const float first_pressure = gps->points[0].pressure;
|
||||
for (int i = 0; i < gps->totpoints; i++) {
|
||||
const bGPDspoint *pt = &gps->points[i];
|
||||
if (pt->pressure != first_pressure) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
Reference in New Issue
Block a user