Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/gpencil/gpencil_utils.c
| Context not available. | |||||
| #include "BLI_rand.h" | #include "BLI_rand.h" | ||||
| #include "DNA_gpencil_types.h" | #include "DNA_gpencil_types.h" | ||||
| #include "DNA_brush_types.h" | |||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_scene_types.h" | #include "DNA_scene_types.h" | ||||
| #include "DNA_screen_types.h" | #include "DNA_screen_types.h" | ||||
| Context not available. | |||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_gpencil.h" | #include "BKE_gpencil.h" | ||||
| #include "BKE_object.h" | |||||
| #include "BKE_paint.h" | |||||
| #include "BKE_tracking.h" | #include "BKE_tracking.h" | ||||
| #include "BKE_action.h" | #include "BKE_action.h" | ||||
| #include "BKE_screen.h" | |||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| Context not available. | |||||
| #include "ED_gpencil.h" | #include "ED_gpencil.h" | ||||
| #include "ED_clip.h" | #include "ED_clip.h" | ||||
| #include "ED_view3d.h" | #include "ED_view3d.h" | ||||
| #include "ED_object.h" | |||||
| #include "ED_screen.h" | |||||
| #include "GPU_immediate.h" | |||||
| #include "GPU_immediate_util.h" | |||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| Context not available. | |||||
| switch (sa->spacetype) { | switch (sa->spacetype) { | ||||
| case SPACE_VIEW3D: /* 3D-View */ | case SPACE_VIEW3D: /* 3D-View */ | ||||
| case SPACE_BUTS: /* properties */ | |||||
| case SPACE_INFO: /* header info (needed after workspaces merge) */ | |||||
| { | { | ||||
| BLI_assert(scene && ELEM(scene->toolsettings->gpencil_src, | /* return obgpencil datablock */ | ||||
| GP_TOOL_SOURCE_SCENE, GP_TOOL_SOURCE_OBJECT)); | if (ob && (ob->type == OB_GPENCIL)) { | ||||
| if (ptr) RNA_id_pointer_create(&ob->id, ptr); | |||||
| if (scene->toolsettings->gpencil_src == GP_TOOL_SOURCE_OBJECT) { | return (bGPdata **)&ob->data; | ||||
| /* legacy behaviour for usage with old addons requiring object-linked to objects */ | |||||
| /* just in case no active/selected object... */ | |||||
| if (ob && (ob->flag & SELECT)) { | |||||
| /* for now, as long as there's an object, default to using that in 3D-View */ | |||||
| if (ptr) RNA_id_pointer_create(&ob->id, ptr); | |||||
| return &ob->gpd; | |||||
| } | |||||
| /* else: defaults to scene... */ | |||||
| } | } | ||||
| else { | else { | ||||
| if (ptr) RNA_id_pointer_create(&scene->id, ptr); | return NULL; | ||||
| return &scene->gpd; | |||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| case SPACE_NODE: /* Nodes Editor */ | case SPACE_NODE: /* Nodes Editor */ | ||||
| Context not available. | |||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| ScrArea *sa = CTX_wm_area(C); | ScrArea *sa = CTX_wm_area(C); | ||||
| Object *ob = CTX_data_active_object(C); | Object *ob = CTX_data_active_object(C); | ||||
| return ED_gpencil_data_get_pointers_direct(screen_id, scene, sa, ob, ptr); | return ED_gpencil_data_get_pointers_direct(screen_id, scene, sa, ob, ptr); | ||||
| } | } | ||||
| Context not available. | |||||
| /* Keyframe Indicator Checks */ | /* Keyframe Indicator Checks */ | ||||
| /* Check whether there's an active GP keyframe on the current frame */ | /* Check whether there's an active GP keyframe on the current frame */ | ||||
| bool ED_gpencil_has_keyframe_v3d(Scene *scene, Object *ob, int cfra) | bool ED_gpencil_has_keyframe_v3d(Scene *UNUSED(scene), Object *ob, int cfra) | ||||
| { | { | ||||
| /* just check both for now... */ | if (ob && ob->data && (ob->type == OB_GPENCIL)) { | ||||
| // XXX: this could get confusing (e.g. if only on the object, but other places don't show this) | bGPDlayer *gpl = BKE_gpencil_layer_getactive(ob->data); | ||||
| if (scene->gpd) { | |||||
| bGPDlayer *gpl = BKE_gpencil_layer_getactive(scene->gpd); | |||||
| if (gpl) { | |||||
| if (gpl->actframe) { | |||||
| // XXX: assumes that frame has been fetched already | |||||
| return (gpl->actframe->framenum == cfra); | |||||
| } | |||||
| else { | |||||
| /* XXX: disabled as could be too much of a penalty */ | |||||
| /* return BKE_gpencil_layer_find_frame(gpl, cfra); */ | |||||
| } | |||||
| } | |||||
| } | |||||
| if (ob && ob->gpd) { | |||||
| bGPDlayer *gpl = BKE_gpencil_layer_getactive(ob->gpd); | |||||
| if (gpl) { | if (gpl) { | ||||
| if (gpl->actframe) { | if (gpl->actframe) { | ||||
| // XXX: assumes that frame has been fetched already | // XXX: assumes that frame has been fetched already | ||||
| Context not available. | |||||
| return (brush != NULL); | return (brush != NULL); | ||||
| } | } | ||||
| /* poll callback for checking if there is an active palette */ | |||||
| int gp_active_palette_poll(bContext *C) | |||||
| { | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| bGPDpalette *palette = BKE_gpencil_palette_getactive(gpd); | |||||
| return (palette != NULL); | |||||
| } | |||||
| /* poll callback for checking if there is an active palette color */ | |||||
| int gp_active_palettecolor_poll(bContext *C) | |||||
| { | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| bGPDpalette *palette = BKE_gpencil_palette_getactive(gpd); | |||||
| bGPDpalettecolor *palcolor = BKE_gpencil_palettecolor_getactive(palette); | |||||
| return (palcolor != NULL); | |||||
| } | |||||
| /* ******************************************************** */ | /* ******************************************************** */ | ||||
| /* Dynamic Enums of GP Layers */ | /* Dynamic Enums of GP Layers */ | ||||
| /* NOTE: These include an option to create a new layer and use that... */ | /* NOTE: These include an option to create a new layer and use that... */ | ||||
| Context not available. | |||||
| /* filter stroke types by flags + spacetype */ | /* filter stroke types by flags + spacetype */ | ||||
| if (gps->flag & GP_STROKE_3DSPACE) { | if (gps->flag & GP_STROKE_3DSPACE) { | ||||
| /* 3D strokes - only in 3D view */ | /* 3D strokes - only in 3D view */ | ||||
| return (sa->spacetype == SPACE_VIEW3D); | return ((sa->spacetype == SPACE_VIEW3D) || (sa->spacetype == SPACE_BUTS)); | ||||
| } | } | ||||
| else if (gps->flag & GP_STROKE_2DIMAGE) { | else if (gps->flag & GP_STROKE_2DIMAGE) { | ||||
| /* Special "image" strokes - only in Image Editor */ | /* Special "image" strokes - only in Image Editor */ | ||||
| Context not available. | |||||
| bool ED_gpencil_stroke_color_use(const bGPDlayer *gpl, const bGPDstroke *gps) | bool ED_gpencil_stroke_color_use(const bGPDlayer *gpl, const bGPDstroke *gps) | ||||
| { | { | ||||
| /* check if the color is editable */ | /* check if the color is editable */ | ||||
| bGPDpalettecolor *palcolor = gps->palcolor; | PaletteColor *palcolor = gps->palcolor; | ||||
| if (palcolor != NULL) { | if ((gps->palette) && (palcolor != NULL)) { | ||||
| if (palcolor->flag & PC_COLOR_HIDE) | if (palcolor->flag & PC_COLOR_HIDE) | ||||
| return false; | return false; | ||||
| if (((gpl->flag & GP_LAYER_UNLOCK_COLOR) == 0) && (palcolor->flag & PC_COLOR_LOCKED)) | if (((gpl->flag & GP_LAYER_UNLOCK_COLOR) == 0) && (palcolor->flag & PC_COLOR_LOCKED)) | ||||
| Context not available. | |||||
| return true; | return true; | ||||
| } | } | ||||
| /* Get palette color or create a new one */ | |||||
| bGPDpalettecolor *ED_gpencil_stroke_getcolor(bGPdata *gpd, bGPDstroke *gps) | |||||
| { | |||||
| bGPDpalette *palette; | |||||
| bGPDpalettecolor *palcolor; | |||||
| if ((gps->palcolor != NULL) && ((gps->flag & GP_STROKE_RECALC_COLOR) == 0)) | |||||
| return gps->palcolor; | |||||
| /* get palette */ | |||||
| palette = BKE_gpencil_palette_getactive(gpd); | |||||
| if (palette == NULL) { | |||||
| palette = BKE_gpencil_palette_addnew(gpd, DATA_("GP_Palette"), true); | |||||
| } | |||||
| /* get color */ | |||||
| palcolor = BKE_gpencil_palettecolor_getbyname(palette, gps->colorname); | |||||
| if (palcolor == NULL) { | |||||
| if (gps->palcolor == NULL) { | |||||
| palcolor = BKE_gpencil_palettecolor_addnew(palette, DATA_("Color"), true); | |||||
| /* set to a different color */ | |||||
| ARRAY_SET_ITEMS(palcolor->color, 1.0f, 0.0f, 1.0f, 0.9f); | |||||
| } | |||||
| else { | |||||
| palcolor = BKE_gpencil_palettecolor_addnew(palette, gps->colorname, true); | |||||
| /* set old color and attributes */ | |||||
| bGPDpalettecolor *gpscolor = gps->palcolor; | |||||
| copy_v4_v4(palcolor->color, gpscolor->color); | |||||
| copy_v4_v4(palcolor->fill, gpscolor->fill); | |||||
| palcolor->flag = gpscolor->flag; | |||||
| } | |||||
| } | |||||
| /* clear flag and set pointer */ | |||||
| gps->flag &= ~GP_STROKE_RECALC_COLOR; | |||||
| gps->palcolor = palcolor; | |||||
| return palcolor; | |||||
| } | |||||
| /* ******************************************************** */ | /* ******************************************************** */ | ||||
| /* Space Conversion */ | /* Space Conversion */ | ||||
| Context not available. | |||||
| } | } | ||||
| /** | /** | ||||
| * Change points position relative to parent object | * Change position relative to parent object | ||||
| */ | */ | ||||
| void gp_apply_parent(bGPDlayer *gpl, bGPDstroke *gps) | void gp_apply_parent(Object *obact, bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps) | ||||
| { | { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| int i; | int i; | ||||
| Context not available. | |||||
| float inverse_diff_mat[4][4]; | float inverse_diff_mat[4][4]; | ||||
| float fpt[3]; | float fpt[3]; | ||||
| ED_gpencil_parent_location(gpl, diff_mat); | ED_gpencil_parent_location(obact, gpd, gpl, diff_mat); | ||||
| invert_m4_m4(inverse_diff_mat, diff_mat); | invert_m4_m4(inverse_diff_mat, diff_mat); | ||||
| for (i = 0; i < gps->totpoints; i++) { | for (i = 0; i < gps->totpoints; i++) { | ||||
| Context not available. | |||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * Change point position relative to parent object | * Change point position relative to parent object | ||||
| */ | */ | ||||
| void gp_apply_parent_point(bGPDlayer *gpl, bGPDspoint *pt) | void gp_apply_parent_point(Object *obact, bGPdata *gpd, bGPDlayer *gpl, bGPDspoint *pt) | ||||
| { | { | ||||
| /* undo matrix */ | /* undo matrix */ | ||||
| float diff_mat[4][4]; | float diff_mat[4][4]; | ||||
| float inverse_diff_mat[4][4]; | float inverse_diff_mat[4][4]; | ||||
| float fpt[3]; | float fpt[3]; | ||||
| ED_gpencil_parent_location(gpl, diff_mat); | ED_gpencil_parent_location(obact, gpd, gpl, diff_mat); | ||||
| invert_m4_m4(inverse_diff_mat, diff_mat); | invert_m4_m4(inverse_diff_mat, diff_mat); | ||||
| mul_v3_m4v3(fpt, inverse_diff_mat, &pt->x); | mul_v3_m4v3(fpt, inverse_diff_mat, &pt->x); | ||||
| Context not available. | |||||
| } | } | ||||
| /** | /** | ||||
| * Apply smooth to stroke point | * Subdivide a stroke once, by adding a point half way between each pair of existing points | ||||
| * \param gps Stroke to smooth | * \param gps Stroke data | ||||
| * \param i Point index | * \param sublevel Number of times to subdivide | ||||
| * \param inf Amount of smoothing to apply | |||||
| * \param affect_pressure Apply smoothing to pressure values too? | |||||
| */ | */ | ||||
| bool gp_smooth_stroke(bGPDstroke *gps, int i, float inf, bool affect_pressure) | void gp_subdivide_stroke(bGPDstroke *gps, const int sublevel) | ||||
| { | { | ||||
| bGPDspoint *pt = &gps->points[i]; | bGPDspoint *temp_points; | ||||
| float pressure = 0.0f; | int totnewpoints, oldtotpoints; | ||||
| float sco[3] = {0.0f}; | int i2; | ||||
| /* Do nothing if not enough points to smooth out */ | /* loop as many times as levels */ | ||||
| if (gps->totpoints <= 2) { | for (int s = 0; s < sublevel; s++) { | ||||
| return false; | totnewpoints = gps->totpoints - 1; | ||||
| } | /* duplicate points in a temp area */ | ||||
| temp_points = MEM_dupallocN(gps->points); | |||||
| /* Only affect endpoints by a fraction of the normal strength, | oldtotpoints = gps->totpoints; | ||||
| * to prevent the stroke from shrinking too much | |||||
| */ | /* resize the points arrys */ | ||||
| if ((i == 0) || (i == gps->totpoints - 1)) { | gps->totpoints += totnewpoints; | ||||
| inf *= 0.1f; | gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); | ||||
| } | gps->flag |= GP_STROKE_RECALC_CACHES; | ||||
| /* Compute smoothed coordinate by taking the ones nearby */ | /* move points from last to first to new place */ | ||||
| /* XXX: This is potentially slow, and suffers from accumulation error as earlier points are handled before later ones */ | i2 = gps->totpoints - 1; | ||||
| { | for (int i = oldtotpoints - 1; i > 0; i--) { | ||||
| // XXX: this is hardcoded to look at 2 points on either side of the current one (i.e. 5 items total) | bGPDspoint *pt = &temp_points[i]; | ||||
| const int steps = 2; | bGPDspoint *pt_final = &gps->points[i2]; | ||||
| const float average_fac = 1.0f / (float)(steps * 2 + 1); | |||||
| int step; | copy_v3_v3(&pt_final->x, &pt->x); | ||||
| pt_final->pressure = pt->pressure; | |||||
| /* add the point itself */ | pt_final->strength = pt->strength; | ||||
| madd_v3_v3fl(sco, &pt->x, average_fac); | pt_final->time = pt->time; | ||||
| pt_final->flag = pt->flag; | |||||
| if (affect_pressure) { | pt_final->totweight = pt->totweight; | ||||
| pressure += pt->pressure * average_fac; | pt_final->weights = pt->weights; | ||||
| i2 -= 2; | |||||
| } | } | ||||
| /* interpolate mid points */ | |||||
| /* n-steps before/after current point */ | i2 = 1; | ||||
| // XXX: review how the endpoints are treated by this algorithm | for (int i = 0; i < oldtotpoints - 1; i++) { | ||||
| // XXX: falloff measures should also introduce some weighting variations, so that further-out points get less weight | bGPDspoint *pt = &temp_points[i]; | ||||
| for (step = 1; step <= steps; step++) { | bGPDspoint *next = &temp_points[i + 1]; | ||||
| bGPDspoint *pt1, *pt2; | bGPDspoint *pt_final = &gps->points[i2]; | ||||
| int before = i - step; | |||||
| int after = i + step; | /* add a half way point */ | ||||
| interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); | |||||
| CLAMP_MIN(before, 0); | pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f); | ||||
| CLAMP_MAX(after, gps->totpoints - 1); | pt_final->strength = interpf(pt->strength, next->strength, 0.5f); | ||||
| CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f); | |||||
| pt1 = &gps->points[before]; | pt_final->time = interpf(pt->time, next->time, 0.5f); | ||||
| pt2 = &gps->points[after]; | pt_final->totweight = 0; | ||||
| pt_final->weights = NULL; | |||||
| /* add both these points to the average-sum (s += p[i]/n) */ | i2 += 2; | ||||
| madd_v3_v3fl(sco, &pt1->x, average_fac); | |||||
| madd_v3_v3fl(sco, &pt2->x, average_fac); | |||||
| #if 0 | |||||
| /* XXX: Disabled because get weird result */ | |||||
| /* do pressure too? */ | |||||
| if (affect_pressure) { | |||||
| pressure += pt1->pressure * average_fac; | |||||
| pressure += pt2->pressure * average_fac; | |||||
| } | |||||
| #endif | |||||
| } | } | ||||
| } | |||||
| /* Based on influence factor, blend between original and optimal smoothed coordinate */ | |||||
| interp_v3_v3v3(&pt->x, &pt->x, sco, inf); | |||||
| #if 0 | |||||
| /* XXX: Disabled because get weird result */ | |||||
| if (affect_pressure) { | |||||
| pt->pressure = pressure; | |||||
| } | |||||
| #endif | |||||
| return true; | |||||
| } | |||||
| /** | MEM_SAFE_FREE(temp_points); | ||||
| * Apply smooth for strength to stroke point | |||||
| * \param gps Stroke to smooth | |||||
| * \param i Point index | |||||
| * \param inf Amount of smoothing to apply | |||||
| */ | |||||
| bool gp_smooth_stroke_strength(bGPDstroke *gps, int i, float inf) | |||||
| { | |||||
| bGPDspoint *ptb = &gps->points[i]; | |||||
| /* Do nothing if not enough points */ | /* move points to smooth stroke */ | ||||
| if (gps->totpoints <= 2) { | /* duplicate points in a temp area with the new subdivide data */ | ||||
| return false; | temp_points = MEM_dupallocN(gps->points); | ||||
| } | |||||
| /* Compute theoretical optimal value using distances */ | |||||
| bGPDspoint *pta, *ptc; | |||||
| int before = i - 1; | |||||
| int after = i + 1; | |||||
| CLAMP_MIN(before, 0); | |||||
| CLAMP_MAX(after, gps->totpoints - 1); | |||||
| pta = &gps->points[before]; | |||||
| ptc = &gps->points[after]; | |||||
| /* the optimal value is the corresponding to the interpolation of the strength | |||||
| * at the distance of point b | |||||
| */ | |||||
| const float fac = line_point_factor_v3(&ptb->x, &pta->x, &ptc->x); | |||||
| const float optimal = (1.0f - fac) * pta->strength + fac * ptc->strength; | |||||
| /* Based on influence factor, blend between original and optimal */ | |||||
| ptb->strength = (1.0f - inf) * ptb->strength + inf * optimal; | |||||
| return true; | |||||
| } | |||||
| /** | |||||
| * Apply smooth for thickness to stroke point (use pressure) | |||||
| * \param gps Stroke to smooth | |||||
| * \param i Point index | |||||
| * \param inf Amount of smoothing to apply | |||||
| */ | |||||
| bool gp_smooth_stroke_thickness(bGPDstroke *gps, int i, float inf) | |||||
| { | |||||
| bGPDspoint *ptb = &gps->points[i]; | |||||
| /* Do nothing if not enough points */ | |||||
| if (gps->totpoints <= 2) { | |||||
| return false; | |||||
| } | |||||
| /* Compute theoretical optimal value using distances */ | /* extreme points are not changed */ | ||||
| bGPDspoint *pta, *ptc; | for (int i = 0; i < gps->totpoints - 2; i++) { | ||||
| int before = i - 1; | bGPDspoint *pt = &temp_points[i]; | ||||
| int after = i + 1; | bGPDspoint *next = &temp_points[i + 1]; | ||||
| bGPDspoint *pt_final = &gps->points[i + 1]; | |||||
| CLAMP_MIN(before, 0); | /* move point */ | ||||
| CLAMP_MAX(after, gps->totpoints - 1); | interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f); | ||||
| } | |||||
| pta = &gps->points[before]; | /* free temp memory */ | ||||
| ptc = &gps->points[after]; | MEM_SAFE_FREE(temp_points); | ||||
| /* the optimal value is the corresponding to the interpolation of the pressure | |||||
| * at the distance of point b | |||||
| */ | |||||
| float fac = line_point_factor_v3(&ptb->x, &pta->x, &ptc->x); | |||||
| float optimal = (1.0f - fac) * pta->pressure + fac * ptc->pressure; | |||||
| /* Based on influence factor, blend between original and optimal */ | |||||
| ptb->pressure = (1.0f - inf) * ptb->pressure + inf * optimal; | |||||
| return true; | |||||
| } | |||||
| /** | |||||
| * Subdivide a stroke once, by adding a point half way between each pair of existing points | |||||
| * \param gps Stroke data | |||||
| * \param new_totpoints Total number of points (after subdividing) | |||||
| */ | |||||
| void gp_subdivide_stroke(bGPDstroke *gps, const int new_totpoints) | |||||
| { | |||||
| /* Move points towards end of enlarged points array to leave space for new points */ | |||||
| int y = 1; | |||||
| for (int i = gps->totpoints - 1; i > 0; i--) { | |||||
| gps->points[new_totpoints - y] = gps->points[i]; | |||||
| y += 2; | |||||
| } | |||||
| /* Create interpolated points */ | |||||
| for (int i = 0; i < new_totpoints - 1; i += 2) { | |||||
| bGPDspoint *prev = &gps->points[i]; | |||||
| bGPDspoint *pt = &gps->points[i + 1]; | |||||
| bGPDspoint *next = &gps->points[i + 2]; | |||||
| /* Interpolate all values */ | |||||
| interp_v3_v3v3(&pt->x, &prev->x, &next->x, 0.5f); | |||||
| pt->pressure = interpf(prev->pressure, next->pressure, 0.5f); | |||||
| pt->strength = interpf(prev->strength, next->strength, 0.5f); | |||||
| CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f); | |||||
| pt->time = interpf(prev->time, next->time, 0.5f); | |||||
| } | } | ||||
| /* Update to new total number of points */ | |||||
| gps->totpoints = new_totpoints; | |||||
| } | } | ||||
| /** | /** | ||||
| Context not available. | |||||
| } | } | ||||
| /* calculate difference matrix */ | /* calculate difference matrix */ | ||||
| void ED_gpencil_parent_location(bGPDlayer *gpl, float diff_mat[4][4]) | void ED_gpencil_parent_location(Object *obact, bGPdata *gpd, bGPDlayer *gpl, float diff_mat[4][4]) | ||||
| { | { | ||||
| Object *ob = gpl->parent; | Object *obparent = gpl->parent; | ||||
| if (ob == NULL) { | /* if not layer parented, try with object parented */ | ||||
| if (obparent == NULL) { | |||||
| if (obact != NULL) { | |||||
| /* the gpd can be scene, but a gpobject can be active, so need check gpd */ | |||||
| if ((obact->type == OB_GPENCIL) && (obact->data == gpd)) { | |||||
| copy_m4_m4(diff_mat, obact->obmat); | |||||
| return; | |||||
| } | |||||
| } | |||||
| /* not gpencil object */ | |||||
| unit_m4(diff_mat); | unit_m4(diff_mat); | ||||
| return; | return; | ||||
| } | } | ||||
| else { | else { | ||||
| if ((gpl->partype == PAROBJECT) || (gpl->partype == PARSKEL)) { | if ((gpl->partype == PAROBJECT) || (gpl->partype == PARSKEL)) { | ||||
| mul_m4_m4m4(diff_mat, ob->obmat, gpl->inverse); | mul_m4_m4m4(diff_mat, obparent->obmat, gpl->inverse); | ||||
| return; | return; | ||||
| } | } | ||||
| else if (gpl->partype == PARBONE) { | else if (gpl->partype == PARBONE) { | ||||
| bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, gpl->parsubstr); | bPoseChannel *pchan = BKE_pose_channel_find_name(obparent->pose, gpl->parsubstr); | ||||
| if (pchan) { | if (pchan) { | ||||
| float tmp_mat[4][4]; | float tmp_mat[4][4]; | ||||
| mul_m4_m4m4(tmp_mat, ob->obmat, pchan->pose_mat); | mul_m4_m4m4(tmp_mat, obparent->obmat, pchan->pose_mat); | ||||
| mul_m4_m4m4(diff_mat, tmp_mat, gpl->inverse); | mul_m4_m4m4(diff_mat, tmp_mat, gpl->inverse); | ||||
| } | } | ||||
| else { | else { | ||||
| mul_m4_m4m4(diff_mat, ob->obmat, gpl->inverse); /* if bone not found use object (armature) */ | mul_m4_m4m4(diff_mat, obparent->obmat, gpl->inverse); /* if bone not found use object (armature) */ | ||||
| } | } | ||||
| return; | return; | ||||
| } | } | ||||
| Context not available. | |||||
| } | } | ||||
| /* reset parent matrix for all layers */ | /* reset parent matrix for all layers */ | ||||
| void ED_gpencil_reset_layers_parent(bGPdata *gpd) | void ED_gpencil_reset_layers_parent(Object *obact, bGPdata *gpd) | ||||
| { | { | ||||
| bGPDspoint *pt; | bGPDspoint *pt; | ||||
| int i; | int i; | ||||
| Context not available. | |||||
| /* only redo if any change */ | /* only redo if any change */ | ||||
| if (!equals_m4m4(gpl->inverse, cur_mat)) { | if (!equals_m4m4(gpl->inverse, cur_mat)) { | ||||
| /* first apply current transformation to all strokes */ | /* first apply current transformation to all strokes */ | ||||
| ED_gpencil_parent_location(gpl, diff_mat); | ED_gpencil_parent_location(obact, gpd, gpl, diff_mat); | ||||
| for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { | for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) { | ||||
| for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { | ||||
| Context not available. | |||||
| return item; | return item; | ||||
| } | } | ||||
| /* Helper function to create new OB_GPENCIL Object */ | |||||
| Object *ED_add_gpencil_object(bContext *C, Scene *scene, const float loc[3]) | |||||
| { | |||||
| float rot[3]; | |||||
| zero_v3(rot); | |||||
| Object *ob = ED_object_add_type(C, OB_GPENCIL, NULL, loc, rot, false, scene->lay); | |||||
| /* define size */ | |||||
| BKE_object_obdata_size_init(ob, GP_OBGPENCIL_DEFAULT_SIZE); | |||||
| /* create default brushes and colors */ | |||||
| ED_gpencil_add_defaults(C); | |||||
| return ob; | |||||
| } | |||||
| /* Helper function to create default colors and drawing brushes */ | |||||
| void ED_gpencil_add_defaults(bContext *C) | |||||
| { | |||||
| ToolSettings *ts = CTX_data_tool_settings(C); | |||||
| bGPdata *gpd = CTX_data_gpencil_data(C); | |||||
| /* ensure palettes, colors, and palette slots exist */ | |||||
| BKE_gpencil_paletteslot_validate(CTX_data_main(C), gpd); | |||||
| /* create default brushes */ | |||||
| if (BLI_listbase_is_empty(&ts->gp_brushes)) { | |||||
| BKE_gpencil_brush_init_presets(ts); | |||||
| } | |||||
| } | |||||
| /* allocate memory for saving gp object to be sorted by zdepth */ | |||||
| tGPencilSort *ED_gpencil_allocate_cache(tGPencilSort *cache, int *gp_cache_size, int gp_cache_used) | |||||
| { | |||||
| tGPencilSort *p = NULL; | |||||
| /* By default a cache is created with one block with a predefined number of free slots, | |||||
| if the size is not enough, the cache is reallocated adding a new block of free slots. | |||||
| This is done in order to keep cache small */ | |||||
| if (gp_cache_used + 1 > *gp_cache_size) { | |||||
| if ((*gp_cache_size == 0) || (cache == NULL)) { | |||||
| p = MEM_callocN(sizeof(struct tGPencilSort) * GP_CACHE_BLOCK_SIZE, "tGPencilSort"); | |||||
| *gp_cache_size = GP_CACHE_BLOCK_SIZE; | |||||
| } | |||||
| else { | |||||
| *gp_cache_size += GP_CACHE_BLOCK_SIZE; | |||||
| p = MEM_recallocN(cache, sizeof(struct tGPencilSort) * *gp_cache_size); | |||||
| } | |||||
| cache = p; | |||||
| } | |||||
| return cache; | |||||
| } | |||||
| /* add gp object to the temporary cache for sorting */ | |||||
| void ED_gpencil_add_to_cache(tGPencilSort *cache, RegionView3D *rv3d, Base *base, int *gp_cache_used) | |||||
| { | |||||
| /* save object */ | |||||
| cache[*gp_cache_used].base = base; | |||||
| /* calculate zdepth from point of view */ | |||||
| float zdepth = 0.0; | |||||
| if (rv3d->is_persp) { | |||||
| zdepth = ED_view3d_calc_zfac(rv3d, base->object->loc, NULL); | |||||
| } | |||||
| else { | |||||
| zdepth = -dot_v3v3(rv3d->viewinv[2], base->object->loc); | |||||
| } | |||||
| cache[*gp_cache_used].zdepth = zdepth; | |||||
| /* increase slots used in cache */ | |||||
| (*gp_cache_used)++; | |||||
| } | |||||
| /* reproject the points of the stroke to a plane locked to axis to avoid stroke offset */ | |||||
| void ED_gp_project_stroke_to_plane(Object *ob, RegionView3D *rv3d, bGPDstroke *gps, const float origin[3], const int axis, char type) | |||||
| { | |||||
| float plane_normal[3]; | |||||
| float vn[3]; | |||||
| float ray[3]; | |||||
| float rpoint[3]; | |||||
| /* normal vector for a plane locked to axis */ | |||||
| zero_v3(plane_normal); | |||||
| if (axis < 0) { | |||||
| /* if the axis is not locked, need a vector to the view direction | |||||
| * in order to get the right size of the stroke. | |||||
| */ | |||||
| ED_view3d_global_to_vector(rv3d, origin, plane_normal); | |||||
| } | |||||
| else { | |||||
| plane_normal[axis] = 1.0f; | |||||
| /* if object, apply object rotation */ | |||||
| if (ob && (ob->type == OB_GPENCIL)) { | |||||
| mul_mat3_m4_v3(ob->obmat, plane_normal); | |||||
| } | |||||
| } | |||||
| /* Reproject the points in the plane */ | |||||
| for (int i = 0; i < gps->totpoints; i++) { | |||||
| bGPDspoint *pt = &gps->points[i]; | |||||
| /* get a vector from the point with the current view direction of the viewport */ | |||||
| ED_view3d_global_to_vector(rv3d, &pt->x, vn); | |||||
| /* calculate line extrem point to create a ray that cross the plane */ | |||||
| mul_v3_fl(vn, -50.0f); | |||||
| add_v3_v3v3(ray, &pt->x, vn); | |||||
| /* if the line never intersect, the point is not changed */ | |||||
| if (isect_line_plane_v3(rpoint, &pt->x, ray, origin, plane_normal)) { | |||||
| copy_v3_v3(&pt->x, rpoint); | |||||
| } | |||||
| } | |||||
| } | |||||
| /* reproject one points to a plane locked to axis to avoid stroke offset */ | |||||
| void ED_gp_project_point_to_plane(Object *ob, RegionView3D *rv3d, const float origin[3], const int axis, char type, bGPDspoint *pt) | |||||
| { | |||||
| float plane_normal[3]; | |||||
| float vn[3]; | |||||
| float ray[3]; | |||||
| float rpoint[3]; | |||||
| /* normal vector for a plane locked to axis */ | |||||
| zero_v3(plane_normal); | |||||
| if (axis < 0) { | |||||
| /* if the axis is not locked, need a vector to the view direction | |||||
| * in order to get the right size of the stroke. | |||||
| */ | |||||
| ED_view3d_global_to_vector(rv3d, origin, plane_normal); | |||||
| } | |||||
| else { | |||||
| plane_normal[axis] = 1.0f; | |||||
| /* if object, apply object rotation */ | |||||
| if (ob && (ob->type == OB_GPENCIL)) { | |||||
| mul_mat3_m4_v3(ob->obmat, plane_normal); | |||||
| } | |||||
| } | |||||
| /* Reproject the points in the plane */ | |||||
| /* get a vector from the point with the current view direction of the viewport */ | |||||
| ED_view3d_global_to_vector(rv3d, &pt->x, vn); | |||||
| /* calculate line extrem point to create a ray that cross the plane */ | |||||
| mul_v3_fl(vn, -50.0f); | |||||
| add_v3_v3v3(ray, &pt->x, vn); | |||||
| /* if the line never intersect, the point is not changed */ | |||||
| if (isect_line_plane_v3(rpoint, &pt->x, ray, origin, plane_normal)) { | |||||
| copy_v3_v3(&pt->x, rpoint); | |||||
| } | |||||
| } | |||||
| /* get drawing reference for conversion or projection of the stroke */ | |||||
| void ED_gp_get_drawing_reference(View3D *v3d, Scene *scene, Object *ob, bGPDlayer *gpl, char align_flag, float vec[3]) | |||||
| { | |||||
| const float *fp = ED_view3d_cursor3d_get(scene, v3d); | |||||
| /* if using a gpencil object at cursor mode, can use the location of the object */ | |||||
| if (align_flag & GP_PROJECT_VIEWSPACE) { | |||||
| if (ob && (ob->type == OB_GPENCIL)) { | |||||
| /* use last stroke position for layer */ | |||||
| if (gpl && gpl->flag & GP_LAYER_USE_LOCATION) { | |||||
| if (gpl->actframe) { | |||||
| bGPDframe *gpf = gpl->actframe; | |||||
| if (gpf->strokes.last) { | |||||
| bGPDstroke *gps = gpf->strokes.last; | |||||
| if (gps->totpoints > 0) { | |||||
| copy_v3_v3(vec, &gps->points[gps->totpoints - 1].x); | |||||
| mul_m4_v3(ob->obmat, vec); | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /* use cursor */ | |||||
| if (align_flag & GP_PROJECT_CURSOR) { | |||||
| /* use 3D-cursor */ | |||||
| copy_v3_v3(vec, fp); | |||||
| } | |||||
| else { | |||||
| /* use object location */ | |||||
| copy_v3_v3(vec, ob->obmat[3]); | |||||
| } | |||||
| } | |||||
| } | |||||
| else { | |||||
| /* use 3D-cursor */ | |||||
| copy_v3_v3(vec, fp); | |||||
| } | |||||
| } | |||||
| /* ******************************************************** */ | /* ******************************************************** */ | ||||
| /* Cursor drawing */ | |||||
| /* check if cursor is in drawing region */ | |||||
| static bool gp_check_cursor_region(bContext *C, int mval[2]) | |||||
| { | |||||
| ARegion *ar = CTX_wm_region(C); | |||||
| ScrArea *sa = CTX_wm_area(C); | |||||
| /* TODO: add more spacetypes */ | |||||
| if (!ELEM(sa->spacetype, SPACE_VIEW3D)) { | |||||
| return false; | |||||
| } | |||||
| if ((ar) && (ar->regiontype != RGN_TYPE_WINDOW)) { | |||||
| return false; | |||||
| } | |||||
| else if (ar) { | |||||
| rcti region_rect; | |||||
| /* Perform bounds check using */ | |||||
| ED_region_visible_rect(ar, ®ion_rect); | |||||
| return BLI_rcti_isect_pt_v(®ion_rect, mval); | |||||
| } | |||||
| else { | |||||
| return false; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /* Helper callback for drawing the cursor itself */ | |||||
| static void gp_brush_drawcursor(bContext *C, int x, int y, void *UNUSED(customdata)) | |||||
| { | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| GP_BrushEdit_Settings *gset = &scene->toolsettings->gp_sculpt; | |||||
| bGPdata *gpd = ED_gpencil_data_get_active(C); | |||||
| GP_EditBrush_Data *brush = NULL; | |||||
| if ((gpd) && (gpd->flag & GP_DATA_STROKE_WEIGHTMODE)) { | |||||
| brush = &gset->brush[gset->weighttype]; | |||||
| } | |||||
| else { | |||||
| brush = &gset->brush[gset->brushtype]; | |||||
| } | |||||
| bGPDbrush *paintbrush; | |||||
| /* default radius and color */ | |||||
| float radius = 5.0f; | |||||
| float color[3], darkcolor[3]; | |||||
| ARRAY_SET_ITEMS(color, 1.0f, 1.0f, 1.0f); | |||||
| int mval[2]; | |||||
| ARRAY_SET_ITEMS(mval, x, y); | |||||
| /* check if cursor is in drawing region and has valid datablock */ | |||||
| if ((!gp_check_cursor_region(C, mval)) || (gpd == NULL)) { | |||||
| return; | |||||
| } | |||||
| /* for paint use paint brush size and color */ | |||||
| if (gpd->flag & GP_DATA_STROKE_PAINTMODE) { | |||||
| /* while drawing hide */ | |||||
| if (gpd->sbuffer_size > 0) { | |||||
| return; | |||||
| } | |||||
| paintbrush = BKE_gpencil_brush_getactive(scene->toolsettings); | |||||
| if (paintbrush) { | |||||
| if ((paintbrush->flag & GP_BRUSH_ENABLE_CURSOR) == 0) { | |||||
| return; | |||||
| } | |||||
| /* after some testing, display the size of the brush is not practical because | |||||
| * is too disruptive and the size of cursor does not change with zoom factor. | |||||
| * The decision was to use a fix size, instead of paintbrush->thickness value. | |||||
| */ | |||||
| radius = 3.0f; | |||||
| copy_v3_v3(color, paintbrush->curcolor); | |||||
| } | |||||
| } | |||||
| /* for sculpt use sculpt brush size */ | |||||
| if (GPENCIL_SCULPT_OR_WEIGHT_MODE(gpd)) { | |||||
| if (brush) { | |||||
| if ((brush->flag & GP_EDITBRUSH_FLAG_ENABLE_CURSOR) == 0) { | |||||
| return; | |||||
| } | |||||
| radius = brush->size; | |||||
| if (brush->flag & (GP_EDITBRUSH_FLAG_INVERT | GP_EDITBRUSH_FLAG_TMP_INVERT)) { | |||||
| copy_v3_v3(color, brush->curcolor_sub); | |||||
| } | |||||
| else { | |||||
| copy_v3_v3(color, brush->curcolor_add); | |||||
| } | |||||
| } | |||||
| } | |||||
| /* draw icon */ | |||||
| Gwn_VertFormat *format = immVertexFormat(); | |||||
| unsigned int pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT); | |||||
| immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); | |||||
| glEnable(GL_LINE_SMOOTH); | |||||
| glEnable(GL_BLEND); | |||||
| /* Inner Ring: Color from UI panel */ | |||||
| immUniformColor4f(color[0], color[1], color[2], 0.8f); | |||||
| imm_draw_circle_wire_2d(pos, x, y, radius, 40); | |||||
| /* Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */ | |||||
| mul_v3_v3fl(darkcolor, color, 0.40f); | |||||
| immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f); | |||||
| imm_draw_circle_wire_2d(pos, x, y, radius + 1, 40); | |||||
| immUnbindProgram(); | |||||
| glDisable(GL_BLEND); | |||||
| glDisable(GL_LINE_SMOOTH); | |||||
| } | |||||
| /* Turn brush cursor in on/off */ | |||||
| void ED_gpencil_toggle_brush_cursor(bContext *C, bool enable) | |||||
| { | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| GP_BrushEdit_Settings *gset = &scene->toolsettings->gp_sculpt; | |||||
| if (gset->paintcursor && !enable) { | |||||
| /* clear cursor */ | |||||
| WM_paint_cursor_end(CTX_wm_manager(C), gset->paintcursor); | |||||
| gset->paintcursor = NULL; | |||||
| } | |||||
| else if (enable) { | |||||
| /* in some situations cursor could be duplicated, so it is better disable first if exist */ | |||||
| if (gset->paintcursor) { | |||||
| /* clear cursor */ | |||||
| WM_paint_cursor_end(CTX_wm_manager(C), gset->paintcursor); | |||||
| gset->paintcursor = NULL; | |||||
| } | |||||
| /* enable cursor */ | |||||
| gset->paintcursor = WM_paint_cursor_activate(CTX_wm_manager(C), | |||||
| NULL, | |||||
| gp_brush_drawcursor, NULL); | |||||
| } | |||||
| } | |||||
| /* assign points to vertex group */ | |||||
| void ED_gpencil_vgroup_assign(bContext *C, Object *ob, float weight) | |||||
| { | |||||
| bGPDspoint *pt; | |||||
| const int def_nr = ob->actdef - 1; | |||||
| if (!BLI_findlink(&ob->defbase, def_nr)) | |||||
| return; | |||||
| CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) | |||||
| { | |||||
| if (gps->flag & GP_STROKE_SELECT) { | |||||
| for (int i = 0; i < gps->totpoints; ++i) { | |||||
| pt = &gps->points[i]; | |||||
| if (pt->flag & GP_SPOINT_SELECT) { | |||||
| BKE_gpencil_vgroup_add_point_weight(pt, def_nr, weight); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| CTX_DATA_END; | |||||
| } | |||||
| /* remove points from vertex group */ | |||||
| void ED_gpencil_vgroup_remove(bContext *C, Object *ob) | |||||
| { | |||||
| bGPDspoint *pt; | |||||
| const int def_nr = ob->actdef - 1; | |||||
| if (!BLI_findlink(&ob->defbase, def_nr)) | |||||
| return; | |||||
| CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) | |||||
| { | |||||
| for (int i = 0; i < gps->totpoints; ++i) { | |||||
| pt = &gps->points[i]; | |||||
| if ((pt->flag & GP_SPOINT_SELECT) && (pt->totweight > 0)) { | |||||
| BKE_gpencil_vgroup_remove_point_weight(pt, def_nr); | |||||
| } | |||||
| } | |||||
| } | |||||
| CTX_DATA_END; | |||||
| } | |||||
| /* select points of vertex group */ | |||||
| void ED_gpencil_vgroup_select(bContext *C, Object *ob) | |||||
| { | |||||
| bGPDspoint *pt; | |||||
| const int def_nr = ob->actdef - 1; | |||||
| if (!BLI_findlink(&ob->defbase, def_nr)) | |||||
| return; | |||||
| CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) | |||||
| { | |||||
| for (int i = 0; i < gps->totpoints; ++i) { | |||||
| pt = &gps->points[i]; | |||||
| if (BKE_gpencil_vgroup_use_index(pt, def_nr) > -1.0f) { | |||||
| pt->flag |= GP_SPOINT_SELECT; | |||||
| gps->flag |= GP_STROKE_SELECT; | |||||
| } | |||||
| } | |||||
| } | |||||
| CTX_DATA_END; | |||||
| } | |||||
| /* unselect points of vertex group */ | |||||
| void ED_gpencil_vgroup_deselect(bContext *C, Object *ob) | |||||
| { | |||||
| bGPDspoint *pt; | |||||
| const int def_nr = ob->actdef - 1; | |||||
| if (!BLI_findlink(&ob->defbase, def_nr)) | |||||
| return; | |||||
| CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes) | |||||
| { | |||||
| for (int i = 0; i < gps->totpoints; ++i) { | |||||
| pt = &gps->points[i]; | |||||
| if (BKE_gpencil_vgroup_use_index(pt, def_nr) > -1.0f) { | |||||
| pt->flag &= ~GP_SPOINT_SELECT; | |||||
| gps->flag |= GP_STROKE_SELECT; | |||||
| } | |||||
| } | |||||
| } | |||||
| CTX_DATA_END; | |||||
| } | |||||
| Context not available. | |||||