/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup edcurve */ #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_math.h" #include "BLI_mempool.h" #include "BKE_context.h" #include "BKE_curve.h" #include "BKE_fcurve.h" #include "BKE_report.h" #include "DEG_depsgraph.h" #include "WM_api.h" #include "WM_types.h" #include "ED_curve.h" #include "ED_screen.h" #include "ED_space_api.h" #include "ED_view3d.h" #include "GPU_batch.h" #include "GPU_batch_presets.h" #include "GPU_immediate.h" #include "GPU_immediate_util.h" #include "GPU_matrix.h" #include "GPU_state.h" #include "curve_intern.h" #include "UI_resources.h" #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" #define USE_SPLINE_FIT #ifdef USE_SPLINE_FIT # include "curve_fit_nd.h" #endif /* Distance between input samples */ #define STROKE_SAMPLE_DIST_MIN_PX 1 #define STROKE_SAMPLE_DIST_MAX_PX 3 /* Distance between start/end points to consider cyclic */ #define STROKE_CYCLIC_DIST_PX 8 /* -------------------------------------------------------------------- */ /** \name StrokeElem / #RNA_OperatorStrokeElement Conversion Functions * \{ */ struct StrokeElem { float mval[2]; float location_world[3]; float location_local[3]; /* surface normal, may be zero'd */ float normal_world[3]; float normal_local[3]; float pressure; }; struct CurveDrawData { short init_event_type; short curve_type; /* projecting 2D into 3D space */ struct { /* use a plane or project to the surface */ bool use_plane; float plane[4]; /* use 'rv3d->depths', note that this will become 'damaged' while drawing, but that's OK. */ bool use_depth; /* offset projection by this value */ bool use_offset; float offset[3]; /* world-space */ float surface_offset; bool use_surface_offset_absolute; } project; /* cursor sampling */ struct { /* use substeps, needed for nicely interpolating depth */ bool use_substeps; } sample; struct { float min, max, range; } radius; struct { float mouse[2]; /* Used in case we can't calculate the depth. */ float location_world[3]; float location_world_valid[3]; const struct StrokeElem *selem; } prev; ViewContext vc; ViewDepths *depths; enum { CURVE_DRAW_IDLE = 0, CURVE_DRAW_PAINTING = 1, } state; /* StrokeElem */ BLI_mempool *stroke_elem_pool; void *draw_handle_view; }; static float stroke_elem_radius_from_pressure(const struct CurveDrawData *cdd, const float pressure) { const Curve *cu = cdd->vc.obedit->data; return ((pressure * cdd->radius.range) + cdd->radius.min) * cu->bevel_radius; } static float stroke_elem_radius(const struct CurveDrawData *cdd, const struct StrokeElem *selem) { return stroke_elem_radius_from_pressure(cdd, selem->pressure); } static void stroke_elem_pressure_set(const struct CurveDrawData *cdd, struct StrokeElem *selem, float pressure) { if ((cdd->project.surface_offset != 0.0f) && !cdd->project.use_surface_offset_absolute && !is_zero_v3(selem->normal_local)) { const float adjust = stroke_elem_radius_from_pressure(cdd, pressure) - stroke_elem_radius_from_pressure(cdd, selem->pressure); madd_v3_v3fl(selem->location_local, selem->normal_local, adjust); mul_v3_m4v3(selem->location_world, cdd->vc.obedit->obmat, selem->location_local); } selem->pressure = pressure; } static void stroke_elem_interp(struct StrokeElem *selem_out, const struct StrokeElem *selem_a, const struct StrokeElem *selem_b, float t) { interp_v2_v2v2(selem_out->mval, selem_a->mval, selem_b->mval, t); interp_v3_v3v3(selem_out->location_world, selem_a->location_world, selem_b->location_world, t); interp_v3_v3v3(selem_out->location_local, selem_a->location_local, selem_b->location_local, t); selem_out->pressure = interpf(selem_a->pressure, selem_b->pressure, t); } /** * Sets the depth from #StrokeElem.mval */ static bool stroke_elem_project(const struct CurveDrawData *cdd, const int mval_i[2], const float mval_fl[2], float surface_offset, const float radius, float r_location_world[3], float r_normal_world[3]) { ARegion *region = cdd->vc.region; bool is_location_world_set = false; /* project to 'location_world' */ if (cdd->project.use_plane) { /* get the view vector to 'location' */ if (ED_view3d_win_to_3d_on_plane( region, cdd->project.plane, mval_fl, true, r_location_world)) { if (r_normal_world) { zero_v3(r_normal_world); } is_location_world_set = true; } } else { const ViewDepths *depths = cdd->depths; if (depths && ((uint)mval_i[0] < depths->w) && ((uint)mval_i[1] < depths->h)) { float depth_fl = 1.0f; ED_view3d_depth_read_cached(depths, mval_i, 0, &depth_fl); const double depth = (double)depth_fl; if ((depth > depths->depth_range[0]) && (depth < depths->depth_range[1])) { if (ED_view3d_depth_unproject_v3(region, mval_i, depth, r_location_world)) { is_location_world_set = true; if (r_normal_world) { zero_v3(r_normal_world); } if (surface_offset != 0.0f) { const float offset = cdd->project.use_surface_offset_absolute ? 1.0f : radius; float normal[3]; if (ED_view3d_depth_read_cached_normal(region, depths, mval_i, normal)) { madd_v3_v3fl(r_location_world, normal, offset * surface_offset); if (r_normal_world) { copy_v3_v3(r_normal_world, normal); } } } } } } } if (is_location_world_set) { if (cdd->project.use_offset) { add_v3_v3(r_location_world, cdd->project.offset); } } return is_location_world_set; } static bool stroke_elem_project_fallback(const struct CurveDrawData *cdd, const int mval_i[2], const float mval_fl[2], const float surface_offset, const float radius, const float location_fallback_depth[3], float r_location_world[3], float r_location_local[3], float r_normal_world[3], float r_normal_local[3]) { bool is_depth_found = stroke_elem_project( cdd, mval_i, mval_fl, surface_offset, radius, r_location_world, r_normal_world); if (is_depth_found == false) { ED_view3d_win_to_3d( cdd->vc.v3d, cdd->vc.region, location_fallback_depth, mval_fl, r_location_world); zero_v3(r_normal_local); } mul_v3_m4v3(r_location_local, cdd->vc.obedit->imat, r_location_world); if (!is_zero_v3(r_normal_world)) { copy_v3_v3(r_normal_local, r_normal_world); mul_transposed_mat3_m4_v3(cdd->vc.obedit->obmat, r_normal_local); normalize_v3(r_normal_local); } else { zero_v3(r_normal_local); } return is_depth_found; } /** * \note #StrokeElem.mval & #StrokeElem.pressure must be set first. */ static bool stroke_elem_project_fallback_elem(const struct CurveDrawData *cdd, const float location_fallback_depth[3], struct StrokeElem *selem) { const int mval_i[2] = {UNPACK2(selem->mval)}; const float radius = stroke_elem_radius(cdd, selem); return stroke_elem_project_fallback(cdd, mval_i, selem->mval, cdd->project.surface_offset, radius, location_fallback_depth, selem->location_world, selem->location_local, selem->normal_world, selem->normal_local); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Operator/Stroke Conversion * \{ */ static void curve_draw_stroke_to_operator_elem(wmOperator *op, const struct StrokeElem *selem) { PointerRNA itemptr; RNA_collection_add(op->ptr, "stroke", &itemptr); RNA_float_set_array(&itemptr, "mouse", selem->mval); RNA_float_set_array(&itemptr, "location", selem->location_world); RNA_float_set(&itemptr, "pressure", selem->pressure); } static void curve_draw_stroke_from_operator_elem(wmOperator *op, PointerRNA *itemptr) { struct CurveDrawData *cdd = op->customdata; struct StrokeElem *selem = BLI_mempool_calloc(cdd->stroke_elem_pool); RNA_float_get_array(itemptr, "mouse", selem->mval); RNA_float_get_array(itemptr, "location", selem->location_world); mul_v3_m4v3(selem->location_local, cdd->vc.obedit->imat, selem->location_world); selem->pressure = RNA_float_get(itemptr, "pressure"); } static void curve_draw_stroke_to_operator(wmOperator *op) { struct CurveDrawData *cdd = op->customdata; BLI_mempool_iter iter; const struct StrokeElem *selem; BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) { curve_draw_stroke_to_operator_elem(op, selem); } } static void curve_draw_stroke_from_operator(wmOperator *op) { RNA_BEGIN (op->ptr, itemptr, "stroke") { curve_draw_stroke_from_operator_elem(op, &itemptr); } RNA_END; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Operator Callbacks & Helpers * \{ */ static void curve_draw_stroke_3d(const struct bContext *UNUSED(C), ARegion *UNUSED(region), void *arg) { wmOperator *op = arg; struct CurveDrawData *cdd = op->customdata; const int stroke_len = BLI_mempool_len(cdd->stroke_elem_pool); if (stroke_len == 0) { return; } Object *obedit = cdd->vc.obedit; Curve *cu = obedit->data; if (cu->bevel_radius > 0.0f) { BLI_mempool_iter iter; const struct StrokeElem *selem; const float location_zero[3] = {0}; const float *location_prev = location_zero; float color[3]; UI_GetThemeColor3fv(TH_WIRE, color); GPUBatch *sphere = GPU_batch_preset_sphere(0); GPU_batch_program_set_builtin(sphere, GPU_SHADER_3D_UNIFORM_COLOR); GPU_batch_uniform_3fv(sphere, "color", color); /* scale to edit-mode space */ GPU_matrix_push(); GPU_matrix_mul(obedit->obmat); BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) { GPU_matrix_translate_3f(selem->location_local[0] - location_prev[0], selem->location_local[1] - location_prev[1], selem->location_local[2] - location_prev[2]); const float radius = stroke_elem_radius(cdd, selem); GPU_matrix_push(); GPU_matrix_scale_1f(radius); GPU_batch_draw(sphere); GPU_matrix_pop(); location_prev = selem->location_local; } GPU_matrix_pop(); } if (stroke_len > 1) { float(*coord_array)[3] = MEM_mallocN(sizeof(*coord_array) * stroke_len, __func__); { BLI_mempool_iter iter; const struct StrokeElem *selem; int i; BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); for (selem = BLI_mempool_iterstep(&iter), i = 0; selem; selem = BLI_mempool_iterstep(&iter), i++) { copy_v3_v3(coord_array[i], selem->location_world); } } { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_depth_test(GPU_DEPTH_NONE); GPU_blend(GPU_BLEND_ALPHA); GPU_line_smooth(true); GPU_line_width(3.0f); imm_cpack(0x0); immBegin(GPU_PRIM_LINE_STRIP, stroke_len); for (int i = 0; i < stroke_len; i++) { immVertex3fv(pos, coord_array[i]); } immEnd(); GPU_line_width(1.0f); imm_cpack(0xffffffff); immBegin(GPU_PRIM_LINE_STRIP, stroke_len); for (int i = 0; i < stroke_len; i++) { immVertex3fv(pos, coord_array[i]); } immEnd(); /* Reset defaults */ GPU_depth_test(GPU_DEPTH_LESS_EQUAL); GPU_blend(GPU_BLEND_NONE); GPU_line_smooth(false); immUnbindProgram(); } MEM_freeN(coord_array); } } static void curve_draw_event_add(wmOperator *op, const wmEvent *event) { struct CurveDrawData *cdd = op->customdata; Object *obedit = cdd->vc.obedit; invert_m4_m4(obedit->imat, obedit->obmat); struct StrokeElem *selem = BLI_mempool_calloc(cdd->stroke_elem_pool); ARRAY_SET_ITEMS(selem->mval, event->mval[0], event->mval[1]); /* handle pressure sensitivity (which is supplied by tablets or otherwise 1.0) */ selem->pressure = event->tablet.pressure; bool is_depth_found = stroke_elem_project_fallback_elem( cdd, cdd->prev.location_world_valid, selem); if (is_depth_found) { /* use the depth if a fallback wasn't used */ copy_v3_v3(cdd->prev.location_world_valid, selem->location_world); } copy_v3_v3(cdd->prev.location_world, selem->location_world); float len_sq = len_squared_v2v2(cdd->prev.mouse, selem->mval); copy_v2_v2(cdd->prev.mouse, selem->mval); if (cdd->sample.use_substeps && cdd->prev.selem) { const struct StrokeElem selem_target = *selem; struct StrokeElem *selem_new_last = selem; if (len_sq >= square_f(STROKE_SAMPLE_DIST_MAX_PX)) { int n = (int)ceil(sqrt((double)len_sq)) / STROKE_SAMPLE_DIST_MAX_PX; for (int i = 1; i < n; i++) { struct StrokeElem *selem_new = selem_new_last; stroke_elem_interp(selem_new, cdd->prev.selem, &selem_target, (float)i / n); const bool is_depth_found_substep = stroke_elem_project_fallback_elem( cdd, cdd->prev.location_world_valid, selem_new); if (is_depth_found == false) { if (is_depth_found_substep) { copy_v3_v3(cdd->prev.location_world_valid, selem_new->location_world); } } selem_new_last = BLI_mempool_calloc(cdd->stroke_elem_pool); } } selem = selem_new_last; *selem_new_last = selem_target; } cdd->prev.selem = selem; ED_region_tag_redraw(cdd->vc.region); } static void curve_draw_event_add_first(wmOperator *op, const wmEvent *event) { struct CurveDrawData *cdd = op->customdata; const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings; /* add first point */ curve_draw_event_add(op, event); if ((cps->depth_mode == CURVE_PAINT_PROJECT_SURFACE) && cdd->project.use_depth && (cps->flag & CURVE_PAINT_FLAG_DEPTH_STROKE_ENDPOINTS)) { RegionView3D *rv3d = cdd->vc.rv3d; cdd->project.use_depth = false; cdd->project.use_plane = true; float normal[3] = {0.0f}; if (ELEM(cps->surface_plane, CURVE_PAINT_SURFACE_PLANE_NORMAL_VIEW, CURVE_PAINT_SURFACE_PLANE_NORMAL_SURFACE)) { if (ED_view3d_depth_read_cached_normal(cdd->vc.region, cdd->depths, event->mval, normal)) { if (cps->surface_plane == CURVE_PAINT_SURFACE_PLANE_NORMAL_VIEW) { float cross_a[3], cross_b[3]; cross_v3_v3v3(cross_a, rv3d->viewinv[2], normal); cross_v3_v3v3(cross_b, normal, cross_a); copy_v3_v3(normal, cross_b); } } } /* CURVE_PAINT_SURFACE_PLANE_VIEW or fallback */ if (is_zero_v3(normal)) { copy_v3_v3(normal, rv3d->viewinv[2]); } normalize_v3_v3(cdd->project.plane, normal); cdd->project.plane[3] = -dot_v3v3(cdd->project.plane, cdd->prev.location_world_valid); /* Special case for when we only have offset applied on the first-hit, * the remaining stroke must be offset too. */ if (cdd->project.surface_offset != 0.0f) { const float mval_fl[2] = {UNPACK2(event->mval)}; float location_no_offset[3]; if (stroke_elem_project(cdd, event->mval, mval_fl, 0.0f, 0.0f, location_no_offset, NULL)) { sub_v3_v3v3(cdd->project.offset, cdd->prev.location_world_valid, location_no_offset); if (!is_zero_v3(cdd->project.offset)) { cdd->project.use_offset = true; } } } /* end special case */ } cdd->init_event_type = event->type; cdd->state = CURVE_DRAW_PAINTING; } static bool curve_draw_init(bContext *C, wmOperator *op, bool is_invoke) { BLI_assert(op->customdata == NULL); struct CurveDrawData *cdd = MEM_callocN(sizeof(*cdd), __func__); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); if (is_invoke) { ED_view3d_viewcontext_init(C, &cdd->vc, depsgraph); if (ELEM(NULL, cdd->vc.region, cdd->vc.rv3d, cdd->vc.v3d, cdd->vc.win, cdd->vc.scene)) { MEM_freeN(cdd); BKE_report(op->reports, RPT_ERROR, "Unable to access 3D viewport"); return false; } } else { cdd->vc.bmain = CTX_data_main(C); cdd->vc.depsgraph = depsgraph; cdd->vc.scene = CTX_data_scene(C); cdd->vc.view_layer = CTX_data_view_layer(C); cdd->vc.obedit = CTX_data_edit_object(C); } op->customdata = cdd; const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings; cdd->curve_type = cps->curve_type; cdd->radius.min = cps->radius_min; cdd->radius.max = cps->radius_max; cdd->radius.range = cps->radius_max - cps->radius_min; cdd->project.surface_offset = cps->surface_offset; cdd->project.use_surface_offset_absolute = (cps->flag & CURVE_PAINT_FLAG_DEPTH_STROKE_OFFSET_ABS) != 0; cdd->stroke_elem_pool = BLI_mempool_create( sizeof(struct StrokeElem), 0, 512, BLI_MEMPOOL_ALLOW_ITER); return true; } static void curve_draw_exit(wmOperator *op) { struct CurveDrawData *cdd = op->customdata; if (cdd) { if (cdd->draw_handle_view) { ED_region_draw_cb_exit(cdd->vc.region->type, cdd->draw_handle_view); WM_cursor_modal_restore(cdd->vc.win); } if (cdd->stroke_elem_pool) { BLI_mempool_destroy(cdd->stroke_elem_pool); } if (cdd->depths) { ED_view3d_depths_free(cdd->depths); } MEM_freeN(cdd); op->customdata = NULL; } } /** * Initialize values before calling 'exec' (when running interactively). */ static void curve_draw_exec_precalc(wmOperator *op) { struct CurveDrawData *cdd = op->customdata; const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings; PropertyRNA *prop; prop = RNA_struct_find_property(op->ptr, "fit_method"); if (!RNA_property_is_set(op->ptr, prop)) { RNA_property_enum_set(op->ptr, prop, cps->fit_method); } prop = RNA_struct_find_property(op->ptr, "corner_angle"); if (!RNA_property_is_set(op->ptr, prop)) { const float corner_angle = (cps->flag & CURVE_PAINT_FLAG_CORNERS_DETECT) ? cps->corner_angle : (float)M_PI; RNA_property_float_set(op->ptr, prop, corner_angle); } prop = RNA_struct_find_property(op->ptr, "error_threshold"); if (!RNA_property_is_set(op->ptr, prop)) { /* Error isn't set so we'll have to calculate it from the pixel values. */ BLI_mempool_iter iter; const struct StrokeElem *selem, *selem_prev; float len_3d = 0.0f, len_2d = 0.0f; float scale_px; /* pixel to local space scale */ int i = 0; BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); selem_prev = BLI_mempool_iterstep(&iter); for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter), i++) { len_3d += len_v3v3(selem->location_local, selem_prev->location_local); len_2d += len_v2v2(selem->mval, selem_prev->mval); selem_prev = selem; } scale_px = ((len_3d > 0.0f) && (len_2d > 0.0f)) ? (len_3d / len_2d) : 0.0f; float error_threshold = (cps->error_threshold * U.dpi_fac) * scale_px; RNA_property_float_set(op->ptr, prop, error_threshold); } prop = RNA_struct_find_property(op->ptr, "use_cyclic"); if (!RNA_property_is_set(op->ptr, prop)) { bool use_cyclic = false; if (BLI_mempool_len(cdd->stroke_elem_pool) > 2) { BLI_mempool_iter iter; const struct StrokeElem *selem, *selem_first, *selem_last; BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); selem_first = selem_last = BLI_mempool_iterstep(&iter); for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) { selem_last = selem; } if (len_squared_v2v2(selem_first->mval, selem_last->mval) <= square_f(STROKE_CYCLIC_DIST_PX * U.dpi_fac)) { use_cyclic = true; } } RNA_property_boolean_set(op->ptr, prop, use_cyclic); } if ((cps->radius_taper_start != 0.0f) || (cps->radius_taper_end != 0.0f)) { /* NOTE: we could try to de-duplicate the length calculations above. */ const int stroke_len = BLI_mempool_len(cdd->stroke_elem_pool); BLI_mempool_iter iter; struct StrokeElem *selem, *selem_prev; float *lengths = MEM_mallocN(sizeof(float) * stroke_len, __func__); struct StrokeElem **selem_array = MEM_mallocN(sizeof(*selem_array) * stroke_len, __func__); lengths[0] = 0.0f; float len_3d = 0.0f; int i = 1; BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); selem_prev = BLI_mempool_iterstep(&iter); selem_array[0] = selem_prev; for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter), i++) { const float len_3d_segment = len_v3v3(selem->location_local, selem_prev->location_local); len_3d += len_3d_segment; lengths[i] = len_3d; selem_array[i] = selem; selem_prev = selem; } if (cps->radius_taper_start != 0.0f) { const float len_taper_max = cps->radius_taper_start * len_3d; for (i = 0; i < stroke_len && lengths[i] < len_taper_max; i++) { const float pressure_new = selem_array[i]->pressure * (lengths[i] / len_taper_max); stroke_elem_pressure_set(cdd, selem_array[i], pressure_new); } } if (cps->radius_taper_end != 0.0f) { const float len_taper_max = cps->radius_taper_end * len_3d; const float len_taper_min = len_3d - len_taper_max; for (i = stroke_len - 1; i > 0 && lengths[i] > len_taper_min; i--) { const float pressure_new = selem_array[i]->pressure * ((len_3d - lengths[i]) / len_taper_max); stroke_elem_pressure_set(cdd, selem_array[i], pressure_new); } } MEM_freeN(lengths); MEM_freeN(selem_array); } } static int curve_draw_exec(bContext *C, wmOperator *op) { if (op->customdata == NULL) { if (!curve_draw_init(C, op, false)) { return OPERATOR_CANCELLED; } } struct CurveDrawData *cdd = op->customdata; const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings; Object *obedit = cdd->vc.obedit; Curve *cu = obedit->data; ListBase *nurblist = object_editcurve_get(obedit); int stroke_len = BLI_mempool_len(cdd->stroke_elem_pool); const bool is_3d = (cu->flag & CU_3D) != 0; invert_m4_m4(obedit->imat, obedit->obmat); if (BLI_mempool_len(cdd->stroke_elem_pool) == 0) { curve_draw_stroke_from_operator(op); stroke_len = BLI_mempool_len(cdd->stroke_elem_pool); } /* Deselect all existing curves. */ ED_curve_deselect_all_multi(C); const float radius_min = cps->radius_min; const float radius_max = cps->radius_max; const float radius_range = cps->radius_max - cps->radius_min; Nurb *nu = MEM_callocN(sizeof(Nurb), __func__); nu->pntsv = 0; nu->resolu = cu->resolu; nu->resolv = cu->resolv; nu->flag |= CU_SMOOTH; const bool use_pressure_radius = (cps->flag & CURVE_PAINT_FLAG_PRESSURE_RADIUS) || ((cps->radius_taper_start != 0.0f) || (cps->radius_taper_end != 0.0f)); if (cdd->curve_type == CU_BEZIER) { nu->type = CU_BEZIER; #ifdef USE_SPLINE_FIT /* Allow to interpolate multiple channels */ int dims = 3; struct { int radius; } coords_indices; coords_indices.radius = use_pressure_radius ? dims++ : -1; float *coords = MEM_mallocN(sizeof(*coords) * stroke_len * dims, __func__); float *cubic_spline = NULL; uint cubic_spline_len = 0; /* error in object local space */ const int fit_method = RNA_enum_get(op->ptr, "fit_method"); const float error_threshold = RNA_float_get(op->ptr, "error_threshold"); const float corner_angle = RNA_float_get(op->ptr, "corner_angle"); const bool use_cyclic = RNA_boolean_get(op->ptr, "use_cyclic"); { BLI_mempool_iter iter; const struct StrokeElem *selem; float *co = coords; BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter), co += dims) { copy_v3_v3(co, selem->location_local); if (coords_indices.radius != -1) { co[coords_indices.radius] = selem->pressure; } /* remove doubles */ if ((co != coords) && UNLIKELY(memcmp(co, co - dims, sizeof(float) * dims) == 0)) { co -= dims; stroke_len--; } } } uint *corners = NULL; uint corners_len = 0; if ((fit_method == CURVE_PAINT_FIT_METHOD_SPLIT) && (corner_angle < (float)M_PI)) { /* this could be configurable... */ const float corner_radius_min = error_threshold / 8; const float corner_radius_max = error_threshold * 2; const uint samples_max = 16; curve_fit_corners_detect_fl(coords, stroke_len, dims, corner_radius_min, corner_radius_max, samples_max, corner_angle, &corners, &corners_len); } uint *corners_index = NULL; uint corners_index_len = 0; uint calc_flag = CURVE_FIT_CALC_HIGH_QUALIY; if ((stroke_len > 2) && use_cyclic) { calc_flag |= CURVE_FIT_CALC_CYCLIC; } int result; if (fit_method == CURVE_PAINT_FIT_METHOD_REFIT) { result = curve_fit_cubic_to_points_refit_fl(coords, stroke_len, dims, error_threshold, calc_flag, NULL, 0, corner_angle, &cubic_spline, &cubic_spline_len, NULL, &corners_index, &corners_index_len); } else { result = curve_fit_cubic_to_points_fl(coords, stroke_len, dims, error_threshold, calc_flag, corners, corners_len, &cubic_spline, &cubic_spline_len, NULL, &corners_index, &corners_index_len); } MEM_freeN(coords); if (corners) { free(corners); } if (result == 0) { nu->pntsu = cubic_spline_len; nu->bezt = MEM_callocN(sizeof(BezTriple) * nu->pntsu, __func__); float *co = cubic_spline; BezTriple *bezt = nu->bezt; for (int j = 0; j < cubic_spline_len; j++, bezt++, co += (dims * 3)) { const float *handle_l = co + (dims * 0); const float *pt = co + (dims * 1); const float *handle_r = co + (dims * 2); copy_v3_v3(bezt->vec[0], handle_l); copy_v3_v3(bezt->vec[1], pt); copy_v3_v3(bezt->vec[2], handle_r); if (coords_indices.radius != -1) { bezt->radius = (pt[coords_indices.radius] * cdd->radius.range) + cdd->radius.min; } else { bezt->radius = radius_max; } bezt->h1 = bezt->h2 = HD_ALIGN; /* will set to free in second pass */ bezt->f1 = bezt->f2 = bezt->f3 = SELECT; } if (corners_index) { /* ignore the first and last */ uint i_start = 0, i_end = corners_index_len; if ((corners_index_len >= 2) && (calc_flag & CURVE_FIT_CALC_CYCLIC) == 0) { i_start += 1; i_end -= 1; } for (uint i = i_start; i < i_end; i++) { bezt = &nu->bezt[corners_index[i]]; bezt->h1 = bezt->h2 = HD_FREE; } } if (calc_flag & CURVE_FIT_CALC_CYCLIC) { nu->flagu |= CU_NURB_CYCLIC; } } if (corners_index) { free(corners_index); } if (cubic_spline) { free(cubic_spline); } #else nu->pntsu = stroke_len; nu->bezt = MEM_callocN(nu->pntsu * sizeof(BezTriple), __func__); BezTriple *bezt = nu->bezt; { BLI_mempool_iter iter; const struct StrokeElem *selem; BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) { copy_v3_v3(bezt->vec[1], selem->location_local); if (!is_3d) { bezt->vec[1][2] = 0.0f; } if (use_pressure_radius) { bezt->radius = selem->pressure; } else { bezt->radius = radius_max; } bezt->h1 = bezt->h2 = HD_AUTO; bezt->f1 |= SELECT; bezt->f2 |= SELECT; bezt->f3 |= SELECT; bezt++; } } #endif BKE_nurb_handles_calc(nu); } else { /* CU_POLY */ BLI_mempool_iter iter; const struct StrokeElem *selem; nu->pntsu = stroke_len; nu->pntsv = 1; nu->type = CU_POLY; nu->bp = MEM_callocN(nu->pntsu * sizeof(BPoint), __func__); /* Misc settings. */ nu->resolu = cu->resolu; nu->resolv = 1; nu->orderu = 4; nu->orderv = 1; BPoint *bp = nu->bp; BLI_mempool_iternew(cdd->stroke_elem_pool, &iter); for (selem = BLI_mempool_iterstep(&iter); selem; selem = BLI_mempool_iterstep(&iter)) { copy_v3_v3(bp->vec, selem->location_local); if (!is_3d) { bp->vec[2] = 0.0f; } if (use_pressure_radius) { bp->radius = (selem->pressure * radius_range) + radius_min; } else { bp->radius = cps->radius_max; } bp->f1 = SELECT; bp->vec[3] = 1.0f; bp++; } BKE_nurb_knot_calc_u(nu); } BLI_addtail(nurblist, nu); BKE_curve_nurb_active_set(cu, nu); cu->actvert = nu->pntsu - 1; WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, 0); curve_draw_exit(op); return OPERATOR_FINISHED; } static int curve_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event) { if (RNA_struct_property_is_set(op->ptr, "stroke")) { return curve_draw_exec(C, op); } if (!curve_draw_init(C, op, true)) { return OPERATOR_CANCELLED; } struct CurveDrawData *cdd = op->customdata; const CurvePaintSettings *cps = &cdd->vc.scene->toolsettings->curve_paint_settings; const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input"); /* Fallback (in case we can't find the depth on first test). */ { const float mval_fl[2] = {UNPACK2(event->mval)}; float center[3]; negate_v3_v3(center, cdd->vc.rv3d->ofs); ED_view3d_win_to_3d(cdd->vc.v3d, cdd->vc.region, center, mval_fl, cdd->prev.location_world); copy_v3_v3(cdd->prev.location_world_valid, cdd->prev.location_world); } cdd->draw_handle_view = ED_region_draw_cb_activate( cdd->vc.region->type, curve_draw_stroke_3d, op, REGION_DRAW_POST_VIEW); WM_cursor_modal_set(cdd->vc.win, WM_CURSOR_PAINT_BRUSH); { View3D *v3d = cdd->vc.v3d; RegionView3D *rv3d = cdd->vc.rv3d; Object *obedit = cdd->vc.obedit; Curve *cu = obedit->data; const float *plane_no = NULL; const float *plane_co = NULL; if (CU_IS_2D(cu)) { /* 2D overrides other options */ plane_co = obedit->obmat[3]; plane_no = obedit->obmat[2]; cdd->project.use_plane = true; } else { if ((cps->depth_mode == CURVE_PAINT_PROJECT_SURFACE) && (v3d->shading.type > OB_WIRE)) { /* needed or else the draw matrix can be incorrect */ view3d_operator_needs_opengl(C); ED_view3d_depth_override(cdd->vc.depsgraph, cdd->vc.region, cdd->vc.v3d, NULL, V3D_DEPTH_NO_GPENCIL, &cdd->depths); if (cdd->depths != NULL) { cdd->project.use_depth = true; } else { BKE_report(op->reports, RPT_WARNING, "Unable to access depth buffer, using view plane"); cdd->project.use_depth = false; } } /* use view plane (when set or as fallback when surface can't be found) */ if (cdd->project.use_depth == false) { plane_co = cdd->vc.scene->cursor.location; plane_no = rv3d->viewinv[2]; cdd->project.use_plane = true; } if (cdd->project.use_depth && (cdd->curve_type != CU_POLY)) { cdd->sample.use_substeps = true; } } if (cdd->project.use_plane) { normalize_v3_v3(cdd->project.plane, plane_no); cdd->project.plane[3] = -dot_v3v3(cdd->project.plane, plane_co); } } if (is_modal == false) { curve_draw_event_add_first(op, event); } /* add temp handler */ WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; } static void curve_draw_cancel(bContext *UNUSED(C), wmOperator *op) { curve_draw_exit(op); } /* Modal event handling of frame changing */ static int curve_draw_modal(bContext *C, wmOperator *op, const wmEvent *event) { int ret = OPERATOR_RUNNING_MODAL; struct CurveDrawData *cdd = op->customdata; UNUSED_VARS(C, op); if (event->type == cdd->init_event_type) { if (event->val == KM_RELEASE) { ED_region_tag_redraw(cdd->vc.region); curve_draw_exec_precalc(op); curve_draw_stroke_to_operator(op); curve_draw_exec(C, op); return OPERATOR_FINISHED; } } else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { ED_region_tag_redraw(cdd->vc.region); curve_draw_cancel(C, op); return OPERATOR_CANCELLED; } else if (ELEM(event->type, LEFTMOUSE)) { if (event->val == KM_PRESS) { curve_draw_event_add_first(op, event); } } else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { if (cdd->state == CURVE_DRAW_PAINTING) { const float mval_fl[2] = {UNPACK2(event->mval)}; if (len_squared_v2v2(mval_fl, cdd->prev.mouse) > square_f(STROKE_SAMPLE_DIST_MIN_PX)) { curve_draw_event_add(op, event); } } } return ret; } void CURVE_OT_draw(wmOperatorType *ot) { /* identifiers */ ot->name = "Draw Curve"; ot->idname = "CURVE_OT_draw"; ot->description = "Draw a freehand spline"; /* api callbacks */ ot->exec = curve_draw_exec; ot->invoke = curve_draw_invoke; ot->cancel = curve_draw_cancel; ot->modal = curve_draw_modal; ot->poll = ED_operator_editcurve; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ PropertyRNA *prop; prop = RNA_def_float_distance(ot->srna, "error_threshold", 0.0f, 0.0f, 10.0f, "Error", "Error distance threshold (in object units)", 0.0001f, 10.0f); RNA_def_property_ui_range(prop, 0.0, 10, 1, 4); RNA_def_enum(ot->srna, "fit_method", rna_enum_curve_fit_method_items, CURVE_PAINT_FIT_METHOD_REFIT, "Fit Method", ""); prop = RNA_def_float_distance( ot->srna, "corner_angle", DEG2RADF(70.0f), 0.0f, M_PI, "Corner Angle", "", 0.0f, M_PI); RNA_def_property_subtype(prop, PROP_ANGLE); prop = RNA_def_boolean(ot->srna, "use_cyclic", true, "Cyclic", ""); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /** \} */