/* * 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) 2001-2002 by NaN Holding BV. * All rights reserved. */ /** \file * \ingroup edtransform */ #include "DNA_gpencil_types.h" #include "MEM_guardedalloc.h" #include "BLI_math.h" #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_gpencil.h" #include "BKE_report.h" #include "ED_gpencil.h" #include "transform.h" #include "transform_convert.h" /* -------------------------------------------------------------------- */ /** \name Gpencil Transform Creation * * \{ */ static void createTransGPencil_center_get(bGPDstroke *gps, float r_center[3]) { bGPDspoint *pt; int i; zero_v3(r_center); int tot_sel = 0; for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (pt->flag & GP_SPOINT_SELECT) { add_v3_v3(r_center, &pt->x); tot_sel++; } } if (tot_sel > 0) { mul_v3_fl(r_center, 1.0f / tot_sel); } } void createTransGPencil(bContext *C, TransInfo *t) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); bGPdata *gpd = ED_gpencil_data_get_active(C); ToolSettings *ts = CTX_data_tool_settings(C); bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); bool use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0; Object *obact = CTX_data_active_object(C); bGPDlayer *gpl; TransData *td = NULL; float mtx[3][3], smtx[3][3]; const Scene *scene = CTX_data_scene(C); const int cfra_scene = CFRA; const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0; const bool is_prop_edit_connected = (t->flag & T_PROP_CONNECTED) != 0; TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); /* == Grease Pencil Strokes to Transform Data == * Grease Pencil stroke points can be a mixture of 2D (screen-space), * or 3D coordinates. However, they're always saved as 3D points. * For now, we just do these without creating TransData2D for the 2D * strokes. This may cause issues in future though. */ tc->data_len = 0; if (gpd == NULL) { return; } /* initialize falloff curve */ if (is_multiedit) { BKE_curvemapping_initialize(ts->gp_sculpt.cur_falloff); } /* First Pass: Count the number of data-points required for the strokes, * (and additional info about the configuration - e.g. 2D/3D?). */ for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { /* only editable and visible layers are considered */ if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { bGPDframe *gpf; bGPDstroke *gps; bGPDframe *init_gpf = gpl->actframe; if (is_multiedit) { init_gpf = gpl->frames.first; } for (gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { for (gps = gpf->strokes.first; gps; gps = gps->next) { /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; } /* check if the color is editable */ if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { continue; } if (is_prop_edit) { /* Proportional Editing... */ if (is_prop_edit_connected) { /* connected only - so only if selected */ if (gps->flag & GP_STROKE_SELECT) { tc->data_len += gps->totpoints; } } else { /* everything goes - connection status doesn't matter */ tc->data_len += gps->totpoints; } } else { /* only selected stroke points are considered */ if (gps->flag & GP_STROKE_SELECT) { bGPDspoint *pt; int i; // TODO: 2D vs 3D? for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { if (pt->flag & GP_SPOINT_SELECT) { tc->data_len++; } } } } } } /* if not multiedit out of loop */ if (!is_multiedit) { break; } } } } /* Stop trying if nothing selected */ if (tc->data_len == 0) { return; } /* Allocate memory for data */ tc->data = MEM_callocN(tc->data_len * sizeof(TransData), "TransData(GPencil)"); td = tc->data; unit_m3(smtx); unit_m3(mtx); /* Second Pass: Build transdata array */ for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { /* only editable and visible layers are considered */ if (gpencil_layer_is_editable(gpl) && (gpl->actframe != NULL)) { const int cfra = (gpl->flag & GP_LAYER_FRAMELOCK) ? gpl->actframe->framenum : cfra_scene; bGPDframe *gpf = gpl->actframe; bGPDstroke *gps; float diff_mat[4][4]; float inverse_diff_mat[4][4]; bGPDframe *init_gpf = gpl->actframe; if (is_multiedit) { init_gpf = gpl->frames.first; } /* init multiframe falloff options */ int f_init = 0; int f_end = 0; if (use_multiframe_falloff) { BKE_gpencil_get_range_selected(gpl, &f_init, &f_end); } /* calculate difference matrix */ ED_gpencil_parent_location(depsgraph, obact, gpd, gpl, diff_mat); /* undo matrix */ invert_m4_m4(inverse_diff_mat, diff_mat); /* Make a new frame to work on if the layer's frame * and the current scene frame don't match up. * * - This is useful when animating as it saves that "uh-oh" moment when you realize you've * spent too much time editing the wrong frame... */ // XXX: should this be allowed when framelock is enabled? if ((gpf->framenum != cfra) && (!is_multiedit)) { gpf = BKE_gpencil_frame_addcopy(gpl, cfra); /* in some weird situations (framelock enabled) return NULL */ if (gpf == NULL) { continue; } if (!is_multiedit) { init_gpf = gpf; } } /* Loop over strokes, adding TransData for points as needed... */ for (gpf = init_gpf; gpf; gpf = gpf->next) { if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { /* if multiframe and falloff, recalculate and save value */ float falloff = 1.0f; /* by default no falloff */ if ((is_multiedit) && (use_multiframe_falloff)) { /* Faloff depends on distance to active frame (relative to the overall frame range) */ falloff = BKE_gpencil_multiframe_falloff_calc( gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff); } for (gps = gpf->strokes.first; gps; gps = gps->next) { TransData *head = td; TransData *tail = td; bool stroke_ok; /* skip strokes that are invalid for current view */ if (ED_gpencil_stroke_can_use(C, gps) == false) { continue; } /* check if the color is editable */ if (ED_gpencil_stroke_color_use(obact, gpl, gps) == false) { continue; } /* What we need to include depends on proportional editing settings... */ if (is_prop_edit) { if (is_prop_edit_connected) { /* A) "Connected" - Only those in selected strokes */ stroke_ok = (gps->flag & GP_STROKE_SELECT) != 0; } else { /* B) All points, always */ stroke_ok = true; } } else { /* C) Only selected points in selected strokes */ stroke_ok = (gps->flag & GP_STROKE_SELECT) != 0; } /* Do stroke... */ if (stroke_ok && gps->totpoints) { bGPDspoint *pt; int i; /* save falloff factor */ gps->runtime.multi_frame_falloff = falloff; /* calculate stroke center */ float center[3]; createTransGPencil_center_get(gps, center); /* add all necessary points... */ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { bool point_ok; /* include point? */ if (is_prop_edit) { /* Always all points in strokes that get included */ point_ok = true; } else { /* Only selected points in selected strokes */ point_ok = (pt->flag & GP_SPOINT_SELECT) != 0; } /* do point... */ if (point_ok) { copy_v3_v3(td->iloc, &pt->x); /* only copy center in local origins. * This allows get interesting effects also when move * using proportional editing */ if ((gps->flag & GP_STROKE_SELECT) && (ts->transform_pivot_point == V3D_AROUND_LOCAL_ORIGINS)) { copy_v3_v3(td->center, center); } else { copy_v3_v3(td->center, &pt->x); } td->loc = &pt->x; td->flag = 0; if (pt->flag & GP_SPOINT_SELECT) { td->flag |= TD_SELECTED; } /* for other transform modes (e.g. shrink-fatten), need to additional data * but never for scale or mirror */ if ((t->mode != TFM_RESIZE) && (t->mode != TFM_MIRROR)) { if (t->mode != TFM_GPENCIL_OPACITY) { td->val = &pt->pressure; td->ival = pt->pressure; } else { td->val = &pt->strength; td->ival = pt->strength; } } /* screenspace needs special matrices... */ if ((gps->flag & (GP_STROKE_3DSPACE | GP_STROKE_2DSPACE | GP_STROKE_2DIMAGE)) == 0) { /* screenspace */ td->protectflag = OB_LOCK_LOCZ | OB_LOCK_ROTZ | OB_LOCK_SCALEZ; } else { /* configure 2D dataspace points so that they don't play up... */ if (gps->flag & (GP_STROKE_2DSPACE | GP_STROKE_2DIMAGE)) { td->protectflag = OB_LOCK_LOCZ | OB_LOCK_ROTZ | OB_LOCK_SCALEZ; } } /* apply parent transformations */ copy_m3_m4(td->smtx, inverse_diff_mat); /* final position */ copy_m3_m4(td->mtx, diff_mat); /* display position */ copy_m3_m4(td->axismtx, diff_mat); /* axis orientation */ /* Triangulation must be calculated again, * so save the stroke for recalc function */ td->extra = gps; /* save pointer to object */ td->ob = obact; td++; tail++; } } /* March over these points, and calculate the proportional editing distances */ if (is_prop_edit && (head != tail)) { /* XXX: for now, we are similar enough that this works... */ calc_distanceCurveVerts(head, tail - 1); } } } } /* if not multiedit out of loop */ if (!is_multiedit) { break; } } } } } /** \} */