GPencil MultiStroke modifier
This patch includes a modifiers that developed for NPR rendering. - MultiStroke modifier that generates multiple strokes around the original ones. Differential Revision: https://developer.blender.org/D5795
This commit is contained in:
parent
8ff9eb97fb
commit
91248876e5
|
@ -2351,7 +2351,52 @@ class DATA_PT_gpencil_modifiers(ModifierButtonsPanel, Panel):
|
|||
sub.active = bool(md.vertex_group)
|
||||
sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
|
||||
|
||||
def GP_MULTIPLY(self, layout, ob, md):
|
||||
gpd = ob.data
|
||||
col = layout.column()
|
||||
|
||||
col.prop(md, "duplications")
|
||||
subcol = col.column()
|
||||
subcol.enabled = md.duplications > 0
|
||||
subcol.prop(md, "distance")
|
||||
subcol.prop(md, "offset", slider=True)
|
||||
|
||||
subcol.separator()
|
||||
|
||||
subcol.prop(md, "enable_fading")
|
||||
if md.enable_fading:
|
||||
subcol.prop(md, "fading_center")
|
||||
subcol.prop(md, "fading_thickness", slider=True)
|
||||
subcol.prop(md, "fading_opacity", slider=True)
|
||||
|
||||
subcol.separator()
|
||||
|
||||
col.prop(md, "enable_angle_splitting")
|
||||
if md.enable_angle_splitting:
|
||||
col.prop(md, "split_angle")
|
||||
|
||||
col = layout.column()
|
||||
col.separator()
|
||||
|
||||
col.label(text="Material:")
|
||||
row = col.row(align=True)
|
||||
row.prop_search(md, "material", gpd, "materials", text="", icon='SHADING_TEXTURE')
|
||||
row.prop(md, "invert_materials", text="", icon='ARROW_LEFTRIGHT')
|
||||
row = layout.row(align=True)
|
||||
row.prop(md, "pass_index", text="Pass")
|
||||
row.prop(md, "invert_material_pass", text="", icon='ARROW_LEFTRIGHT')
|
||||
|
||||
col = layout.column()
|
||||
col.separator()
|
||||
|
||||
col.label(text="Layer:")
|
||||
row = col.row(align=True)
|
||||
row.prop_search(md, "layer", gpd, "layers", text="", icon='GREASEPENCIL')
|
||||
row.prop(md, "invert_layers", text="", icon='ARROW_LEFTRIGHT')
|
||||
row = layout.row(align=True)
|
||||
row.prop(md, "layer_pass", text="Pass")
|
||||
row.prop(md, "invert_layer_pass", text="", icon='ARROW_LEFTRIGHT')
|
||||
|
||||
classes = (
|
||||
DATA_PT_modifiers,
|
||||
DATA_PT_gpencil_modifiers,
|
||||
|
|
|
@ -125,6 +125,12 @@ struct bGPDstroke *BKE_gpencil_add_stroke(struct bGPDframe *gpf,
|
|||
int totpoints,
|
||||
short thickness);
|
||||
|
||||
struct bGPDstroke *BKE_gpencil_add_stroke_existing_style(struct bGPDframe *gpf,
|
||||
struct bGPDstroke *existing,
|
||||
int mat_idx,
|
||||
int totpoints,
|
||||
short thickness);
|
||||
|
||||
/* Stroke and Fill - Alpha Visibility Threshold */
|
||||
#define GPENCIL_ALPHA_OPACITY_THRESH 0.001f
|
||||
#define GPENCIL_STRENGTH_MIN 0.003f
|
||||
|
@ -238,6 +244,18 @@ bool BKE_gpencil_smooth_stroke_uv(struct bGPDstroke *gps, int point_index, float
|
|||
bool BKE_gpencil_close_stroke(struct bGPDstroke *gps);
|
||||
void BKE_gpencil_dissolve_points(struct bGPDframe *gpf, struct bGPDstroke *gps, const short tag);
|
||||
|
||||
bool BKE_gpencil_stretch_stroke(struct bGPDstroke *gps, const float dist, const float tip_length);
|
||||
bool BKE_gpencil_trim_stroke_points(struct bGPDstroke *gps,
|
||||
const int index_from,
|
||||
const int index_to);
|
||||
bool BKE_gpencil_split_stroke(struct bGPDframe *gpf,
|
||||
struct bGPDstroke *gps,
|
||||
const int before_index,
|
||||
struct bGPDstroke **remaining_gps);
|
||||
bool BKE_gpencil_shrink_stroke(struct bGPDstroke *gps, const float dist);
|
||||
|
||||
float BKE_gpencil_stroke_length(const struct bGPDstroke *gps, bool use_3d);
|
||||
|
||||
void BKE_gpencil_get_range_selected(struct bGPDlayer *gpl, int *r_initframe, int *r_endframe);
|
||||
float BKE_gpencil_multiframe_falloff_calc(
|
||||
struct bGPDframe *gpf, int actnum, int f_init, int f_end, struct CurveMapping *cur_falloff);
|
||||
|
|
|
@ -505,6 +505,18 @@ bGPDstroke *BKE_gpencil_add_stroke(bGPDframe *gpf, int mat_idx, int totpoints, s
|
|||
return gps;
|
||||
}
|
||||
|
||||
/* Add a stroke and copy the temporary drawing color value from one of the existing stroke */
|
||||
bGPDstroke *BKE_gpencil_add_stroke_existing_style(
|
||||
bGPDframe *gpf, bGPDstroke *existing, int mat_idx, int totpoints, short thickness)
|
||||
{
|
||||
bGPDstroke *gps = BKE_gpencil_add_stroke(gpf, mat_idx, totpoints, thickness);
|
||||
/* Copy runtime color data so that strokes added in the modifier has the style.
|
||||
* There are depsgrapgh reference pointers inside,
|
||||
* change the copy function if interfere with future drawing implementation. */
|
||||
memcpy(&gps->runtime, &existing->runtime, sizeof(bGPDstroke_Runtime));
|
||||
return gps;
|
||||
}
|
||||
|
||||
/* ************************************************** */
|
||||
/* Data Duplication */
|
||||
|
||||
|
@ -1752,6 +1764,240 @@ bool BKE_gpencil_sample_stroke(bGPDstroke *gps, const float dist, const bool sel
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backbone stretch similar to Freestyle.
|
||||
* \param gps: Stroke to sample
|
||||
* \param dist: Distance of one segment
|
||||
* \param tip_length: Ignore tip jittering, set zero to use default value.
|
||||
*/
|
||||
bool BKE_gpencil_stretch_stroke(bGPDstroke *gps, const float dist, const float tip_length)
|
||||
{
|
||||
bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt;
|
||||
int i;
|
||||
float threshold = (tip_length == 0 ? 0.001f : tip_length);
|
||||
|
||||
if (gps->totpoints < 2 || dist < FLT_EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
last_pt = &pt[gps->totpoints - 1];
|
||||
second_last = &pt[gps->totpoints - 2];
|
||||
next_pt = &pt[1];
|
||||
|
||||
float len1 = 0.0f;
|
||||
float len2 = 0.0f;
|
||||
|
||||
i = 1;
|
||||
while (len1 < threshold && gps->totpoints > i) {
|
||||
next_pt = &pt[i];
|
||||
len1 = len_v3v3(&next_pt->x, &pt->x);
|
||||
i++;
|
||||
}
|
||||
|
||||
i = 2;
|
||||
while (len2 < threshold && gps->totpoints >= i) {
|
||||
second_last = &pt[gps->totpoints - i];
|
||||
len2 = len_v3v3(&last_pt->x, &second_last->x);
|
||||
i++;
|
||||
}
|
||||
|
||||
float extend1 = (len1 + dist) / len1;
|
||||
float extend2 = (len2 + dist) / len2;
|
||||
|
||||
float result1[3], result2[3];
|
||||
|
||||
interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1);
|
||||
interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2);
|
||||
|
||||
copy_v3_v3(&pt->x, result1);
|
||||
copy_v3_v3(&last_pt->x, result2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim stroke to needed segments
|
||||
* \param gps: Target stroke
|
||||
* \param index_from: the index of the first point to be used in the trimmed result
|
||||
* \param index_to: the index of the last point to be used in the trimmed result
|
||||
*/
|
||||
bool BKE_gpencil_trim_stroke_points(bGPDstroke *gps, const int index_from, const int index_to)
|
||||
{
|
||||
bGPDspoint *pt = gps->points, *new_pt;
|
||||
MDeformVert *dv, *new_dv;
|
||||
|
||||
const int new_count = index_to - index_from + 1;
|
||||
|
||||
if (new_count >= gps->totpoints) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_count == 1) {
|
||||
BKE_gpencil_free_stroke_weights(gps);
|
||||
MEM_freeN(gps->points);
|
||||
gps->points = NULL;
|
||||
gps->dvert = NULL;
|
||||
gps->totpoints = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
new_pt = MEM_callocN(sizeof(bGPDspoint) * new_count, "gp_stroke_points_trimmed");
|
||||
|
||||
for (int i = 0; i < new_count; i++) {
|
||||
memcpy(&new_pt[i], &pt[i + index_from], sizeof(bGPDspoint));
|
||||
}
|
||||
|
||||
if (gps->dvert) {
|
||||
new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, "gp_stroke_dverts_trimmed");
|
||||
for (int i = 0; i < new_count; i++) {
|
||||
dv = &gps->dvert[i + index_from];
|
||||
new_dv[i].flag = dv->flag;
|
||||
new_dv[i].totweight = dv->totweight;
|
||||
new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
|
||||
"gp_stroke_dverts_dw_trimmed");
|
||||
for (int j = 0; j < dv->totweight; j++) {
|
||||
new_dv[i].dw[j].weight = dv->dw[j].weight;
|
||||
new_dv[i].dw[j].def_nr = dv->dw[j].def_nr;
|
||||
}
|
||||
}
|
||||
MEM_freeN(gps->dvert);
|
||||
gps->dvert = new_dv;
|
||||
}
|
||||
|
||||
MEM_freeN(gps->points);
|
||||
gps->points = new_pt;
|
||||
gps->totpoints = new_count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BKE_gpencil_split_stroke(bGPDframe *gpf,
|
||||
bGPDstroke *gps,
|
||||
const int before_index,
|
||||
bGPDstroke **remaining_gps)
|
||||
{
|
||||
bGPDstroke *new_gps;
|
||||
bGPDspoint *pt = gps->points, *new_pt;
|
||||
MDeformVert *dv, *new_dv;
|
||||
|
||||
if (before_index >= gps->totpoints || before_index == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int new_count = gps->totpoints - before_index;
|
||||
const int old_count = before_index;
|
||||
|
||||
/* Handle remaining segments first. */
|
||||
|
||||
new_gps = BKE_gpencil_add_stroke_existing_style(
|
||||
gpf, gps, gps->mat_nr, new_count, gps->thickness);
|
||||
|
||||
new_pt = new_gps->points; /* Allocated from above. */
|
||||
|
||||
for (int i = 0; i < new_count; i++) {
|
||||
memcpy(&new_pt[i], &pt[i + before_index], sizeof(bGPDspoint));
|
||||
}
|
||||
|
||||
if (gps->dvert) {
|
||||
new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, "gp_stroke_dverts_remaining");
|
||||
for (int i = 0; i < new_count; i++) {
|
||||
dv = &gps->dvert[i + before_index];
|
||||
new_dv[i].flag = dv->flag;
|
||||
new_dv[i].totweight = dv->totweight;
|
||||
new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
|
||||
"gp_stroke_dverts_dw_remaining");
|
||||
for (int j = 0; j < dv->totweight; j++) {
|
||||
new_dv[i].dw[j].weight = dv->dw[j].weight;
|
||||
new_dv[i].dw[j].def_nr = dv->dw[j].def_nr;
|
||||
}
|
||||
}
|
||||
new_gps->dvert = new_dv;
|
||||
}
|
||||
|
||||
(*remaining_gps) = new_gps;
|
||||
|
||||
/* Trim the original stroke into a shorter one.
|
||||
* Keep the end point. */
|
||||
|
||||
BKE_gpencil_trim_stroke_points(gps, 0, old_count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrink the stroke by length.
|
||||
* \param gps: Stroke to shrink
|
||||
* \param dist: delta length
|
||||
*/
|
||||
bool BKE_gpencil_shrink_stroke(bGPDstroke *gps, const float dist)
|
||||
{
|
||||
bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt;
|
||||
int i;
|
||||
|
||||
if (gps->totpoints < 2 || dist < FLT_EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
last_pt = &pt[gps->totpoints - 1];
|
||||
second_last = &pt[gps->totpoints - 2];
|
||||
next_pt = &pt[1];
|
||||
|
||||
float len1, this_len1, cut_len1;
|
||||
float len2, this_len2, cut_len2;
|
||||
int index_start, index_end;
|
||||
|
||||
len1 = len2 = this_len1 = this_len2 = cut_len1 = cut_len2 = 0.0f;
|
||||
|
||||
i = 1;
|
||||
while (len1 < dist && gps->totpoints > i - 1) {
|
||||
next_pt = &pt[i];
|
||||
this_len1 = len_v3v3(&pt[i].x, &pt[i + 1].x);
|
||||
len1 += this_len1;
|
||||
cut_len1 = len1 - dist;
|
||||
i++;
|
||||
}
|
||||
index_start = i - 2;
|
||||
|
||||
i = 2;
|
||||
while (len2 < dist && gps->totpoints >= i) {
|
||||
second_last = &pt[gps->totpoints - i];
|
||||
this_len2 = len_v3v3(&second_last[1].x, &second_last->x);
|
||||
len2 += this_len2;
|
||||
cut_len2 = len2 - dist;
|
||||
i++;
|
||||
}
|
||||
index_end = gps->totpoints - i + 2;
|
||||
|
||||
if (len1 < dist || len2 < dist || index_end <= index_start) {
|
||||
index_start = index_end = 0; /* empty stroke */
|
||||
}
|
||||
|
||||
if ((index_end == index_start + 1) && (cut_len1 + cut_len2 > 1.0f)) {
|
||||
index_start = index_end = 0; /* no length left to cut */
|
||||
}
|
||||
|
||||
BKE_gpencil_trim_stroke_points(gps, index_start, index_end);
|
||||
|
||||
if (gps->totpoints == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pt = gps->points;
|
||||
|
||||
float cut1 = cut_len1 / this_len1;
|
||||
float cut2 = cut_len2 / this_len2;
|
||||
|
||||
float result1[3], result2[3];
|
||||
|
||||
interp_v3_v3v3(result1, &pt[1].x, &pt[0].x, cut1);
|
||||
interp_v3_v3v3(result2, &pt[gps->totpoints - 2].x, &pt[gps->totpoints - 1].x, cut2);
|
||||
|
||||
copy_v3_v3(&pt[0].x, result1);
|
||||
copy_v3_v3(&pt[gps->totpoints - 1].x, result2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply smooth to stroke point
|
||||
* \param gps: Stroke to smooth
|
||||
|
@ -2273,6 +2519,28 @@ void BKE_gpencil_stroke_2d_flat_ref(const bGPDspoint *ref_points,
|
|||
*r_direction = (int)locy[2];
|
||||
}
|
||||
|
||||
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;
|
||||
int i;
|
||||
bGPDspoint *pt;
|
||||
float total_length = 0.0f;
|
||||
for (i = 1; i < gps->totpoints; i++) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim stroke to the first intersection or loop
|
||||
* \param gps: Stroke data
|
||||
|
|
|
@ -50,6 +50,7 @@ set(SRC
|
|||
intern/MOD_gpencilhook.c
|
||||
intern/MOD_gpencillattice.c
|
||||
intern/MOD_gpencilmirror.c
|
||||
intern/MOD_gpencilmultiply.c
|
||||
intern/MOD_gpencilnoise.c
|
||||
intern/MOD_gpenciloffset.c
|
||||
intern/MOD_gpencilopacity.c
|
||||
|
|
|
@ -42,6 +42,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Hook;
|
|||
extern GpencilModifierTypeInfo modifierType_Gpencil_Offset;
|
||||
extern GpencilModifierTypeInfo modifierType_Gpencil_Armature;
|
||||
extern GpencilModifierTypeInfo modifierType_Gpencil_Time;
|
||||
extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply;
|
||||
|
||||
/* MOD_gpencil_util.c */
|
||||
void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]);
|
||||
|
|
|
@ -71,6 +71,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[])
|
|||
INIT_GP_TYPE(Offset);
|
||||
INIT_GP_TYPE(Armature);
|
||||
INIT_GP_TYPE(Time);
|
||||
INIT_GP_TYPE(Multiply);
|
||||
#undef INIT_GP_TYPE
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* The Original Code is Copyright (C) 2017, Blender Foundation
|
||||
* This is a new part of Blender
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK *****
|
||||
*
|
||||
*/
|
||||
|
||||
/** \file blender/gpencil_modifiers/intern/MOD_gpencilstrokes.c
|
||||
* \ingroup modifiers
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_gpencil_types.h"
|
||||
#include "DNA_gpencil_modifier_types.h"
|
||||
|
||||
#include "BLI_blenlib.h"
|
||||
#include "BLI_rand.h"
|
||||
#include "BLI_math.h"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_alloca.h"
|
||||
|
||||
#include "BKE_gpencil.h"
|
||||
#include "BKE_gpencil_modifier.h"
|
||||
#include "BKE_modifier.h"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_global.h"
|
||||
#include "BKE_object.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_scene.h"
|
||||
#include "BKE_layer.h"
|
||||
#include "BKE_library_query.h"
|
||||
#include "BKE_collection.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_mapping.h"
|
||||
|
||||
#include "bmesh.h"
|
||||
#include "bmesh_tools.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
#include "DEG_depsgraph_build.h"
|
||||
#include "DEG_depsgraph_query.h"
|
||||
|
||||
#include "MOD_gpencil_util.h"
|
||||
#include "MOD_gpencil_modifiertypes.h"
|
||||
|
||||
static void initData(GpencilModifierData *md)
|
||||
{
|
||||
MultiplyGpencilModifierData *mmd = (MultiplyGpencilModifierData *)md;
|
||||
mmd->duplications = 3;
|
||||
mmd->distance = 0.1f;
|
||||
mmd->split_angle = 1.0f;
|
||||
}
|
||||
|
||||
static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
|
||||
{
|
||||
BKE_gpencil_modifier_copyData_generic(md, target);
|
||||
}
|
||||
|
||||
static void splitStroke(bGPDframe *gpf, bGPDstroke *gps, float split_angle)
|
||||
{
|
||||
bGPDspoint *pt = gps->points;
|
||||
bGPDstroke *new_gps = gps;
|
||||
int i;
|
||||
volatile float angle;
|
||||
|
||||
if (split_angle <= FLT_EPSILON) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 1; i < new_gps->totpoints - 1; i++) {
|
||||
angle = angle_v3v3v3(&pt[i - 1].x, &pt[i].x, &pt[i + 1].x);
|
||||
if (angle < split_angle) {
|
||||
if (BKE_gpencil_split_stroke(gpf, new_gps, i, &new_gps)) {
|
||||
pt = new_gps->points;
|
||||
i = 0;
|
||||
continue; /* then i == 1 again */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void minter_v3_v3v3v3_ref(
|
||||
float *result, float *left, float *middle, float *right, float *stroke_normal)
|
||||
{
|
||||
float left_arm[3], right_arm[3], inter1[3], inter2[3];
|
||||
float minter[3];
|
||||
if (left) {
|
||||
sub_v3_v3v3(left_arm, middle, left);
|
||||
cross_v3_v3v3(inter1, stroke_normal, left_arm);
|
||||
}
|
||||
if (right) {
|
||||
sub_v3_v3v3(right_arm, right, middle);
|
||||
cross_v3_v3v3(inter2, stroke_normal, right_arm);
|
||||
}
|
||||
if (!left) {
|
||||
normalize_v3(inter2);
|
||||
copy_v3_v3(result, inter2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!right) {
|
||||
normalize_v3(inter1);
|
||||
copy_v3_v3(result, inter1);
|
||||
return;
|
||||
}
|
||||
|
||||
interp_v3_v3v3(minter, inter1, inter2, 0.5);
|
||||
normalize_v3(minter);
|
||||
copy_v3_v3(result, minter);
|
||||
}
|
||||
|
||||
static void duplicateStroke(bGPDframe *gpf,
|
||||
bGPDstroke *gps,
|
||||
int count,
|
||||
float dist,
|
||||
float offset,
|
||||
ListBase *results,
|
||||
int fading,
|
||||
float fading_center,
|
||||
float fading_thickness,
|
||||
float fading_opacity)
|
||||
{
|
||||
int i;
|
||||
bGPDstroke *new_gps;
|
||||
float stroke_normal[3];
|
||||
float minter[3];
|
||||
bGPDspoint *pt;
|
||||
float offset_factor;
|
||||
float thickness_factor;
|
||||
float opacity_factor;
|
||||
|
||||
BKE_gpencil_stroke_normal(gps, stroke_normal);
|
||||
if (len_v3(stroke_normal) < FLT_EPSILON) {
|
||||
add_v3_fl(stroke_normal, 1);
|
||||
normalize_v3(stroke_normal);
|
||||
}
|
||||
|
||||
float *t1_array = MEM_callocN(sizeof(float) * 3 * gps->totpoints,
|
||||
"duplicate_temp_result_array_1");
|
||||
float *t2_array = MEM_callocN(sizeof(float) * 3 * gps->totpoints,
|
||||
"duplicate_temp_result_array_2");
|
||||
|
||||
pt = gps->points;
|
||||
|
||||
for (int j = 0; j < gps->totpoints; j++) {
|
||||
if (j == 0) {
|
||||
minter_v3_v3v3v3_ref(minter, NULL, &pt[j].x, &pt[j + 1].x, stroke_normal);
|
||||
}
|
||||
else if (j == gps->totpoints - 1) {
|
||||
minter_v3_v3v3v3_ref(minter, &pt[j - 1].x, &pt[j].x, NULL, stroke_normal);
|
||||
}
|
||||
else {
|
||||
minter_v3_v3v3v3_ref(minter, &pt[j - 1].x, &pt[j].x, &pt[j + 1].x, stroke_normal);
|
||||
}
|
||||
mul_v3_fl(minter, dist);
|
||||
add_v3_v3v3(&t1_array[j * 3], &pt[j].x, minter);
|
||||
sub_v3_v3v3(&t2_array[j * 3], &pt[j].x, minter);
|
||||
}
|
||||
|
||||
/* This ensures the original stroke is the last one to be processed. */
|
||||
for (i = count - 1; i >= 0; i--) {
|
||||
if (i != 0) {
|
||||
new_gps = BKE_gpencil_stroke_duplicate(gps);
|
||||
new_gps->flag |= GP_STROKE_RECALC_GEOMETRY;
|
||||
BLI_addtail(results, new_gps);
|
||||
}
|
||||
else {
|
||||
new_gps = gps;
|
||||
}
|
||||
|
||||
pt = new_gps->points;
|
||||
|
||||
if (count == 1) {
|
||||
offset_factor = 0;
|
||||
}
|
||||
else {
|
||||
offset_factor = (float)i / (float)(count - 1);
|
||||
}
|
||||
|
||||
if (fading) {
|
||||
thickness_factor = (offset_factor > fading_center) ?
|
||||
(interpf(1 - fading_thickness, 1.0f, offset_factor - fading_center)) :
|
||||
(interpf(
|
||||
1.0f, 1 - fading_thickness, offset_factor - fading_center + 1));
|
||||
opacity_factor = (offset_factor > fading_center) ?
|
||||
(interpf(1 - fading_opacity, 1.0f, offset_factor - fading_center)) :
|
||||
(interpf(1.0f, 1 - fading_opacity, offset_factor - fading_center + 1));
|
||||
}
|
||||
|
||||
for (int j = 0; j < new_gps->totpoints; j++) {
|
||||
interp_v3_v3v3(&pt[j].x,
|
||||
&t1_array[j * 3],
|
||||
&t2_array[j * 3],
|
||||
interpf(1 + offset, offset, offset_factor));
|
||||
if (fading) {
|
||||
pt[j].pressure = gps->points[j].pressure * thickness_factor;
|
||||
pt[j].strength = gps->points[j].strength * opacity_factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
MEM_freeN(t1_array);
|
||||
MEM_freeN(t2_array);
|
||||
}
|
||||
|
||||
static void bakeModifier(Main *UNUSED(bmain),
|
||||
Depsgraph *depsgraph,
|
||||
GpencilModifierData *md,
|
||||
Object *ob)
|
||||
{
|
||||
|
||||
bGPdata *gpd = ob->data;
|
||||
|
||||
for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
||||
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
||||
ListBase duplicates = {0};
|
||||
MultiplyGpencilModifierData *mmd = (MultiplyGpencilModifierData *)md;
|
||||
bGPDstroke *gps;
|
||||
for (gps = gpf->strokes.first; gps; gps = gps->next) {
|
||||
if (!is_stroke_affected_by_modifier(ob,
|
||||
mmd->layername,
|
||||
mmd->materialname,
|
||||
mmd->pass_index,
|
||||
mmd->layer_pass,
|
||||
1,
|
||||
gpl,
|
||||
gps,
|
||||
mmd->flag & GP_MIRROR_INVERT_LAYER,
|
||||
mmd->flag & GP_MIRROR_INVERT_PASS,
|
||||
mmd->flag & GP_MIRROR_INVERT_LAYERPASS,
|
||||
mmd->flag & GP_MIRROR_INVERT_MATERIAL)) {
|
||||
continue;
|
||||
}
|
||||
if (mmd->flags & GP_MULTIPLY_ENABLE_ANGLE_SPLITTING) {
|
||||
splitStroke(gpf, gps, mmd->split_angle);
|
||||
}
|
||||
if (mmd->duplications > 0) {
|
||||
duplicateStroke(gpf,
|
||||
gps,
|
||||
mmd->duplications,
|
||||
mmd->distance,
|
||||
mmd->offset,
|
||||
&duplicates,
|
||||
mmd->flags & GP_MULTIPLY_ENABLE_FADING,
|
||||
mmd->fading_center,
|
||||
mmd->fading_thickness,
|
||||
mmd->fading_opacity);
|
||||
}
|
||||
}
|
||||
if (duplicates.first) {
|
||||
((bGPDstroke *)gpf->strokes.last)->next = duplicates.first;
|
||||
((bGPDstroke *)duplicates.first)->prev = gpf->strokes.last;
|
||||
gpf->strokes.last = duplicates.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------- */
|
||||
|
||||
/* Generic "generateStrokes" callback */
|
||||
static void generateStrokes(
|
||||
GpencilModifierData *md, Depsgraph *depsgraph, Object *ob, bGPDlayer *gpl, bGPDframe *gpf)
|
||||
{
|
||||
MultiplyGpencilModifierData *mmd = (MultiplyGpencilModifierData *)md;
|
||||
bGPDstroke *gps;
|
||||
ListBase duplicates = {0};
|
||||
for (gps = gpf->strokes.first; gps; gps = gps->next) {
|
||||
if (!is_stroke_affected_by_modifier(ob,
|
||||
mmd->layername,
|
||||
mmd->materialname,
|
||||
mmd->pass_index,
|
||||
mmd->layer_pass,
|
||||
1,
|
||||
gpl,
|
||||
gps,
|
||||
mmd->flag & GP_MIRROR_INVERT_LAYER,
|
||||
mmd->flag & GP_MIRROR_INVERT_PASS,
|
||||
mmd->flag & GP_MIRROR_INVERT_LAYERPASS,
|
||||
mmd->flag & GP_MIRROR_INVERT_MATERIAL)) {
|
||||
continue;
|
||||
}
|
||||
if (mmd->flags & GP_MULTIPLY_ENABLE_ANGLE_SPLITTING) {
|
||||
splitStroke(gpf, gps, mmd->split_angle);
|
||||
}
|
||||
if (mmd->duplications > 0) {
|
||||
duplicateStroke(gpf,
|
||||
gps,
|
||||
mmd->duplications,
|
||||
mmd->distance,
|
||||
mmd->offset,
|
||||
&duplicates,
|
||||
mmd->flags & GP_MULTIPLY_ENABLE_FADING,
|
||||
mmd->fading_center,
|
||||
mmd->fading_thickness,
|
||||
mmd->fading_opacity);
|
||||
}
|
||||
}
|
||||
if (duplicates.first) {
|
||||
((bGPDstroke *)gpf->strokes.last)->next = duplicates.first;
|
||||
((bGPDstroke *)duplicates.first)->prev = gpf->strokes.last;
|
||||
gpf->strokes.last = duplicates.first;
|
||||
}
|
||||
}
|
||||
|
||||
GpencilModifierTypeInfo modifierType_Gpencil_Multiply = {
|
||||
/* name */ "Multiple Strokes",
|
||||
/* structName */ "MultiplyGpencilModifierData",
|
||||
/* structSize */ sizeof(MultiplyGpencilModifierData),
|
||||
/* type */ eGpencilModifierTypeType_Gpencil,
|
||||
/* flags */ 0,
|
||||
|
||||
/* copyData */ copyData,
|
||||
|
||||
/* deformStroke */ NULL,
|
||||
/* generateStrokes */ generateStrokes,
|
||||
/* bakeModifier */ bakeModifier,
|
||||
/* remapTime */ NULL,
|
||||
|
||||
/* initData */ initData,
|
||||
/* freeData */ NULL,
|
||||
/* isDisabled */ NULL,
|
||||
/* updateDepsgraph */ NULL,
|
||||
/* dependsOnTime */ NULL,
|
||||
/* foreachObjectLink */ NULL,
|
||||
/* foreachIDLink */ NULL,
|
||||
/* foreachTexLink */ NULL,
|
||||
};
|
|
@ -46,6 +46,7 @@ typedef enum GpencilModifierType {
|
|||
eGpencilModifierType_Mirror = 14,
|
||||
eGpencilModifierType_Armature = 15,
|
||||
eGpencilModifierType_Time = 16,
|
||||
eGpencilModifierType_Multiply = 17,
|
||||
NUM_GREASEPENCIL_MODIFIER_TYPES,
|
||||
} GpencilModifierType;
|
||||
|
||||
|
@ -640,4 +641,40 @@ typedef struct ArmatureGpencilModifierData {
|
|||
|
||||
} ArmatureGpencilModifierData;
|
||||
|
||||
typedef struct MultiplyGpencilModifierData {
|
||||
GpencilModifierData modifier;
|
||||
/** Layer name. */
|
||||
char layername[64];
|
||||
/** Material name. */
|
||||
char materialname[64];
|
||||
/** Custom index for passes. */
|
||||
int pass_index;
|
||||
/** Flags. */
|
||||
int flag;
|
||||
/** Custom index for passes. */
|
||||
int layer_pass;
|
||||
char _pad[4];
|
||||
|
||||
int flags;
|
||||
|
||||
int duplications;
|
||||
float distance;
|
||||
/* -1:inner 0:middle 1:outer */
|
||||
float offset;
|
||||
|
||||
float fading_center;
|
||||
float fading_thickness;
|
||||
float fading_opacity;
|
||||
|
||||
/* in rad not deg */
|
||||
float split_angle;
|
||||
|
||||
/* char _pad[4]; */
|
||||
} MultiplyGpencilModifierData;
|
||||
|
||||
typedef enum eMultiplyGpencil_Flag {
|
||||
GP_MULTIPLY_ENABLE_ANGLE_SPLITTING = (1 << 1),
|
||||
GP_MULTIPLY_ENABLE_FADING = (1 << 2),
|
||||
} eMultiplyGpencil_Flag;
|
||||
|
||||
#endif /* __DNA_GPENCIL_MODIFIER_TYPES_H__ */
|
||||
|
|
|
@ -432,6 +432,7 @@ extern StructRNA RNA_MovieTrackingObject;
|
|||
extern StructRNA RNA_MovieTrackingStabilization;
|
||||
extern StructRNA RNA_MovieTrackingTrack;
|
||||
extern StructRNA RNA_MulticamSequence;
|
||||
extern StructRNA RNA_MultiplyGpencilModifier;
|
||||
extern StructRNA RNA_MultiresModifier;
|
||||
extern StructRNA RNA_MusgraveTexture;
|
||||
extern StructRNA RNA_NandController;
|
||||
|
|
|
@ -81,6 +81,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
|
|||
ICON_MOD_SUBSURF,
|
||||
"Subdivide",
|
||||
"Subdivide stroke adding more control points"},
|
||||
{eGpencilModifierType_Multiply,
|
||||
"GP_MULTIPLY",
|
||||
ICON_GP_MULTIFRAME_EDITING,
|
||||
"Multiple Strokes",
|
||||
"Produce multiple strokes along one stroke"},
|
||||
{0, "", 0, N_("Deform"), ""},
|
||||
{eGpencilModifierType_Armature,
|
||||
"GP_ARMATURE",
|
||||
|
@ -217,6 +222,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr)
|
|||
return &RNA_OffsetGpencilModifier;
|
||||
case eGpencilModifierType_Armature:
|
||||
return &RNA_ArmatureGpencilModifier;
|
||||
case eGpencilModifierType_Multiply:
|
||||
return &RNA_MultiplyGpencilModifier;
|
||||
/* Default */
|
||||
case eGpencilModifierType_None:
|
||||
case NUM_GREASEPENCIL_MODIFIER_TYPES:
|
||||
|
@ -1866,6 +1873,106 @@ static void rna_def_modifier_gpencilarmature(BlenderRNA *brna)
|
|||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update");
|
||||
}
|
||||
|
||||
static void rna_def_modifier_gpencilmultiply(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "MultiplyGpencilModifier", "GpencilModifier");
|
||||
RNA_def_struct_ui_text(srna, "Multiply Modifier", "Generate multiple strokes from one stroke");
|
||||
RNA_def_struct_sdna(srna, "MultiplyGpencilModifierData");
|
||||
RNA_def_struct_ui_icon(srna, ICON_GP_MULTIFRAME_EDITING);
|
||||
|
||||
prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE);
|
||||
RNA_def_property_string_sdna(prop, NULL, "layername");
|
||||
RNA_def_property_ui_text(prop, "Layer", "Layer name");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "material", PROP_STRING, PROP_NONE);
|
||||
RNA_def_property_string_sdna(prop, NULL, "materialname");
|
||||
RNA_def_property_ui_text(prop, "Material", "Material name");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "pass_index");
|
||||
RNA_def_property_range(prop, 0, 100);
|
||||
RNA_def_property_ui_text(prop, "Pass", "Pass index");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_MIRROR_INVERT_LAYER);
|
||||
RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_MIRROR_INVERT_MATERIAL);
|
||||
RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_MIRROR_INVERT_PASS);
|
||||
RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "layer_pass");
|
||||
RNA_def_property_range(prop, 0, 100);
|
||||
RNA_def_property_ui_text(prop, "Pass", "Layer pass index");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_MIRROR_INVERT_LAYERPASS);
|
||||
RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "enable_angle_splitting", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "flags", GP_MULTIPLY_ENABLE_ANGLE_SPLITTING);
|
||||
RNA_def_property_ui_text(prop, "Angle Splitting", "Enable angle splitting");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "enable_fading", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "flags", GP_MULTIPLY_ENABLE_FADING);
|
||||
RNA_def_property_ui_text(prop, "Enable Fading", "Enable fading");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "split_angle", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_range(prop, 0, M_PI);
|
||||
RNA_def_property_ui_text(prop, "Angle", "Split angle for segments");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "duplications", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_range(prop, 0, 10);
|
||||
RNA_def_property_ui_text(prop, "Duplications", "How many copies of strokes be displayed");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "distance", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_range(prop, 0, M_PI);
|
||||
RNA_def_property_ui_text(prop, "Distance", "Distance of duplications.");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "offset", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_ui_range(prop, -1, 1, 0.1, 3);
|
||||
RNA_def_property_ui_text(prop, "Offset", "Offset of duplications. -1 to 1: inner to outer");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "fading_thickness", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_range(prop, 0, 1);
|
||||
RNA_def_property_float_default(prop, 0.5);
|
||||
RNA_def_property_ui_text(prop, "Thickness", "Fade influence of stroke's thickness");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "fading_opacity", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_range(prop, 0, 1);
|
||||
RNA_def_property_float_default(prop, 0.5);
|
||||
RNA_def_property_ui_text(prop, "Opacity", "Fade influence of stroke's opacity");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "fading_center", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_range(prop, 0, 1);
|
||||
RNA_def_property_ui_text(prop, "Center", "Fade center");
|
||||
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
|
||||
}
|
||||
|
||||
void RNA_def_greasepencil_modifier(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
@ -1938,6 +2045,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna)
|
|||
rna_def_modifier_gpencilmirror(brna);
|
||||
rna_def_modifier_gpencilhook(brna);
|
||||
rna_def_modifier_gpencilarmature(brna);
|
||||
rna_def_modifier_gpencilmultiply(brna);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue