diff options
Diffstat (limited to 'source')
37 files changed, 4545 insertions, 708 deletions
diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index 203543b0ef0..62923d18b70 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -68,6 +68,7 @@ set(SRC_DNA_INC ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_outliner_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_packedFile_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_particle_types.h + ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_curveprofile_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_rigidbody_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_scene_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_screen_types.h diff --git a/source/blender/blenkernel/BKE_curveprofile.h b/source/blender/blenkernel/BKE_curveprofile.h new file mode 100644 index 00000000000..1f6659d785a --- /dev/null +++ b/source/blender/blenkernel/BKE_curveprofile.h @@ -0,0 +1,76 @@ +/* + * 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. + * + * Copyright (C) 2019 Blender Foundation. + * All rights reserved. + */ + +#ifndef __BKE_CURVEPROFILE_H__ +#define __BKE_CURVEPROFILE_H__ + +/** \file + * \ingroup bke + */ + +struct CurveProfile; +struct CurveProfilePoint; + +void BKE_curveprofile_set_defaults(struct CurveProfile *profile); + +struct CurveProfile *BKE_curveprofile_add(int preset); + +void BKE_curveprofile_free_data(struct CurveProfile *profile); + +void BKE_curveprofile_free(struct CurveProfile *profile); + +void BKE_curveprofile_copy_data(struct CurveProfile *target, const struct CurveProfile *profile); + +struct CurveProfile *BKE_curveprofile_copy(const struct CurveProfile *profile); + +bool BKE_curveprofile_remove_point(struct CurveProfile *profile, struct CurveProfilePoint *point); + +void BKE_curveprofile_remove_by_flag(struct CurveProfile *profile, const short flag); + +struct CurveProfilePoint *BKE_curveprofile_insert(struct CurveProfile *profile, float x, float y); + +void BKE_curveprofile_selected_handle_set(struct CurveProfile *profile, int type_1, int type_2); + +void BKE_curveprofile_reverse(struct CurveProfile *profile); + +void BKE_curveprofile_reset(struct CurveProfile *profile); + +void BKE_curveprofile_create_samples(struct CurveProfile *profile, + int segments_len, + bool sample_straight_edges, + struct CurveProfilePoint *r_samples); + +void BKE_curveprofile_initialize(struct CurveProfile *profile, short segments_len); + +/* Called for a complete update of the widget after modifications */ +void BKE_curveprofile_update(struct CurveProfile *profile, const bool rem_doubles); + +/* Need to find the total length of the curve to sample a portion of it */ +float BKE_curveprofile_total_length(const struct CurveProfile *profile); + +void BKE_curveprofile_create_samples_even_spacing(struct CurveProfile *profile, + int segments_len, + struct CurveProfilePoint *r_samples); + +/* Length portion is the fraction of the total path length where we want the location */ +void BKE_curveprofile_evaluate_length_portion(const struct CurveProfile *profile, + float length_portion, + float *x_out, + float *y_out); +#endif diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 023980292fa..49cead5c003 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -185,6 +185,7 @@ set(SRC intern/pbvh_bmesh.c intern/pbvh_parallel.cc intern/pointcache.c + intern/curveprofile.c intern/report.c intern/rigidbody.c intern/scene.c @@ -330,6 +331,7 @@ set(SRC BKE_particle.h BKE_pbvh.h BKE_pointcache.h + BKE_curveprofile.h BKE_report.h BKE_rigidbody.h BKE_scene.h diff --git a/source/blender/blenkernel/intern/curveprofile.c b/source/blender/blenkernel/intern/curveprofile.c new file mode 100644 index 00000000000..6689eca132d --- /dev/null +++ b/source/blender/blenkernel/intern/curveprofile.c @@ -0,0 +1,1011 @@ +/* + * 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. + * + * Copyright (C) 2019 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup bke + */ + +#include <string.h> +#include <math.h> +#include <stdlib.h> +#include <float.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_curveprofile_types.h" +#include "DNA_curve_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" +#include "BLI_task.h" +#include "BLI_threads.h" + +#include "BKE_curveprofile.h" +#include "BKE_curve.h" +#include "BKE_fcurve.h" + +void BKE_curveprofile_free_data(CurveProfile *profile) +{ + MEM_SAFE_FREE(profile->path); + MEM_SAFE_FREE(profile->table); + MEM_SAFE_FREE(profile->segments); +} + +void BKE_curveprofile_free(CurveProfile *profile) +{ + if (profile) { + BKE_curveprofile_free_data(profile); + MEM_freeN(profile); + } +} + +void BKE_curveprofile_copy_data(CurveProfile *target, const CurveProfile *profile) +{ + *target = *profile; + + target->path = MEM_dupallocN(profile->path); + target->table = MEM_dupallocN(profile->table); + target->segments = MEM_dupallocN(profile->segments); +} + +CurveProfile *BKE_curveprofile_copy(const CurveProfile *profile) +{ + if (profile) { + CurveProfile *new_prdgt = MEM_dupallocN(profile); + BKE_curveprofile_copy_data(new_prdgt, profile); + return new_prdgt; + } + return NULL; +} + +/** Removes a specific point from the path of control points. + * \note: Requires curveprofile_update call after. */ +bool BKE_curveprofile_remove_point(CurveProfile *profile, CurveProfilePoint *point) +{ + CurveProfilePoint *pts; + + /* Must have 2 points minimum. */ + if (profile->path_len <= 2) { + return false; + } + + /* Input point must be within the array. */ + if (!(point > profile->path && point < profile->path + profile->path_len)) { + return false; + } + + pts = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len, "path points"); + + uint i_delete = (uint)(point - profile->path); + + /* Copy the before and after the deleted point. */ + memcpy(pts, profile->path, i_delete); + memcpy(pts + i_delete, profile->path + i_delete + 1, (size_t)profile->path_len - i_delete - 1); + + MEM_freeN(profile->path); + profile->path = pts; + profile->path_len -= 1; + return true; +} + +/** Removes every point in the widget with the supplied flag set, except for the first and last. + * \param flag: CurveProfilePoint->flag. + * \note: Requires curveprofile_update call after. */ +void BKE_curveprofile_remove_by_flag(CurveProfile *profile, const short flag) +{ + int i_old, i_new, n_removed = 0; + + /* Copy every point without the flag into the new path. */ + CurveProfilePoint *new_pts = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len, + "profile path"); + + /* Build the new list without any of the points with the flag. Keep the first and last points. */ + new_pts[0] = profile->path[0]; + for (i_old = 1, i_new = 1; i_old < profile->path_len - 1; i_old++) { + if (!(profile->path[i_old].flag & flag)) { + new_pts[i_new] = profile->path[i_old]; + i_new++; + } + else { + n_removed++; + } + } + new_pts[i_new] = profile->path[i_old]; + + MEM_freeN(profile->path); + profile->path = new_pts; + profile->path_len -= n_removed; +} + +/** Adds a new point at the specified location. The choice for which points to place the new vertex + * between is made by checking which control point line segment is closest to the new point and + * placing the new vertex in between that segment's points. + * \note: Requires curveprofile_update call after. */ +CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float y) +{ + CurveProfilePoint *new_pt = NULL; + float new_loc[2] = {x, y}; + + /* Don't add more control points than the maximum size of the higher resolution table. */ + if (profile->path_len == PROF_TABLE_MAX - 1) { + return NULL; + } + + /* Find the index at the line segment that's closest to the new position. */ + float distance; + float min_distance = FLT_MAX; + int i_insert = 0; + for (int i = 0; i < profile->path_len - 1; i++) { + float loc1[2] = {profile->path[i].x, profile->path[i].y}; + float loc2[2] = {profile->path[i + 1].x, profile->path[i + 1].y}; + + distance = dist_squared_to_line_segment_v2(new_loc, loc1, loc2); + if (distance < min_distance) { + min_distance = distance; + i_insert = i + 1; + } + } + + /* Insert the new point at the location we found and copy all of the old points in as well. */ + profile->path_len++; + CurveProfilePoint *new_pts = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len, + "profile path"); + for (int i_new = 0, i_old = 0; i_new < profile->path_len; i_new++) { + if (i_new != i_insert) { + /* Insert old points */ + new_pts[i_new].x = profile->path[i_old].x; + new_pts[i_new].y = profile->path[i_old].y; + new_pts[i_new].flag = profile->path[i_old].flag & ~PROF_SELECT; /* Deselect old points. */ + new_pts[i_new].h1 = profile->path[i_old].h1; + new_pts[i_new].h2 = profile->path[i_old].h2; + i_old++; + } + else { + /* Insert new point. */ + new_pts[i_new].x = x; + new_pts[i_new].y = y; + new_pts[i_new].flag = PROF_SELECT; + new_pt = &new_pts[i_new]; + /* Set handles of new point based on its neighbors. */ + if (new_pts[i_new - 1].h2 == HD_VECT && profile->path[i_insert].h1 == HD_VECT) { + new_pt->h1 = new_pt->h2 = HD_VECT; + } + else { + new_pt->h1 = new_pt->h2 = HD_AUTO; + } + } + } + + /* Free the old path and use the new one. */ + MEM_freeN(profile->path); + profile->path = new_pts; + return new_pt; +} + +/** Sets the handle type of the selected control points. + * \param type_*: Either HD_VECT or HD_AUTO. Handle types for the first and second handles. + * \note: Requires curveprofile_update call after. */ +void BKE_curveprofile_selected_handle_set(CurveProfile *profile, int type_1, int type_2) +{ + for (int i = 0; i < profile->path_len; i++) { + if (profile->path[i].flag & PROF_SELECT) { + switch (type_1) { + case HD_AUTO: + profile->path[i].h1 = HD_AUTO; + break; + case HD_VECT: + profile->path[i].h1 = HD_VECT; + break; + default: + profile->path[i].h1 = HD_AUTO; + break; + } + switch (type_2) { + case HD_AUTO: + profile->path[i].h2 = HD_AUTO; + break; + case HD_VECT: + profile->path[i].h2 = HD_VECT; + break; + default: + profile->path[i].h1 = HD_AUTO; + break; + } + } + } +} + +/** Flips the profile across the diagonal so that its orientation is reversed. + * \note: Requires curveprofile_update call after. */ +void BKE_curveprofile_reverse(CurveProfile *profile) +{ + /* When there are only two points, reversing shouldn't do anything. */ + if (profile->path_len == 2) { + return; + } + CurveProfilePoint *new_pts = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len, + "profile path"); + /* Mirror the new points across the y = x line */ + for (int i = 0; i < profile->path_len; i++) { + new_pts[profile->path_len - i - 1].x = profile->path[i].y; + new_pts[profile->path_len - i - 1].y = profile->path[i].x; + new_pts[profile->path_len - i - 1].flag = profile->path[i].flag; + new_pts[profile->path_len - i - 1].h1 = profile->path[i].h1; + new_pts[profile->path_len - i - 1].h2 = profile->path[i].h2; + } + + /* Free the old points and use the new ones */ + MEM_freeN(profile->path); + profile->path = new_pts; +} + +/** Builds a quarter circle profile with space on each side for 'support loops.' */ +static void CurveProfile_build_supports(CurveProfile *profile) +{ + int n = profile->path_len; + + profile->path[0].x = 1.0; + profile->path[0].y = 0.0; + profile->path[0].flag = 0; + profile->path[0].h1 = HD_VECT; + profile->path[0].h2 = HD_VECT; + profile->path[1].x = 1.0; + profile->path[1].y = 0.5; + profile->path[1].flag = 0; + profile->path[1].h1 = HD_VECT; + profile->path[1].h2 = HD_VECT; + for (int i = 1; i < n - 2; i++) { + profile->path[i + 1].x = 1.0f - (0.5f * (1.0f - cosf((float)((i / (float)(n - 3))) * M_PI_2))); + profile->path[i + 1].y = 0.5f + 0.5f * sinf((float)((i / (float)(n - 3)) * M_PI_2)); + profile->path[i + 1].flag = 0; + profile->path[i + 1].h1 = HD_AUTO; + profile->path[i + 1].h2 = HD_AUTO; + } + profile->path[n - 2].x = 0.5; + profile->path[n - 2].y = 1.0; + profile->path[n - 2].flag = 0; + profile->path[n - 2].h1 = HD_VECT; + profile->path[n - 2].h2 = HD_VECT; + profile->path[n - 1].x = 0.0; + profile->path[n - 1].y = 1.0; + profile->path[n - 1].flag = 0; + profile->path[n - 1].h1 = HD_VECT; + profile->path[n - 1].h2 = HD_VECT; +} + +/** Puts the widget's control points in a step pattern. Uses vector handles for each point. */ +static void CurveProfile_build_steps(CurveProfile *profile) +{ + int n, step_x, step_y; + float n_steps_x, n_steps_y; + + n = profile->path_len; + + /* Special case for two points to avoid dividing by zero later. */ + if (n == 2) { + profile->path[0].x = 1.0f; + profile->path[0].y = 0.0f; + profile->path[0].flag = 0; + profile->path[0].h1 = HD_VECT; + profile->path[0].h2 = HD_VECT; + profile->path[1].x = 0.0f; + profile->path[1].y = 1.0f; + profile->path[1].flag = 0; + profile->path[1].h1 = HD_VECT; + profile->path[1].h2 = HD_VECT; + return; + } + + n_steps_x = (n % 2 == 0) ? n : (n - 1); + n_steps_y = (n % 2 == 0) ? (n - 2) : (n - 1); + + for (int i = 0; i < n; i++) { + step_x = (i + 1) / 2; + step_y = i / 2; + profile->path[i].x = 1.0f - ((float)(2 * step_x) / n_steps_x); + profile->path[i].y = (float)(2 * step_y) / n_steps_y; + profile->path[i].flag = 0; + profile->path[i].h1 = HD_VECT; + profile->path[i].h2 = HD_VECT; + } +} + +/** Shorthand helper function for setting location and interpolation of a point. */ +static void point_init(CurveProfilePoint *point, float x, float y, short flag, char h1, char h2) +{ + point->x = x; + point->y = y; + point->flag = flag; + point->h1 = h1; + point->h2 = h2; +} + +/** Resets the profile to the current preset. + * \note: Requires curveprofile_update call after. */ +void BKE_curveprofile_reset(CurveProfile *profile) +{ + if (profile->path) { + MEM_freeN(profile->path); + } + + int preset = profile->preset; + switch (preset) { + case PROF_PRESET_LINE: + profile->path_len = 2; + break; + case PROF_PRESET_SUPPORTS: + /* Use a dynamic number of control points for the widget's profile. */ + if (profile->segments_len < 4) { + /* But always use enough points to at least build the support points. */ + profile->path_len = 5; + } + else { + profile->path_len = profile->segments_len + 1; + } + break; + case PROF_PRESET_CORNICE: + profile->path_len = 13; + break; + case PROF_PRESET_CROWN: + profile->path_len = 11; + break; + case PROF_PRESET_STEPS: + /* Also use dynamic number of control points based on the set number of segments. */ + if (profile->segments_len == 0) { + /* totsegments hasn't been set-- use the number of control points for 8 steps. */ + profile->path_len = 17; + } + else { + profile->path_len = profile->segments_len + 1; + } + break; + } + + profile->path = MEM_callocN(sizeof(CurveProfilePoint) * profile->path_len, "profile path"); + + switch (preset) { + case PROF_PRESET_LINE: + point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[1], 0.0f, 1.0f, 0, HD_AUTO, HD_AUTO); + break; + case PROF_PRESET_SUPPORTS: + CurveProfile_build_supports(profile); + break; + case PROF_PRESET_CORNICE: + point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[1], 1.0f, 0.125f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[2], 0.92f, 0.16f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[3], 0.875f, 0.25f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[4], 0.8f, 0.25f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[5], 0.733f, 0.433f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[6], 0.582f, 0.522f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[7], 0.4f, 0.6f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[8], 0.289f, 0.727f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[9], 0.25f, 0.925f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[10], 0.175f, 0.925f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[11], 0.175f, 1.0f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[12], 0.0f, 1.0f, 0, HD_VECT, HD_VECT); + break; + case PROF_PRESET_CROWN: + point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[1], 1.0f, 0.25f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[2], 0.75f, 0.25f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[3], 0.75f, 0.325f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[4], 0.925f, 0.4f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[5], 0.975f, 0.5f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[6], 0.94f, 0.65f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[7], 0.85f, 0.75f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[8], 0.75f, 0.875f, 0, HD_AUTO, HD_AUTO); + point_init(&profile->path[9], 0.7f, 1.0f, 0, HD_VECT, HD_VECT); + point_init(&profile->path[10], 0.0f, 1.0f, 0, HD_VECT, HD_VECT); + break; + case PROF_PRESET_STEPS: + CurveProfile_build_steps(profile); + break; + } + + if (profile->table) { + MEM_freeN(profile->table); + profile->table = NULL; + } +} + +/** Helper for 'curve_profile_create' samples. Returns whether both handles that make up the edge + * are vector handles. */ +static bool is_curved_edge(BezTriple *bezt, int i) +{ + return (bezt[i].h2 != HD_VECT || bezt[i + 1].h1 != HD_VECT); +} + +/** Used to set bezier handle locations in the sample creation process. Reduced copy of + * #calchandleNurb_intern code in curve.c. */ +static void calchandle_profile(BezTriple *bezt, const BezTriple *prev, const BezTriple *next) +{ +#define point_handle1 ((point_loc)-3) +#define point_handle2 ((point_loc) + 3) + + const float *prev_loc, *next_loc; + float *point_loc; + float pt[3]; + float len, len_a, len_b; + float dvec_a[2], dvec_b[2]; + + if (bezt->h1 == 0 && bezt->h2 == 0) { + return; + } + + point_loc = bezt->vec[1]; + + if (prev == NULL) { + next_loc = next->vec[1]; + pt[0] = 2.0f * point_loc[0] - next_loc[0]; + pt[1] = 2.0f * point_loc[1] - next_loc[1]; + prev_loc = pt; + } + else { + prev_loc = prev->vec[1]; + } + + if (next == NULL) { + prev_loc = prev->vec[1]; + pt[0] = 2.0f * point_loc[0] - prev_loc[0]; + pt[1] = 2.0f * point_loc[1] - prev_loc[1]; + next_loc = pt; + } + else { + next_loc = next->vec[1]; + } + + sub_v2_v2v2(dvec_a, point_loc, prev_loc); + sub_v2_v2v2(dvec_b, next_loc, point_loc); + + len_a = len_v2(dvec_a); + len_b = len_v2(dvec_b); + + if (len_a == 0.0f) { + len_a = 1.0f; + } + if (len_b == 0.0f) { + len_b = 1.0f; + } + + if (bezt->h1 == HD_AUTO || bezt->h2 == HD_AUTO) { /* auto */ + float tvec[2]; + tvec[0] = dvec_b[0] / len_b + dvec_a[0] / len_a; + tvec[1] = dvec_b[1] / len_b + dvec_a[1] / len_a; + + len = len_v2(tvec) * 2.5614f; + if (len != 0.0f) { + + if (bezt->h1 == HD_AUTO) { + len_a /= len; + madd_v2_v2v2fl(point_handle1, point_loc, tvec, -len_a); + } + if (bezt->h2 == HD_AUTO) { + len_b /= len; + madd_v2_v2v2fl(point_handle2, point_loc, tvec, len_b); + } + } + } + + if (bezt->h1 == HD_VECT) { /* vector */ + madd_v2_v2v2fl(point_handle1, point_loc, dvec_a, -1.0f / 3.0f); + } + if (bezt->h2 == HD_VECT) { + madd_v2_v2v2fl(point_handle2, point_loc, dvec_b, 1.0f / 3.0f); + } +#undef point_handle1 +#undef point_handle2 +} + +/** Helper function for 'BKE_CurveProfile_create_samples.' Calculates the angle between the + * handles on the inside of the edge starting at index i. A larger angle means the edge is + * more curved. + * \param i_edge: The start index of the edge to calculate the angle for. */ +static float bezt_edge_handle_angle(const BezTriple *bezt, int i_edge) +{ + /* Find the direction of the handles that define this edge along the direction of the path. */ + float start_handle_direction[2], end_handle_direction[2]; + /* Handle 2 - point location. */ + sub_v2_v2v2(start_handle_direction, bezt[i_edge].vec[2], bezt[i_edge].vec[1]); + /* Point location - handle 1. */ + sub_v2_v2v2(end_handle_direction, bezt[i_edge + 1].vec[1], bezt[i_edge + 1].vec[0]); + + float angle = angle_v2v2(start_handle_direction, end_handle_direction); + return angle; +} + +/** Struct to sort curvature of control point edges. */ +typedef struct { + /** The index of the corresponding bezier point. */ + int bezt_index; + /** The curvature of the edge with the above index. */ + float bezt_curvature; +} CurvatureSortPoint; + +/** Helper function for 'BKE_curveprofile_create_samples' for sorting edges based on curvature. */ +static int sort_points_curvature(const void *in_a, const void *in_b) +{ + const CurvatureSortPoint *a = (const CurvatureSortPoint *)in_a; + const CurvatureSortPoint *b = (const CurvatureSortPoint *)in_b; + + if (a->bezt_curvature > b->bezt_curvature) { + return 0; + } + else { + return 1; + } +} + +/** Used for sampling curves along the profile's path. Any points more than the number of user- + * defined points will be evenly distributed among the curved edges. Then the remainders will be + * distributed to the most curved edges. + * \param n_segments: The number of segments to sample along the path. It must be higher than the + * number of points used to define the profile (profile->path_len). + * \param sample_straight_edges: Whether to sample points between vector handle control points. If + * this is true and there are only vector edges the straight edges will still be sampled. + * \param r_samples: An array of points to put the sampled positions. Must have length n_segments. + * \return r_samples: Fill the array with the sampled locations and if the point corresponds + * to a control point, its handle type */ +void BKE_curveprofile_create_samples(CurveProfile *profile, + int n_segments, + bool sample_straight_edges, + CurveProfilePoint *r_samples) +{ + BezTriple *bezt; + int i, n_left, n_common, i_sample, n_curved_edges; + int *n_samples; + CurvatureSortPoint *curve_sorted; + int totpoints = profile->path_len; + int totedges = totpoints - 1; + + BLI_assert(n_segments > 0); + + /* Create Bezier points for calculating the higher resolution path. */ + bezt = MEM_callocN(sizeof(BezTriple) * totpoints, "beztarr"); + for (i = 0; i < totpoints; i++) { + bezt[i].vec[1][0] = profile->path[i].x; + bezt[i].vec[1][1] = profile->path[i].y; + bezt[i].h1 = (profile->path[i].h1 == HD_VECT) ? HD_VECT : HD_AUTO; + bezt[i].h2 = (profile->path[i].h2 == HD_VECT) ? HD_VECT : HD_AUTO; + } + /* Give the first and last bezier points the same handle type as their neighbors. */ + if (totpoints > 2) { + bezt[0].h1 = bezt[0].h2 = bezt[1].h1; + bezt[totpoints - 1].h1 = bezt[totpoints - 1].h2 = bezt[totpoints - 2].h2; + } + /* Get handle positions for the bezier points. */ + calchandle_profile(&bezt[0], NULL, &bezt[1]); + for (i = 1; i < totpoints - 1; i++) { + calchandle_profile(&bezt[i], &bezt[i - 1], &bezt[i + 1]); + } + calchandle_profile(&bezt[totpoints - 1], &bezt[totpoints - 2], NULL); + + /* Create a list of edge indices with the most curved at the start, least curved at the end. */ + curve_sorted = MEM_callocN(sizeof(CurvatureSortPoint) * totedges, "curve sorted"); + for (i = 0; i < totedges; i++) { + curve_sorted[i].bezt_index = i; + /* Calculate the curvature of each edge once for use when sorting for curvature. */ + curve_sorted[i].bezt_curvature = bezt_edge_handle_angle(bezt, i); + } + qsort(curve_sorted, (size_t)totedges, sizeof(CurvatureSortPoint), sort_points_curvature); + + /* Assign the number of sampled points for each edge. */ + n_samples = MEM_callocN(sizeof(int) * totedges, "create samples numbers"); + int n_added = 0; + if (n_segments >= totedges) { + if (sample_straight_edges) { + /* Assign an even number to each edge if it’s possible, then add the remainder of sampled + * points starting with the most curved edges. */ + n_common = n_segments / totedges; + n_left = n_segments % totedges; + + /* Assign the points that fill fit evenly to the edges. */ + if (n_common > 0) { + for (i = 0; i < totedges; i++) { + n_samples[i] = n_common; + n_added += n_common; + } + } + } + else { + /* Count the number of curved edges */ + n_curved_edges = 0; + for (i = 0; i < totedges; i++) { + if (is_curved_edge(bezt, i)) { + n_curved_edges++; + } + } + /* Just sample all of the edges if there are no curved edges. */ + n_curved_edges = (n_curved_edges == 0) ? totedges : n_curved_edges; + + /* Give all of the curved edges the same number of points and straight edges one point. */ + n_left = n_segments - (totedges - n_curved_edges); /* Left after 1 for each straight edge. */ + n_common = n_left / n_curved_edges; /* Number assigned to all curved edges */ + if (n_common > 0) { + for (i = 0; i < totedges; i++) { + /* Add the common number if it's a curved edge or if edges are curved. */ + if (is_curved_edge(bezt, i) || n_curved_edges == totedges) { + n_samples[i] += n_common; + n_added += n_common; + } + else { + n_samples[i] = 1; + n_added++; + } + } + } + n_left -= n_common * n_curved_edges; + } + } + else { + /* Not enough segments to give one to each edge, so just give them to the most curved edges. */ + n_left = n_segments; + } + /* Assign the remainder of the points that couldn't be spread out evenly. */ + BLI_assert(n_left < totedges); + for (i = 0; i < n_left; i++) { + n_samples[curve_sorted[i].bezt_index]++; + n_added++; + } + + BLI_assert(n_added == n_segments); /* n_added is just used for this assert, could remove it. */ + + /* Sample the points and add them to the locations table. */ + for (i_sample = 0, i = 0; i < totedges; i++) { + if (n_samples[i] > 0) { + /* Carry over the handle types from the control point to its first corresponding sample. */ + r_samples[i_sample].h1 = profile->path[i].h1; + r_samples[i_sample].h2 = profile->path[i].h2; + /* All extra sample points for this control point get "auto" handles. */ + for (int j = i_sample + 1; j < i_sample + n_samples[i]; j++) { + r_samples[j].flag = 0; + r_samples[j].h1 = HD_AUTO; + r_samples[j].h2 = HD_AUTO; + BLI_assert(j < n_segments); + } + + /* Do the sampling from bezier points, X values first, then Y values. */ + BKE_curve_forward_diff_bezier(bezt[i].vec[1][0], + bezt[i].vec[2][0], + bezt[i + 1].vec[0][0], + bezt[i + 1].vec[1][0], + &r_samples[i_sample].x, + n_samples[i], + sizeof(CurveProfilePoint)); + BKE_curve_forward_diff_bezier(bezt[i].vec[1][1], + bezt[i].vec[2][1], + bezt[i + 1].vec[0][1], + bezt[i + 1].vec[1][1], + &r_samples[i_sample].y, + n_samples[i], + sizeof(CurveProfilePoint)); + } + i_sample += n_samples[i]; /* Add the next set of points after the ones we just added. */ + BLI_assert(i_sample <= n_segments); + } + +#ifdef DEBUG_profile_TABLE + printf("CURVEPROFILE CREATE SAMPLES\n"); + printf("n_segments: %d\n", n_segments); + printf("totedges: %d\n", totedges); + printf("n_common: %d\n", n_common); + printf("n_left: %d\n", n_left); + printf("n_samples: "); + for (i = 0; i < totedges; i++) { + printf("%d, ", n_samples[i]); + } + printf("\n"); + printf("i_curved_sorted: "); + for (i = 0; i < totedges; i++) { + printf("(%d %.2f), ", curve_sorted[i].bezt_index, curve_sorted[i].bezt_curvature); + } + printf("\n"); +#endif + MEM_freeN(bezt); + MEM_freeN(curve_sorted); + MEM_freeN(n_samples); +} + +/** Creates a higher resolution table by sampling the curved points. This table is used for display + * and evenly spaced evaluation. */ +static void curveprofile_make_table(CurveProfile *profile) +{ + int n_samples = PROF_N_TABLE(profile->path_len); + CurveProfilePoint *new_table = MEM_callocN(sizeof(CurveProfilePoint) * (n_samples + 1), + "high-res table"); + + BKE_curveprofile_create_samples(profile, n_samples - 1, false, new_table); + /* Manually add last point at the end of the profile */ + new_table[n_samples - 1].x = 0.0f; + new_table[n_samples - 1].y = 1.0f; + + if (profile->table) { + MEM_freeN(profile->table); + } + profile->table = new_table; +} + +/** Creates the table of points used for displaying a preview of the sampled segment locations on + * the widget itself. */ +static void CurveProfile_make_segments_table(CurveProfile *profile) +{ + int n_samples = profile->segments_len; + if (n_samples <= 0) { + return; + } + CurveProfilePoint *new_table = MEM_callocN(sizeof(CurveProfilePoint) * (n_samples + 1), + "samples table"); + + if (profile->flag & PROF_SAMPLE_EVEN_LENGTHS) { + /* Even length sampling incompatible with only straight edge sampling for now. */ + BKE_curveprofile_create_samples_even_spacing(profile, n_samples, new_table); + } + else { + BKE_curveprofile_create_samples( + profile, n_samples, profile->flag & PROF_SAMPLE_STRAIGHT_EDGES, new_table); + } + + if (profile->segments) { + MEM_freeN(profile->segments); + } + profile->segments = new_table; +} + +/** Sets the default settings and clip range for the profile widget. Does not generate either + * table. */ +void BKE_curveprofile_set_defaults(CurveProfile *profile) +{ + profile->flag = PROF_USE_CLIP; + + BLI_rctf_init(&profile->view_rect, 0.0f, 1.0f, 0.0f, 1.0f); + profile->clip_rect = profile->view_rect; + + profile->path_len = 2; + profile->path = MEM_callocN(2 * sizeof(CurveProfilePoint), "path points"); + + profile->path[0].x = 1.0f; + profile->path[0].y = 0.0f; + profile->path[1].x = 1.0f; + profile->path[1].y = 1.0f; + + profile->changed_timestamp = 0; +} + +/** Returns a pointer to a newly allocated curve profile, using the given preset. + \param preset: Value in eCurveProfilePresets. */ +struct CurveProfile *BKE_curveprofile_add(int preset) +{ + CurveProfile *profile = MEM_callocN(sizeof(CurveProfile), "curve profile"); + + BKE_curveprofile_set_defaults(profile); + profile->preset = preset; + BKE_curveprofile_reset(profile); + curveprofile_make_table(profile); + + return profile; +} + +/** Should be called after the widget is changed. Does profile and remove double checks and more + * importantly, recreates the display / evaluation and segments tables. */ +void BKE_curveprofile_update(CurveProfile *profile, const bool remove_double) +{ + CurveProfilePoint *points = profile->path; + rctf *clipr = &profile->clip_rect; + float thresh; + float dx, dy; + int i; + + profile->changed_timestamp++; + + /* Clamp with the clipping rect in case something got past. */ + if (profile->flag & PROF_USE_CLIP) { + /* Move points inside the clip rectangle. */ + for (i = 0; i < profile->path_len; i++) { + points[i].x = max_ff(points[i].x, clipr->xmin); + points[i].x = min_ff(points[i].x, clipr->xmax); + points[i].y = max_ff(points[i].y, clipr->ymin); + points[i].y = min_ff(points[i].y, clipr->ymax); + } + /* Ensure zoom-level respects clipping. */ + if (BLI_rctf_size_x(&profile->view_rect) > BLI_rctf_size_x(&profile->clip_rect)) { + profile->view_rect.xmin = profile->clip_rect.xmin; + profile->view_rect.xmax = profile->clip_rect.xmax; + } + if (BLI_rctf_size_y(&profile->view_rect) > BLI_rctf_size_y(&profile->clip_rect)) { + profile->view_rect.ymin = profile->clip_rect.ymin; + profile->view_rect.ymax = profile->clip_rect.ymax; + } + } + + /* Remove doubles with a threshold set at 1% of default range. */ + thresh = 0.01f * BLI_rctf_size_x(clipr); + if (remove_double && profile->path_len > 2) { + for (i = 0; i < profile->path_len - 1; i++) { + dx = points[i].x - points[i + 1].x; + dy = points[i].y - points[i + 1].y; + if (sqrtf(dx * dx + dy * dy) < thresh) { + if (i == 0) { + points[i + 1].flag |= HD_VECT; + if (points[i + 1].flag & PROF_SELECT) { + points[i].flag |= PROF_SELECT; + } + } + else { + points[i].flag |= HD_VECT; + if (points[i].flag & PROF_SELECT) { + points[i + 1].flag |= PROF_SELECT; + } + } + break; /* Assumes 1 deletion per edit is ok. */ + } + } + if (i != profile->path_len - 1) { + BKE_curveprofile_remove_by_flag(profile, 2); + } + } + + /* Create the high resolution table for drawing and some evaluation functions. */ + curveprofile_make_table(profile); + + /* Store a table of samples for the segment locations for a preview and the table's user. */ + if (profile->segments_len > 0) { + CurveProfile_make_segments_table(profile); + } +} + +/** Refreshes the higher resolution table sampled from the input points. A call to this or + * curveprofile_update is needed before evaluation functions that use the table. Also sets the + * number of segments used for the display preview of the locations of the sampled points. */ +void BKE_curveprofile_initialize(CurveProfile *profile, short segments_len) +{ + profile->segments_len = segments_len; + + /* Calculate the higher resolution / segments tables for display and evaluation. */ + BKE_curveprofile_update(profile, false); +} + +/** Gives the distance to the next point in the widget's sampled table, in other words the length + * of the ith edge of the table. + * \note Requires curveprofile_initialize or curveprofile_update call before to fill table. */ +static float curveprofile_distance_to_next_table_point(const CurveProfile *profile, int i) +{ + BLI_assert(i < PROF_N_TABLE(profile->path_len)); + + return len_v2v2(&profile->table[i].x, &profile->table[i + 1].x); +} + +/** Calculates the total length of the profile from the curves sampled in the table. + * \note Requires curveprofile_initialize or curveprofile_update call before to fill table. */ +float BKE_curveprofile_total_length(const CurveProfile *profile) +{ + float total_length = 0; + for (int i = 0; i < PROF_N_TABLE(profile->path_len) - 1; i++) { + total_length += len_v2v2(&profile->table[i].x, &profile->table[i + 1].x); + } + return total_length; +} + +/** Samples evenly spaced positions along the curve profile's table (generated from path). Fills + * an entire table at once for a speedup if all of the results are going to be used anyway. + * \note Requires curveprofile_initialize or curveprofile_update call before to fill table. + * \note Working, but would conflict with "Sample Straight Edges" option, so this is unused for + * now. */ +void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile, + int n_segments, + CurveProfilePoint *r_samples) +{ + const float total_length = BKE_curveprofile_total_length(profile); + const float segment_length = total_length / n_segments; + float length_travelled = 0.0f; + float distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, 0); + float distance_to_previous_table_point = 0.0f; + float segment_left, factor; + int i_table = 0; + + /* Set the location for the first point. */ + r_samples[0].x = profile->table[0].x; + r_samples[0].y = profile->table[0].y; + + /* Travel along the path, recording the locations of segments as we pass them. */ + segment_left = segment_length; + for (int i = 1; i < n_segments; i++) { + /* Travel over all of the points that fit inside this segment. */ + while (distance_to_next_table_point < segment_left) { + length_travelled += distance_to_next_table_point; + segment_left -= distance_to_next_table_point; + i_table++; + distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, i_table); + distance_to_previous_table_point = 0.0f; + } + /* We're at the last table point that fits inside the current segment, use interpolation. */ + factor = (distance_to_previous_table_point + segment_left) / + (distance_to_previous_table_point + distance_to_next_table_point); + r_samples[i].x = interpf(profile->table[i_table + 1].x, profile->table[i_table].x, factor); + r_samples[i].y = interpf(profile->table[i_table + 1].y, profile->table[i_table].y, factor); +#ifdef DEBUG_CURVEPROFILE_EVALUATE + BLI_assert(factor <= 1.0f && factor >= 0.0f); + printf("segment_left: %.3f\n", segment_left); + printf("i_table: %d\n", i_table); + printf("distance_to_previous_table_point: %.3f\n", distance_to_previous_table_point); + printf("distance_to_next_table_point: %.3f\n", distance_to_next_table_point); + printf("Interpolating with factor %.3f from (%.3f, %.3f) to (%.3f, %.3f)\n\n", + factor, + profile->table[i_table].x, + profile->table[i_table].y, + profile->table[i_table + 1].x, + profile->table[i_table + 1].y); +#endif + + /* We sampled in between this table point and the next, so the next travel step is smaller. */ + distance_to_next_table_point -= segment_left; + distance_to_previous_table_point += segment_left; + length_travelled += segment_left; + segment_left = segment_length; + } +} + +/** Does a single evaluation along the profile's path. Travels down (length_portion * path) length + * and returns the position at that point. + * \param length_portion: The portion (0 to 1) of the path's full length to sample at. + * \note Requires curveprofile_initialize or curveprofile_update call before to fill table */ +void BKE_curveprofile_evaluate_length_portion(const CurveProfile *profile, + float length_portion, + float *x_out, + float *y_out) +{ + const float total_length = BKE_curveprofile_total_length(profile); + const float requested_length = length_portion * total_length; + + /* Find the last point along the path with a lower length portion than the input. */ + int i = 0; + float length_travelled = 0.0f; + while (length_travelled < requested_length) { + /* Check if we reached the last point before the final one. */ + if (i == PROF_N_TABLE(profile->path_len) - 2) { + break; + } + float new_length = curveprofile_distance_to_next_table_point(profile, i); + if (length_travelled + new_length >= requested_length) { + break; + } + length_travelled += new_length; + i++; + } + + /* Now travel the remaining distance of length portion down the path to the next point and + * find the location where we stop. */ + float distance_to_next_point = curveprofile_distance_to_next_table_point(profile, i); + float lerp_factor = (requested_length - length_travelled) / distance_to_next_point; + +#ifdef DEBUG_CURVEPROFILE_EVALUATE + printf("CURVEPROFILE EVALUATE\n"); + printf(" length portion input: %f\n", (double)length_portion); + printf(" requested path length: %f\n", (double)requested_length); + printf(" distance to next point: %f\n", (double)distance_to_next_point); + printf(" length travelled: %f\n", (double)length_travelled); + printf(" lerp-factor: %f\n", (double)lerp_factor); + printf(" ith point (%f, %f)\n", (double)profile->path[i].x, (double)profile->path[i].y); + printf(" next point(%f, %f)\n", (double)profile->path[i + 1].x, (double)profile->path[i + 1].y); +#endif + + *x_out = interpf(profile->table[i].x, profile->table[i + 1].x, lerp_factor); + *y_out = interpf(profile->table[i].y, profile->table[i + 1].y, lerp_factor); +} diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 53e5f1fdfe5..2753cbd9be2 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -29,6 +29,7 @@ #include "DNA_anim_types.h" #include "DNA_collection_types.h" +#include "DNA_curveprofile_types.h" #include "DNA_linestyle_types.h" #include "DNA_mesh_types.h" #include "DNA_node_types.h" @@ -80,6 +81,7 @@ #include "BKE_node.h" #include "BKE_object.h" #include "BKE_paint.h" +#include "BKE_curveprofile.h" #include "BKE_rigidbody.h" #include "BKE_scene.h" #include "BKE_screen.h" @@ -182,6 +184,8 @@ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag) /* duplicate Grease Pencil multiframe fallof */ ts->gp_sculpt.cur_falloff = BKE_curvemapping_copy(ts->gp_sculpt.cur_falloff); ts->gp_sculpt.cur_primitive = BKE_curvemapping_copy(ts->gp_sculpt.cur_primitive); + + ts->custom_bevel_profile_preset = BKE_curveprofile_copy(ts->custom_bevel_profile_preset); return ts; } @@ -224,6 +228,10 @@ void BKE_toolsettings_free(ToolSettings *toolsettings) BKE_curvemapping_free(toolsettings->gp_sculpt.cur_primitive); } + if (toolsettings->custom_bevel_profile_preset) { + BKE_curveprofile_free(toolsettings->custom_bevel_profile_preset); + } + MEM_freeN(toolsettings); } @@ -729,6 +737,9 @@ void BKE_scene_init(Scene *sce) copy_v3_v3(gp_brush->curcolor_sub, curcolor_sub); } + /* Curve Profile */ + sce->toolsettings->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE); + for (int i = 0; i < ARRAY_SIZE(sce->orientation_slots); i++) { sce->orientation_slots[i].index_custom = -1; } diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 167917f7b6a..f6c1cd0380a 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -74,6 +74,7 @@ #include "DNA_object_types.h" #include "DNA_packedFile_types.h" #include "DNA_particle_types.h" +#include "DNA_curveprofile_types.h" #include "DNA_lightprobe_types.h" #include "DNA_rigidbody_types.h" #include "DNA_text_types.h" @@ -132,6 +133,7 @@ #include "BKE_paint.h" #include "BKE_particle.h" #include "BKE_pointcache.h" +#include "BKE_curveprofile.h" #include "BKE_report.h" #include "BKE_scene.h" #include "BKE_screen.h" @@ -2698,6 +2700,19 @@ static void direct_link_curvemapping(FileData *fd, CurveMapping *cumap) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Read CurveProfile + * \{ */ + +static void direct_link_curveprofile(FileData *fd, CurveProfile *profile) +{ + profile->path = newdataadr(fd, profile->path); + profile->table = NULL; + profile->segments = NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Read ID: Brush * \{ */ @@ -5802,6 +5817,13 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb) } } } + else if (md->type == eModifierType_Bevel) { + BevelModifierData *bmd = (BevelModifierData *)md; + bmd->custom_profile = newdataadr(fd, bmd->custom_profile); + if (bmd->custom_profile) { + direct_link_curveprofile(fd, bmd->custom_profile); + } + } } } @@ -6758,6 +6780,13 @@ static void direct_link_scene(FileData *fd, Scene *sce) if (sce->toolsettings->gp_sculpt.cur_primitive) { direct_link_curvemapping(fd, sce->toolsettings->gp_sculpt.cur_primitive); } + + /* Relink toolsettings curve profile */ + sce->toolsettings->custom_bevel_profile_preset = newdataadr( + fd, sce->toolsettings->custom_bevel_profile_preset); + if (sce->toolsettings->custom_bevel_profile_preset) { + direct_link_curveprofile(fd, sce->toolsettings->custom_bevel_profile_preset); + } } if (sce->ed) { diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 50363e3f42a..761424a5879 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -36,6 +36,7 @@ #include "DNA_cloth_types.h" #include "DNA_collection_types.h" #include "DNA_constraint_types.h" +#include "DNA_curveprofile_types.h" #include "DNA_gpu_types.h" #include "DNA_light_types.h" #include "DNA_layer_types.h" @@ -74,6 +75,7 @@ #include "BKE_node.h" #include "BKE_paint.h" #include "BKE_pointcache.h" +#include "BKE_curveprofile.h" #include "BKE_report.h" #include "BKE_rigidbody.h" #include "BKE_screen.h" @@ -3932,10 +3934,35 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) { /* Versioning code until next subversion bump goes here. */ + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) { sa->flag &= ~AREA_FLAG_UNUSED_6; } } + + /* Add custom curve profile to toolsettings for bevel tool */ + if (!DNA_struct_elem_find(fd->filesdna, "ToolSettings", "CurveProfile", "custom_profile")) { + for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) { + ToolSettings *ts = scene->toolsettings; + if ((ts) && (ts->custom_bevel_profile_preset == NULL)) { + ts->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE); + } + } + } + + /* Add custom curve profile to bevel modifier */ + if (!DNA_struct_elem_find(fd->filesdna, "BevelModifier", "CurveProfile", "custom_profile")) { + for (Object *object = bmain->objects.first; object != NULL; object = object->id.next) { + for (ModifierData *md = object->modifiers.first; md; md = md->next) { + if (md->type == eModifierType_Bevel) { + BevelModifierData *bmd = (BevelModifierData *)md; + if (!bmd->custom_profile) { + bmd->custom_profile = BKE_curveprofile_add(PROF_PRESET_LINE); + } + } + } + } + } } } diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 45fb62a4ac0..05758b446ad 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -27,6 +27,7 @@ #include "BLI_system.h" #include "DNA_camera_types.h" +#include "DNA_curveprofile_types.h" #include "DNA_gpencil_types.h" #include "DNA_mesh_types.h" #include "DNA_object_types.h" @@ -51,6 +52,7 @@ #include "BKE_paint.h" #include "BKE_screen.h" #include "BKE_workspace.h" +#include "BKE_curveprofile.h" #include "BLO_readfile.h" @@ -324,6 +326,11 @@ static void blo_update_defaults_scene(Main *bmain, Scene *scene) copy_v2_v2(me->mloopuv[i].uv, uv_values[i]); } } + + /* Make sure that the curve profile is initialized */ + if (ts->custom_bevel_profile_preset == NULL) { + ts->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE); + } } /** diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index 2966a030f63..3390d30ad5d 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -139,6 +139,7 @@ #include "DNA_workspace_types.h" #include "DNA_movieclip_types.h" #include "DNA_mask_types.h" +#include "DNA_curveprofile_types.h" #include "MEM_guardedalloc.h" // MEM_freeN #include "BLI_bitmap.h" @@ -953,6 +954,12 @@ static void write_curvemapping(WriteData *wd, CurveMapping *cumap) write_curvemapping_curves(wd, cumap); } +static void write_CurveProfile(WriteData *wd, CurveProfile *profile) +{ + writestruct(wd, DATA, CurveProfile, 1, profile); + writestruct(wd, DATA, CurveProfilePoint, profile->path_len, profile->path); +} + static void write_node_socket(WriteData *wd, bNodeSocket *sock) { /* actual socket writing */ @@ -1759,6 +1766,12 @@ static void write_modifiers(WriteData *wd, ListBase *modbase) } } } + else if (md->type == eModifierType_Bevel) { + BevelModifierData *bmd = (BevelModifierData *)md; + if (bmd->custom_profile) { + write_CurveProfile(wd, bmd->custom_profile); + } + } } } @@ -2533,6 +2546,10 @@ static void write_scene(WriteData *wd, Scene *sce) if (tos->gp_sculpt.cur_primitive) { write_curvemapping(wd, tos->gp_sculpt.cur_primitive); } + /* Write the curve profile to the file. */ + if (tos->custom_bevel_profile_preset) { + write_CurveProfile(wd, tos->custom_bevel_profile_preset); + } write_paint(wd, &tos->imapaint.paint); diff --git a/source/blender/bmesh/intern/bmesh_opdefines.c b/source/blender/bmesh/intern/bmesh_opdefines.c index 7086cea1ace..74d01dca66a 100644 --- a/source/blender/bmesh/intern/bmesh_opdefines.c +++ b/source/blender/bmesh/intern/bmesh_opdefines.c @@ -1725,6 +1725,12 @@ static BMO_FlagSet bmo_enum_bevel_miter_type[] = { {0, NULL}, }; +static BMO_FlagSet bmo_enum_bevel_vmesh_method[] = { + {BEVEL_VMESH_ADJ, "ADJ"}, + {BEVEL_VMESH_CUTOFF, "CUTOFF"}, + {0, NULL}, +}; + /* * Bevel. * @@ -1735,7 +1741,8 @@ static BMOpDefine bmo_bevel_def = { /* slots_in */ {{"geom", BMO_OP_SLOT_ELEMENT_BUF, {BM_VERT | BM_EDGE | BM_FACE}}, /* input edges and vertices */ {"offset", BMO_OP_SLOT_FLT}, /* amount to offset beveled edge */ - {"offset_type", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM}, bmo_enum_bevel_offset_type}, /* how to measure the offset */ + {"offset_type", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM}, + bmo_enum_bevel_offset_type}, /* how to measure the offset */ {"segments", BMO_OP_SLOT_INT}, /* number of segments in bevel */ {"profile", BMO_OP_SLOT_FLT}, /* profile shape, 0->1 (.5=>round) */ {"vertex_only", BMO_OP_SLOT_BOOL}, /* only bevel vertices, not edges */ @@ -1746,13 +1753,18 @@ static BMOpDefine bmo_bevel_def = { {"mark_sharp", BMO_OP_SLOT_BOOL}, /* extend edge data to allow sharp edges to run across bevels */ {"harden_normals", BMO_OP_SLOT_BOOL}, /* harden normals */ {"face_strength_mode", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM}, - bmo_enum_bevel_face_strength_type}, /* whether to set face strength, and which faces to set if so */ + bmo_enum_bevel_face_strength_type}, /* whether to set face strength, and which faces to set if so */ {"miter_outer", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM}, - bmo_enum_bevel_miter_type}, /* outer miter kind */ + bmo_enum_bevel_miter_type}, /* outer miter kind */ {"miter_inner", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM}, - bmo_enum_bevel_miter_type}, /* outer miter kind */ + bmo_enum_bevel_miter_type}, /* outer miter kind */ {"spread", BMO_OP_SLOT_FLT}, /* amount to offset beveled edge */ {"smoothresh", BMO_OP_SLOT_FLT}, /* for passing mesh's smoothresh, used in hardening */ + {"use_custom_profile", BMO_OP_SLOT_BOOL}, /* Whether to use custom profile feature */ + /* the ProfileWiget struct for the custom profile shape */ + {"custom_profile", BMO_OP_SLOT_PTR, {(int)BMO_OP_SLOT_SUBTYPE_PTR_STRUCT}}, + {"vmesh_method", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM}, + bmo_enum_bevel_vmesh_method}, {{'\0'}}, }, /* slots_out */ diff --git a/source/blender/bmesh/intern/bmesh_operator_api.h b/source/blender/bmesh/intern/bmesh_operator_api.h index 2039289dcd7..dbd2bf076c6 100644 --- a/source/blender/bmesh/intern/bmesh_operator_api.h +++ b/source/blender/bmesh/intern/bmesh_operator_api.h @@ -237,6 +237,7 @@ typedef enum eBMOpSlotSubType_Ptr { BMO_OP_SLOT_SUBTYPE_PTR_SCENE = 101, BMO_OP_SLOT_SUBTYPE_PTR_OBJECT = 102, BMO_OP_SLOT_SUBTYPE_PTR_MESH = 103, + BMO_OP_SLOT_SUBTYPE_PTR_STRUCT = 104, } eBMOpSlotSubType_Ptr; typedef enum eBMOpSlotSubType_Int { BMO_OP_SLOT_SUBTYPE_INT_ENUM = 200, @@ -294,8 +295,8 @@ typedef struct BMOpSlot { BLI_assert(((slot >= (op)->slots_in) && (slot < &(op)->slots_in[BMO_OP_MAX_SLOTS])) || \ ((slot >= (op)->slots_out) && (slot < &(op)->slots_out[BMO_OP_MAX_SLOTS]))) -/* way more than probably needed, compiler complains if limit hit */ -#define BMO_OP_MAX_SLOTS 20 +/* Limit hit, so expanded for bevel operator. Compiler complains if limit is hit. */ +#define BMO_OP_MAX_SLOTS 21 /* BMOpDefine->type_flag */ typedef enum { diff --git a/source/blender/bmesh/intern/bmesh_operators.h b/source/blender/bmesh/intern/bmesh_operators.h index 78e8ce04115..9f0107db693 100644 --- a/source/blender/bmesh/intern/bmesh_operators.h +++ b/source/blender/bmesh/intern/bmesh_operators.h @@ -126,6 +126,12 @@ enum { BEVEL_MITER_ARC, }; +/* Bevel vertex mesh creation methods */ +enum { + BEVEL_VMESH_ADJ, + BEVEL_VMESH_CUTOFF, +}; + /* Normal Face Strength values */ enum { FACE_STRENGTH_WEAK = -16384, diff --git a/source/blender/bmesh/operators/bmo_bevel.c b/source/blender/bmesh/operators/bmo_bevel.c index 33be9559db3..1d5bcf8b160 100644 --- a/source/blender/bmesh/operators/bmo_bevel.c +++ b/source/blender/bmesh/operators/bmo_bevel.c @@ -24,6 +24,8 @@ #include "bmesh.h" #include "bmesh_tools.h" +#include "BKE_curveprofile.h" +#include "DNA_curveprofile_types.h" #include "intern/bmesh_operators_private.h" /* own include */ @@ -45,6 +47,9 @@ void bmo_bevel_exec(BMesh *bm, BMOperator *op) const int miter_inner = BMO_slot_int_get(op->slots_in, "miter_inner"); const float spread = BMO_slot_float_get(op->slots_in, "spread"); const float smoothresh = BMO_slot_float_get(op->slots_in, "smoothresh"); + const bool use_custom_profile = BMO_slot_bool_get(op->slots_in, "use_custom_profile"); + const CurveProfile *custom_profile = BMO_slot_ptr_get(op->slots_in, "custom_profile"); + const int vmesh_method = BMO_slot_int_get(op->slots_in, "vmesh_method"); if (offset > 0) { BMOIter siter; @@ -87,7 +92,10 @@ void bmo_bevel_exec(BMesh *bm, BMOperator *op) miter_outer, miter_inner, spread, - smoothresh); + smoothresh, + use_custom_profile, + custom_profile, + vmesh_method); BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG); BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "edges.out", BM_EDGE, BM_ELEM_TAG); diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c index 797e2ca864e..6989946147e 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.c +++ b/source/blender/bmesh/tools/bmesh_bevel.c @@ -38,6 +38,9 @@ #include "eigen_capi.h" +#include "BKE_curveprofile.h" +#include "DNA_curveprofile_types.h" + #include "bmesh.h" #include "bmesh_bevel.h" /* own include */ @@ -50,10 +53,22 @@ #define BEVEL_EPSILON_BIG_SQ 1e-8f #define BEVEL_EPSILON_ANG DEG2RADF(2.0f) #define BEVEL_SMALL_ANG DEG2RADF(10.0f) +/** Difference in dot products that corresponds to 10 degree difference between vectors. */ +#define BEVEL_SMALL_ANG_DOT 1 - cosf(BEVEL_SMALL_ANG) #define BEVEL_MAX_ADJUST_PCT 10.0f #define BEVEL_MAX_AUTO_ADJUST_PCT 300.0f #define BEVEL_MATCH_SPEC_WEIGHT 0.2 +//#define DEBUG_CUSTOM_PROFILE_CUTOFF +//#define DEBUG_CUSTOM_PROFILE_SAMPLE + +#if defined(DEBUG_PROFILE_ORIENTATION_DRAW) || defined(DEBUG_CUSTOM_PROFILE_PIPE) +static float debug_color_red[4] = {1.0f, 0.0f, 0.0f, 1.0f}; +static float debug_color_blue[4] = {0.0f, 0.0f, 1.0f, 1.0f}; +extern void DRW_debug_sphere(const float center[3], const float radius, const float color[4]); +extern void DRW_debug_line_v3v3(const float v1[3], const float v2[3], const float color[4]); +#endif + /* happens far too often, uncomment for development */ // #define BEVEL_ASSERT_PROJECT @@ -64,72 +79,108 @@ typedef struct NewVert { BMVert *v; float co[3]; - // int _pad; + char _pad[4]; } NewVert; struct BoundVert; /* Data for one end of an edge involved in a bevel */ typedef struct EdgeHalf { - struct EdgeHalf *next, *prev; /* in CCW order */ - BMEdge *e; /* original mesh edge */ - BMFace *fprev; /* face between this edge and previous, if any */ - BMFace *fnext; /* face between this edge and next, if any */ - struct BoundVert *leftv; /* left boundary vert (looking along edge to end) */ - struct BoundVert *rightv; /* right boundary vert, if beveled */ - int profile_index; /* offset into profile to attach non-beveled edge */ - int seg; /* how many segments for the bevel */ - float offset_l; /* offset for this edge, on left side */ - float offset_r; /* offset for this edge, on right side */ - float offset_l_spec; /* user specification for offset_l */ - float offset_r_spec; /* user specification for offset_r */ - bool is_bev; /* is this edge beveled? */ - bool is_rev; /* is e->v2 the vertex at this end? */ - bool is_seam; /* is e a seam for custom loopdata (e.g., UVs)? */ - // int _pad; + /** Other EdgeHalves connected to the same BevVert, in CCW order. */ + struct EdgeHalf *next, *prev; + /** Original mesh edge */ + BMEdge *e; + /** Face between this edge and previous, if any */ + BMFace *fprev; + /** Face between this edge and next, if any */ + BMFace *fnext; + /** Left boundary vert (looking along edge to end) */ + struct BoundVert *leftv; + /** Right boundary vert, if beveled */ + struct BoundVert *rightv; + /** Offset into profile to attach non-beveled edge */ + int profile_index; + /** How many segments for the bevel */ + int seg; + /** Offset for this edge, on left side */ + float offset_l; + /** Offset for this edge, on right side */ + float offset_r; + /** User specification for offset_l */ + float offset_l_spec; + /** User specification for offset_r */ + float offset_r_spec; + /** Is this edge beveled? */ + bool is_bev; + /** Is e->v2 the vertex at this end? */ + bool is_rev; + /** Is e a seam for custom loopdata (e.g., UVs)? */ + bool is_seam; + /** Used during the custom profile orientation pass */ + bool visited_rpo; + char _pad[4]; } EdgeHalf; -/* Profile specification. +/* Profile specification: * Many interesting profiles are in family of superellipses: * (abs(x/a))^r + abs(y/b))^r = 1 * r==2 => ellipse; r==1 => line; r < 1 => concave; r > 1 => bulging out. * Special cases: let r==0 mean straight-inward, and r==4 mean straight outward. - * The profile is an arc with control points coa, midco, + * The profile is a path defined with start, middle, and end control points * projected onto a plane (plane_no is normal, plane_co is a point on it) * via lines in a given direction (proj_dir). * After the parameters are all set, the actual profile points are calculated - * and point in prof_co. We also may need profile points for a higher resolution - * number of segments, in order to make the vertex mesh pattern, and that goes - * in prof_co_2. + * and pointed to by prof_co. We also may need profile points for a higher resolution + * number of segments for the subdivision while making the ADJ vertex mesh pattern, + * and that goes in prof_co_2. */ typedef struct Profile { - float super_r; /* superellipse r parameter */ - float coa[3]; /* start control point for profile */ - float midco[3]; /* mid control point for profile */ - float cob[3]; /* end control point for profile */ - float plane_no[3]; /* normal of plane to project to */ - float plane_co[3]; /* coordinate on plane to project to */ - float proj_dir[3]; /* direction of projection line */ - float *prof_co; /* seg+1 profile coordinates (triples of floats) */ - float *prof_co_2; /* like prof_co, but for seg power of 2 >= seg */ + /** Superellipse r parameter */ + float super_r; + /** Height for profile cutoff face sides */ + float height; + /** Start control point for profile */ + float start[3]; + /** Mid control point for profile */ + float middle[3]; + /** End control point for profile */ + float end[3]; + /** Normal of plane to project to */ + float plane_no[3]; + /** Coordinate on plane to project to */ + float plane_co[3]; + /** Direction of projection line */ + float proj_dir[3]; + /** seg+1 profile coordinates (triples of floats) */ + float *prof_co; + /** Like prof_co, but for seg power of 2 >= seg */ + float *prof_co_2; } Profile; #define PRO_SQUARE_R 1e4f #define PRO_CIRCLE_R 2.0f #define PRO_LINE_R 1.0f #define PRO_SQUARE_IN_R 0.0f -/* Cache result of expensive calculation of u parameter values to +/** The un-transformed 2D storage of profile vertex locations. Also for non-custom profiles + * this serves as a cache for the results of the expensive calculation of u parameter values to * get even spacing on superellipse for current BevelParams seg * and pro_super_r. */ typedef struct ProfileSpacing { - double *xvals; /* seg+1 x values */ - double *xvals_2; /* seg_2+1 x values, seg_2 = power of 2 >= seg */ - double *yvals; /* seg+1 y values */ - double *yvals_2; /* seg_2+1 y values, seg_2 = power of 2 >= seg */ - int seg_2; /* the seg_2 value */ + /** The profile's seg+1 x values */ + double *xvals; + /** The profile's seg+1 y values */ + double *yvals; + /** The profile's seg_2+1 x values, (seg_2 = power of 2 >= seg) */ + double *xvals_2; + /** The profile's seg_2+1 y values, (seg_2 = power of 2 >= seg) */ + double *yvals_2; + /** The power of two greater than or equal to the number of segments. */ + int seg_2; + /** How far "out" the profile is, used at the start of subdivision */ + float fullness; } ProfileSpacing; -/* An element in a cyclic boundary of a Vertex Mesh (VMesh) */ +/** An element in a cyclic boundary of a Vertex Mesh (VMesh) */ typedef struct BoundVert { /** In CCW order. */ struct BoundVert *next, *prev; @@ -137,7 +188,7 @@ typedef struct BoundVert { /** First of edges attached here: in CCW order. */ EdgeHalf *efirst; EdgeHalf *elast; - /** The "edge between" that this is on, in offset_on_edge_between case. */ + /** The "edge between" that this boundvert on, in offset_on_edge_between case. */ EdgeHalf *eon; /** Beveled edge whose left side is attached here, if any. */ EdgeHalf *ebev; @@ -157,26 +208,35 @@ typedef struct BoundVert { bool is_arc_start; /** This boundvert begins a patch profile */ bool is_patch_start; + /** Is this boundvert the side of the custom profile's start */ + bool is_profile_start; + char _pad[3]; /** Length of seam starting from current boundvert to next boundvert with ccw ordering */ int seam_len; /** Same as seam_len but defines length of sharp edges */ int sharp_len; - // int _pad; } BoundVert; -/* Mesh structure replacing a vertex */ +/** Mesh structure replacing a vertex */ typedef struct VMesh { - NewVert *mesh; /* allocated array - size and structure depends on kind */ - BoundVert *boundstart; /* start of boundary double-linked list */ - int count; /* number of vertices in the boundary */ - int seg; /* common # of segments for segmented edges */ + /** Allocated array - size and structure depends on kind */ + NewVert *mesh; + /** Start of boundary double-linked list */ + BoundVert *boundstart; + /** Number of vertices in the boundary */ + int count; + /** Common number of segments for segmented edges (same as bp->seg) */ + int seg; + /** The kind of mesh to build at the corner vertex meshes */ enum { M_NONE, /* no polygon mesh needed */ M_POLY, /* a simple polygon */ M_ADJ, /* "adjacent edges" mesh pattern */ M_TRI_FAN, /* a simple polygon - fan filled */ + M_CUTOFF, /* A triangulated face at the end of each profile */ } mesh_kind; - // int _pad; + + int _pad; } VMesh; /* Data for a vertex involved in a bevel */ @@ -196,6 +256,7 @@ typedef struct BevVert { /** Used in graph traversal */ bool visited; /** Array of size edgecount; CCW order from vertex normal side */ + char _pad[6]; EdgeHalf *edges; /** Array of size wirecount of wire edges */ BMEdge **wire_edges; @@ -209,7 +270,7 @@ typedef enum { F_NONE, /** Original face, not touched */ F_ORIG, - /** Face for construction aroun a vert */ + /** Face for construction around a vert */ F_VERT, /** Face for a beveled edge */ F_EDGE, @@ -217,11 +278,21 @@ typedef enum { F_RECON, } FKind; +/** Helper for keeping track of angle kind. */ +enum { + /** Angle less than 180 degrees */ + ANGLE_SMALLER = -1, + /** 180 degree angle */ + ANGLE_STRAIGHT = 0, + /** Angle greater than 180 degrees */ + ANGLE_LARGER = 1, +}; + #if 0 static const char* fkind_names[] = {"F_NONE", "F_ORIG", "F_VERT", "F_EDGE", "F_RECON"}; /* DEBUG */ #endif -/* Bevel parameters and state */ +/** Bevel parameters and state */ typedef struct BevelParams { /** Records BevVerts made: key BMVert*, value BevVert* */ GHash *vert_hash; @@ -229,9 +300,10 @@ typedef struct BevelParams { GHash *face_hash; /** Use for all allocs while bevel runs, if we need to free we can switch to mempool. */ MemArena *mem_arena; - /** Parameter values for evenly spaced profiles. */ + /** Profile vertex location and spacings */ ProfileSpacing pro_spacing; - + /** Parameter values for evenly spaced profile points for the miter profiles */ + ProfileSpacing pro_spacing_miter; /** Blender units to offset each side of a beveled edge. */ float offset; /** How offset is measured; enum defined in bmesh_operators.h */ @@ -258,6 +330,11 @@ typedef struct BevelParams { bool mark_sharp; /** Should we harden normals? */ bool harden_normals; + /** Should we use the custom profiles feature? */ + bool use_custom_profile; + char _pad[3]; + /** The struct used to store the custom profile input */ + const struct CurveProfile *custom_profile; /** Vertex group array, maybe set if vertex_only. */ const struct MDeformVert *dvert; /** Vertex group index, maybe set if vertex_only. */ @@ -270,6 +347,8 @@ typedef struct BevelParams { int miter_outer; /** What kind of miter pattern to use on non-reflex angles. */ int miter_inner; + /** The method to use for vertex mesh creation */ + int vmesh_method; /** Amount to spread when doing inside miter. */ float spread; /** Mesh's smoothresh, used if hardening. */ @@ -278,8 +357,6 @@ typedef struct BevelParams { // #pragma GCC diagnostic ignored "-Wpadded" -// #include "bevdebug.c" - /* Some flags to re-enable old behavior for a while, * in case fixes broke things not caught by regression tests. */ static int bev_debug_flags = 0; @@ -367,6 +444,7 @@ static BoundVert *add_new_bound_vert(MemArena *mem_arena, VMesh *vm, const float ans->any_seam = false; ans->is_arc_start = false; ans->is_patch_start = false; + ans->is_profile_start = false; vm->count++; return ans; } @@ -628,7 +706,7 @@ static BMFace *bev_create_ngon(BMesh *bm, } if (mat_nr >= 0) { - f->mat_nr = mat_nr; + f->mat_nr = (short)mat_nr; } return f; } @@ -812,7 +890,7 @@ static void bev_merge_edge_uvs(BMesh *bm, BMEdge *bme, BMVert *v) } /* Calculate coordinates of a point a distance d from v on e->e and return it in slideco */ -static void slide_dist(EdgeHalf *e, BMVert *v, float d, float slideco[3]) +static void slide_dist(EdgeHalf *e, BMVert *v, float d, float r_slideco[3]) { float dir[3], len; @@ -821,8 +899,8 @@ static void slide_dist(EdgeHalf *e, BMVert *v, float d, float slideco[3]) if (d > len) { d = len - (float)(50.0 * BEVEL_EPSILON_D); } - copy_v3_v3(slideco, v->co); - madd_v3_v3fl(slideco, dir, -d); + copy_v3_v3(r_slideco, v->co); + madd_v3_v3fl(r_slideco, dir, -d); } /* Is co not on the edge e? if not, return the closer end of e in ret_closer_v */ @@ -873,13 +951,13 @@ static int edges_angle_kind(EdgeHalf *e1, EdgeHalf *e2, BMVert *v) } dot = dot_v3v3(cross, no); if (fabsf(dot) < BEVEL_EPSILON_BIG) { - return 0; + return ANGLE_STRAIGHT; } else if (dot < 0.0f) { - return 1; + return ANGLE_LARGER; } else { - return -1; + return ANGLE_SMALLER; } } @@ -917,7 +995,7 @@ static bool point_between_edges(float co[3], BMVert *v, BMFace *f, EdgeHalf *e1, /* * Calculate the meeting point between the offset edges for e1 and e2, putting answer in meetco. * e1 and e2 share vertex v and face f (may be NULL) and viewed from the normal side of - * the bevel vertex, e1 precedes e2 in CCW order. + * the bevel vertex, e1 precedes e2 in CCW order. * Except: if edges_between is true, there are edges between e1 and e2 in CCW order so they * don't share a common face. We want the meeting point to be on an existing face so it * should be dropped onto one of the intermediate faces, if possible. @@ -978,7 +1056,7 @@ static void offset_meet( normalize_v3(norm_perp1); copy_v3_v3(off1a, v->co); d = max_ff(e1->offset_r, e2->offset_l); - d = d / cos(ang / 2.0f); + d = d / cosf(ang / 2.0f); madd_v3_v3fl(off1a, norm_perp1, d); copy_v3_v3(meetco, off1a); } @@ -1260,37 +1338,37 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) negate_v3(pro->proj_dir); } normalize_v3(pro->proj_dir); - project_to_edge(e->e, co1, co2, pro->midco); + project_to_edge(e->e, co1, co2, pro->middle); if (DEBUG_OLD_PROJ_TO_PERP_PLANE) { - /* put arc endpoints on plane with normal proj_dir, containing midco */ + /* put arc endpoints on plane with normal proj_dir, containing middle */ add_v3_v3v3(co3, co1, pro->proj_dir); - if (!isect_line_plane_v3(pro->coa, co1, co3, pro->midco, pro->proj_dir)) { + if (!isect_line_plane_v3(pro->start, co1, co3, pro->middle, pro->proj_dir)) { /* shouldn't happen */ - copy_v3_v3(pro->coa, co1); + copy_v3_v3(pro->start, co1); } add_v3_v3v3(co3, co2, pro->proj_dir); - if (!isect_line_plane_v3(pro->cob, co2, co3, pro->midco, pro->proj_dir)) { + if (!isect_line_plane_v3(pro->end, co2, co3, pro->middle, pro->proj_dir)) { /* shouldn't happen */ - copy_v3_v3(pro->cob, co2); + copy_v3_v3(pro->end, co2); } } else { - copy_v3_v3(pro->coa, co1); - copy_v3_v3(pro->cob, co2); + copy_v3_v3(pro->start, co1); + copy_v3_v3(pro->end, co2); } - /* default plane to project onto is the one with triangle co1 - midco - co2 in it */ - sub_v3_v3v3(d1, pro->midco, co1); - sub_v3_v3v3(d2, pro->midco, co2); + /* default plane to project onto is the one with triangle co1 - middle - co2 in it */ + sub_v3_v3v3(d1, pro->middle, co1); + sub_v3_v3v3(d2, pro->middle, co2); normalize_v3(d1); normalize_v3(d2); cross_v3_v3v3(pro->plane_no, d1, d2); normalize_v3(pro->plane_no); if (nearly_parallel(d1, d2)) { - /* co1 - midco -co2 are collinear. + /* co1 - middle -co2 are collinear. * Should be case that beveled edge is coplanar with two boundary verts. * We want to move the profile to that common plane, if possible. * That makes the multi-segment bevels curve nicely in that plane, as users expect. - * The new midco should be either v (when neighbor edges are unbeveled) + * The new middle should be either v (when neighbor edges are unbeveled) * or the intersection of the offset lines (if they are). * If the profile is going to lead into unbeveled edges on each side * (that is, both BoundVerts are "on-edge" points on non-beveled edges) @@ -1300,14 +1378,14 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) } else { if (DEBUG_OLD_PROJ_TO_PERP_PLANE) { - copy_v3_v3(pro->coa, co1); - copy_v3_v3(pro->cob, co2); + copy_v3_v3(pro->start, co1); + copy_v3_v3(pro->end, co2); } if (DEBUG_OLD_FLAT_MID) { - copy_v3_v3(pro->midco, bv->v->co); + copy_v3_v3(pro->middle, bv->v->co); } else { - copy_v3_v3(pro->midco, bv->v->co); + copy_v3_v3(pro->middle, bv->v->co); if (e->prev->is_bev && e->next->is_bev && bv->selcount >= 3) { /* want mid at the meet point of next and prev offset edges */ float d3[3], d4[3], co4[3], meetco[3], isect2[3]; @@ -1319,7 +1397,7 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) normalize_v3(d4); if (nearly_parallel(d3, d4)) { /* offset lines are collinear - want linear interpolation */ - mid_v3_v3v3(pro->midco, co1, co2); + mid_v3_v3v3(pro->middle, co1, co2); do_linear_interp = true; } else { @@ -1327,20 +1405,20 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) add_v3_v3v3(co4, co2, d4); isect_kind = isect_line_line_v3(co1, co3, co2, co4, meetco, isect2); if (isect_kind != 0) { - copy_v3_v3(pro->midco, meetco); + copy_v3_v3(pro->middle, meetco); } else { /* offset lines don't intersect - want linear interpolation */ - mid_v3_v3v3(pro->midco, co1, co2); + mid_v3_v3v3(pro->middle, co1, co2); do_linear_interp = true; } } } } - copy_v3_v3(pro->cob, co2); - sub_v3_v3v3(d1, pro->midco, co1); + copy_v3_v3(pro->end, co2); + sub_v3_v3v3(d1, pro->middle, co1); normalize_v3(d1); - sub_v3_v3v3(d2, pro->midco, co2); + sub_v3_v3v3(d2, pro->middle, co2); normalize_v3(d2); cross_v3_v3v3(pro->plane_no, d1, d2); normalize_v3(pro->plane_no); @@ -1357,9 +1435,9 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) copy_v3_v3(pro->plane_co, co1); } else if (bndv->is_arc_start) { - /* assume pro->midco was already set */ - copy_v3_v3(pro->coa, co1); - copy_v3_v3(pro->cob, co2); + /* assume pro->middle was alredy set */ + copy_v3_v3(pro->start, co1); + copy_v3_v3(pro->end, co2); pro->super_r = PRO_CIRCLE_R; zero_v3(pro->plane_co); zero_v3(pro->plane_no); @@ -1368,9 +1446,9 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) } if (do_linear_interp) { pro->super_r = PRO_LINE_R; - copy_v3_v3(pro->coa, co1); - copy_v3_v3(pro->cob, co2); - mid_v3_v3v3(pro->midco, co1, co2); + copy_v3_v3(pro->start, co1); + copy_v3_v3(pro->end, co2); + mid_v3_v3v3(pro->middle, co1, co2); /* won't use projection for this line profile */ zero_v3(pro->plane_co); zero_v3(pro->plane_no); @@ -1378,22 +1456,23 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv) } } -/* Maybe move the profile plane for bndv->ebev to the plane its profile's coa, cob and the +/* Maybe move the profile plane for bndv->ebev to the plane its profile's start, and the * original beveled vert, bmv. This will usually be the plane containing its adjacent - * non-beveled edges, but sometimes coa and cob are not on those edges. - */ -static void move_profile_plane(BoundVert *bndv, BMVert *bmv) + * non-beveled edges, but sometimes the start and the end are not on those edges. + * + * Currently just used in build boundary terminal edge */ +static void move_profile_plane(BoundVert *bndv, BMVert *bmvert) { float d1[3], d2[3], no[3], no2[3], no3[3], dot2, dot3; Profile *pro = &bndv->profile; - /* only do this if projecting, and coa, cob, and proj_dir are not coplanar */ + /* only do this if projecting, and start, end, and proj_dir are not coplanar */ if (is_zero_v3(pro->proj_dir)) { return; } - sub_v3_v3v3(d1, bmv->co, pro->coa); + sub_v3_v3v3(d1, bmvert->co, pro->start); normalize_v3(d1); - sub_v3_v3v3(d2, bmv->co, pro->cob); + sub_v3_v3v3(d2, bmvert->co, pro->end); normalize_v3(d2); cross_v3_v3v3(no, d1, d2); cross_v3_v3v3(no2, d1, pro->proj_dir); @@ -1584,35 +1663,35 @@ static double superellipse_co(double x, float r, bool rbig) } /* Find the point on given profile at parameter i which goes from 0 to n as - * the profile is moved from pro->coa to pro->cob. + * the profile is moved from pro->start to pro->end. * We assume that n is either the global seg number or a power of 2 less than * or equal to the power of 2 >= seg. * In the latter case, we subsample the profile for seg_2, which will not necessarily * give equal spaced chords, but is in fact more what is desired by the cubic subdivision * method used to make the vmesh pattern. */ -static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int n, float r_co[3]) +static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int nseg, float r_co[3]) { - int d; + int subsample_spacing; if (bp->seg == 1) { if (i == 0) { - copy_v3_v3(r_co, pro->coa); + copy_v3_v3(r_co, pro->start); } else { - copy_v3_v3(r_co, pro->cob); + copy_v3_v3(r_co, pro->end); } } else { - if (n == bp->seg) { + if (nseg == bp->seg) { BLI_assert(pro->prof_co != NULL); copy_v3_v3(r_co, pro->prof_co + 3 * i); } else { - BLI_assert(is_power_of_2_i(n) && n <= bp->pro_spacing.seg_2); - /* set d to spacing in prof_co_2 between subsamples */ - d = bp->pro_spacing.seg_2 / n; - copy_v3_v3(r_co, pro->prof_co_2 + 3 * i * d); + BLI_assert(is_power_of_2_i(nseg) && nseg <= bp->pro_spacing.seg_2); + /* Find spacing between subsamples in prof_co_2 */ + subsample_spacing = bp->pro_spacing.seg_2 / nseg; + copy_v3_v3(r_co, pro->prof_co_2 + 3 * i * subsample_spacing); } } } @@ -1624,15 +1703,16 @@ static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int n, * the coordinate values for the power of 2 >= bp->seg, * because the ADJ pattern needs power-of-2 boundaries * during construction. */ -static void calculate_profile(BevelParams *bp, BoundVert *bndv) +static void calculate_profile(BevelParams *bp, BoundVert *bndv, bool reversed, bool miter) { int i, k, ns; const double *xvals, *yvals; - float co[3], co2[3], p[3], m[4][4]; + float co[3], co2[3], p[3], map[4][4], bottom_corner[3], top_corner[3]; float *prof_co, *prof_co_k; float r; bool need_2, map_ok; Profile *pro = &bndv->profile; + ProfileSpacing *pro_spacing = (miter) ? &bp->pro_spacing_miter : &bp->pro_spacing; if (bp->seg == 1) { return; @@ -1640,27 +1720,39 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv) need_2 = bp->seg != bp->pro_spacing.seg_2; if (!pro->prof_co) { - pro->prof_co = (float *)BLI_memarena_alloc(bp->mem_arena, (bp->seg + 1) * 3 * sizeof(float)); + pro->prof_co = (float *)BLI_memarena_alloc(bp->mem_arena, + ((size_t)bp->seg + 1) * 3 * sizeof(float)); if (need_2) { pro->prof_co_2 = (float *)BLI_memarena_alloc( - bp->mem_arena, (bp->pro_spacing.seg_2 + 1) * 3 * sizeof(float)); + bp->mem_arena, ((size_t)bp->pro_spacing.seg_2 + 1) * 3 * sizeof(float)); } else { pro->prof_co_2 = pro->prof_co; } } r = pro->super_r; - if (r == PRO_LINE_R) { + if (!bp->use_custom_profile && r == PRO_LINE_R) { map_ok = false; } else { - map_ok = make_unit_square_map(pro->coa, pro->midco, pro->cob, m); - } + map_ok = make_unit_square_map(pro->start, pro->middle, pro->end, map); + } + if (bp->vmesh_method == BEVEL_VMESH_CUTOFF && map_ok) { + /* Calculate the "height" of the profile by putting the (0,0) and (1,1) corners of the + * un-transformed profile throught the 2D->3D map and calculating the distance between them */ + zero_v3(p); + mul_v3_m4v3(bottom_corner, map, p); + p[0] = 1.0f; + p[1] = 1.0f; + mul_v3_m4v3(top_corner, map, p); + pro->height = len_v3v3(bottom_corner, top_corner); + } + /* The first iteration is the nseg case, the second is the seg_2 case (if it's needed) */ for (i = 0; i < 2; i++) { if (i == 0) { ns = bp->seg; - xvals = bp->pro_spacing.xvals; - yvals = bp->pro_spacing.yvals; + xvals = pro_spacing->xvals; + yvals = pro_spacing->yvals; prof_co = pro->prof_co; } else { @@ -1668,33 +1760,42 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv) break; /* shares coords with pro->prof_co */ } ns = bp->pro_spacing.seg_2; - xvals = bp->pro_spacing.xvals_2; - yvals = bp->pro_spacing.yvals_2; + xvals = pro_spacing->xvals_2; + yvals = pro_spacing->yvals_2; prof_co = pro->prof_co_2; } - BLI_assert((r == PRO_LINE_R || (xvals != NULL && yvals != NULL)) && prof_co != NULL); + + /* Iterate over the vertices along the boundary arc */ for (k = 0; k <= ns; k++) { if (k == 0) { - copy_v3_v3(co, pro->coa); + copy_v3_v3(co, pro->start); } else if (k == ns) { - copy_v3_v3(co, pro->cob); + copy_v3_v3(co, pro->end); } else { if (map_ok) { - p[0] = xvals[k]; - p[1] = yvals[k]; + if (reversed) { + p[0] = (float)yvals[ns - k]; + p[1] = (float)xvals[ns - k]; + } + else { + p[0] = (float)xvals[k]; + p[1] = (float)yvals[k]; + } p[2] = 0.0f; - mul_v3_m4v3(co, m, p); + /* Do the 2D->3D transformation of the profile coordinates */ + mul_v3_m4v3(co, map, p); } else { - interp_v3_v3v3(co, pro->coa, pro->cob, (float)k / (float)ns); + interp_v3_v3v3(co, pro->start, pro->end, (float)k / (float)ns); } } - /* project co onto final profile plane */ - prof_co_k = prof_co + 3 * k; + /* Finish the 2D->3D transformation by projecting onto the final profile plane */ + prof_co_k = prof_co + 3 * k; /* Each coord takes up 3 spaces */ if (!is_zero_v3(pro->proj_dir)) { add_v3_v3v3(co2, co, pro->proj_dir); + /* pro->plane_co and pro->plane_no are filled in "set_profile_params" */ if (!isect_line_plane_v3(prof_co_k, co, co2, pro->plane_co, pro->plane_no)) { /* shouldn't happen */ copy_v3_v3(prof_co_k, co); @@ -1708,11 +1809,12 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv) } /* Snap a direction co to a superellipsoid with parameter super_r. - * For square profiles, midline says whether or not to snap to both planes. */ + * For square profiles, midline says whether or not to snap to both planes. + * + * Only currently used for the pipe and cube corner special cases */ static void snap_to_superellipsoid(float co[3], const float super_r, bool midline) { float a, b, c, x, y, z, r, rinv, dx, dy; - r = super_r; if (r == PRO_CIRCLE_R) { normalize_v3(co); @@ -1936,7 +2038,7 @@ static void bevel_extend_edge_data(BevVert *bv) } while (bcur != start); } -/* Mark edges as sharp if they are between a smooth recon face and a new face. */ +/* Mark edges as sharp if they are between a smooth reconstructed face and a new face. */ static void bevel_edges_sharp_boundary(BMesh *bm, BevelParams *bp) { BMIter fiter, liter; @@ -1972,7 +2074,7 @@ static void bevel_edges_sharp_boundary(BMesh *bm, BevelParams *bp) * And at boundaries between #F_EDGE and #F_VERT faces, the normals should match the #F_EDGE ones. * Assumes custom loop normals are in use. */ -static void bevel_harden_normals(BMesh *bm, BevelParams *bp) +static void bevel_harden_normals(BevelParams *bp, BMesh *bm) { BMIter liter, fiter; BMFace *f; @@ -2207,13 +2309,17 @@ static bool eh_on_plane(EdgeHalf *e) /* Calculate the profiles for all the BoundVerts of VMesh vm */ static void calculate_vm_profiles(BevelParams *bp, BevVert *bv, VMesh *vm) { - BoundVert *v; + BoundVert *bndv; - v = vm->boundstart; + bndv = vm->boundstart; do { - set_profile_params(bp, bv, v); - calculate_profile(bp, v); - } while ((v = v->next) != vm->boundstart); + set_profile_params(bp, bv, bndv); + /* Use the miter profile spacing struct if the default is filled with the custom profile. */ + bool miter_profile = bp->use_custom_profile && (bndv->is_arc_start || bndv->is_patch_start); + /* Don't bother reversing the profile if it's a miter profile */ + bool reverse_profile = !bndv->is_profile_start && !miter_profile; + calculate_profile(bp, bndv, reverse_profile, miter_profile); + } while ((bndv = bndv->next) != vm->boundstart); } /* Implements build_boundary for vertex-only case */ @@ -2263,14 +2369,15 @@ static void build_boundary_vertex_only(BevelParams *bp, BevVert *bv, bool constr static void build_boundary_terminal_edge(BevelParams *bp, BevVert *bv, EdgeHalf *efirst, - bool construct) + const bool construct) { MemArena *mem_arena = bp->mem_arena; VMesh *vm = bv->vmesh; - BoundVert *v; + BoundVert *bndv; EdgeHalf *e; const float *no; float co[3], d; + bool use_tri_fan; e = efirst; if (bv->edgecount == 2) { @@ -2278,9 +2385,9 @@ static void build_boundary_terminal_edge(BevelParams *bp, no = e->fprev ? e->fprev->no : (e->fnext ? e->fnext->no : NULL); offset_in_plane(e, no, true, co); if (construct) { - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = v->elast = v->ebev = e; - e->leftv = v; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = bndv->elast = bndv->ebev = e; + e->leftv = bndv; } else { adjust_bound_vert(e->leftv, co); @@ -2288,9 +2395,9 @@ static void build_boundary_terminal_edge(BevelParams *bp, no = e->fnext ? e->fnext->no : (e->fprev ? e->fprev->no : NULL); offset_in_plane(e, no, false, co); if (construct) { - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = v->elast = e; - e->rightv = v; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = bndv->elast = e; + e->rightv = bndv; } else { adjust_bound_vert(e->rightv, co); @@ -2298,11 +2405,9 @@ static void build_boundary_terminal_edge(BevelParams *bp, /* make artificial extra point along unbeveled edge, and form triangle */ slide_dist(e->next, bv->v, e->offset_l, co); if (construct) { - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = v->elast = e->next; - e->next->leftv = e->next->rightv = v; - /* could use M_POLY too, but tri-fan looks nicer)*/ - vm->mesh_kind = M_TRI_FAN; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = bndv->elast = e->next; + e->next->leftv = e->next->rightv = bndv; set_bound_vert_seams(bv, bp->mark_seam, bp->mark_sharp); } else { @@ -2316,11 +2421,11 @@ static void build_boundary_terminal_edge(BevelParams *bp, /* TODO: should do something else if angle between e and e->prev > 180 */ offset_meet(e->prev, e, bv->v, e->fprev, false, co); if (construct) { - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = e->prev; - v->elast = v->ebev = e; - e->leftv = v; - e->prev->leftv = e->prev->rightv = v; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = e->prev; + bndv->elast = bndv->ebev = e; + e->leftv = bndv; + e->prev->leftv = e->prev->rightv = bndv; } else { adjust_bound_vert(e->leftv, co); @@ -2328,23 +2433,26 @@ static void build_boundary_terminal_edge(BevelParams *bp, e = e->next; offset_meet(e->prev, e, bv->v, e->fprev, false, co); if (construct) { - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = e->prev; - v->elast = e; - e->leftv = e->rightv = v; - e->prev->rightv = v; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = e->prev; + bndv->elast = e; + e->leftv = e->rightv = bndv; + e->prev->rightv = bndv; } else { adjust_bound_vert(e->leftv, co); } /* For the edges not adjacent to the beveled edge, slide the bevel amount along. */ d = efirst->offset_l_spec; + if (bp->use_custom_profile || bp->profile < 0.25f) { + d *= sqrtf(2.0f); /* Need to go further along the edge to make room for full profile area. */ + } for (e = e->next; e->next != efirst; e = e->next) { slide_dist(e, bv->v, d, co); if (construct) { - v = add_new_bound_vert(mem_arena, vm, co); - v->efirst = v->elast = e; - e->leftv = e->rightv = v; + bndv = add_new_bound_vert(mem_arena, vm, co); + bndv->efirst = bndv->elast = e; + e->leftv = e->rightv = bndv; } else { adjust_bound_vert(e->leftv, co); @@ -2354,11 +2462,12 @@ static void build_boundary_terminal_edge(BevelParams *bp, calculate_vm_profiles(bp, bv, vm); if (bv->edgecount >= 3) { - /* special case: snap profile to plane of adjacent two edges */ - v = vm->boundstart; - BLI_assert(v->ebev != NULL); - move_profile_plane(v, bv->v); - calculate_profile(bp, v); + /* Special case: snap profile to plane of adjacent two edges. */ + bndv = vm->boundstart; + BLI_assert(bndv->ebev != NULL); + move_profile_plane(bndv, bv->v); + /* This step happens before the profile orientation pass so don't reverse the profile. */ + calculate_profile(bp, bndv, false, false); } if (construct) { @@ -2368,12 +2477,34 @@ static void build_boundary_terminal_edge(BevelParams *bp, vm->mesh_kind = M_NONE; } else if (vm->count == 3) { - vm->mesh_kind = M_TRI_FAN; + use_tri_fan = true; + if (bp->use_custom_profile) { + /* Use M_POLY if the extra point is planar with the profile to prevent overhanging edges */ + bndv = efirst->leftv; + float profile_plane[4]; + plane_from_point_normal_v3(profile_plane, bndv->profile.plane_co, bndv->profile.plane_no); + bndv = efirst->rightv->next; /* The added boundvert placed along the non-adjacent edge */ + if (dist_squared_to_plane_v3(bndv->nv.co, profile_plane) < BEVEL_EPSILON_BIG) { + use_tri_fan = false; + } + } + vm->mesh_kind = (use_tri_fan) ? M_TRI_FAN : M_POLY; } else { vm->mesh_kind = M_POLY; } } +#ifdef DEBUG_CUSTOM_PROFILE_WELD + if (bp->seg > 1) { + printf("Terminal Edge Profile Coordinates:\n"); + for (int k = 0; k < bp->seg; k++) { + printf("%0.4f, %0.4f, %0.4f\n", + (double)vm->boundstart->profile.prof_co[3 * k], + (double)vm->boundstart->profile.prof_co[3 * k + 1], + (double)vm->boundstart->profile.prof_co[3 * k + 2]); + } + } +#endif } /* Helper for build_boundary to handle special miters */ @@ -2399,7 +2530,7 @@ static void adjust_miter_coords(BevelParams *bp, BevVert *bv, EdgeHalf *emiter) v3next = v3->next; copy_v3_v3(co2, v1->nv.co); if (v1->is_arc_start) { - copy_v3_v3(v1->profile.midco, co2); + copy_v3_v3(v1->profile.middle, co2); } /* co1 is intersection of line through co2 in dir of emiter->e @@ -2477,7 +2608,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) BoundVert *v, *v1, *v2, *v3; VMesh *vm; float co[3], r; - int nip, nnip, miter_outer, miter_inner; + int in_plane, not_in_plane, miter_outer, miter_inner; int ang_kind; /* Current bevel does nothing if only one edge into a vertex */ @@ -2497,7 +2628,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) BLI_assert(e->is_bev); if (bv->selcount == 1) { - /* special case: only one beveled edge in */ + /* Special case: only one beveled edge in */ build_boundary_terminal_edge(bp, bv, efirst, construct); return; } @@ -2509,7 +2640,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) /* keep track of the first beveled edge of an outside miter (there can be at most 1 per bv */ emiter = NULL; - /* Here: there is more than one beveled edge. + /* There is more than one beveled edge. * We make BoundVerts to connect the sides of the beveled edges. * Non-beveled edges in between will just join to the appropriate juncture point. */ e = efirst; @@ -2519,26 +2650,26 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) /* Make the BoundVert for the right side of e; other side will be made * when the beveled edge to the left of e is handled. * Analyze edges until next beveled edge. - * They are either "in plane" (preceding and subsequent faces are coplanar) - * or not. The "non-in-plane" edges effect silhouette and we prefer to slide - * along one of those if possible. */ - nip = nnip = 0; /* counts of in-plane / not-in-plane */ - enip = eip = NULL; /* representatives of each */ + * They are either "in plane" (preceding and subsequent faces are coplanar) or not. + * The "non-in-plane" edges affect the silhouette and we prefer to slide along one of those if + * possible. */ + in_plane = not_in_plane = 0; /* Counts of in-plane / not-in-plane */ + enip = eip = NULL; /* representatives of each */ for (e2 = e->next; !e2->is_bev; e2 = e2->next) { if (eh_on_plane(e2)) { - nip++; + in_plane++; eip = e2; } else { - nnip++; + not_in_plane++; enip = e2; } } - if (nip == 0 && nnip == 0) { + if (in_plane == 0 && not_in_plane == 0) { offset_meet(e, e2, bv->v, e->fnext, false, co); } - else if (nnip > 0) { - if (bp->loop_slide && nnip == 1 && good_offset_on_edge_between(e, e2, enip, bv->v)) { + else if (not_in_plane > 0) { + if (bp->loop_slide && not_in_plane == 1 && good_offset_on_edge_between(e, e2, enip, bv->v)) { if (offset_on_edge_between(e, e2, enip, bv->v, co, &r)) { eon = enip; } @@ -2548,8 +2679,8 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) } } else { - /* nip > 0 and nnip == 0 */ - if (bp->loop_slide && nip == 1 && good_offset_on_edge_between(e, e2, eip, bv->v)) { + /* n_in_plane > 0 and n_not_in_plane == 0 */ + if (bp->loop_slide && in_plane == 1 && good_offset_on_edge_between(e, e2, eip, bv->v)) { if (offset_on_edge_between(e, e2, eip, bv->v, co, &r)) { eon = eip; } @@ -2579,16 +2710,15 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) * There can only be one outer reflex angle, so only one outer miter, * and emiter will be set to the first edge of such an edge. * A miter kind of BEVEL_MITER_SHARP means no special miter */ - - if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == 1) || - (miter_inner != BEVEL_MITER_SHARP && ang_kind == -1)) { - if (ang_kind == 1) { + if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == ANGLE_LARGER) || + (miter_inner != BEVEL_MITER_SHARP && ang_kind == ANGLE_SMALLER)) { + if (ang_kind == ANGLE_LARGER) { emiter = e; } /* make one or two more boundverts; for now all will have same co */ v1 = v; v1->ebev = NULL; - if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) { + if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) { v2 = add_new_bound_vert(mem_arena, vm, co); } else { @@ -2600,7 +2730,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) v3->elast = e2; v3->eon = NULL; e2->leftv = v3; - if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) { + if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) { v1->is_patch_start = true; v2->eon = v1->eon; v2->sinratio = v1->sinratio; @@ -2622,17 +2752,16 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) } else { v1->is_arc_start = true; - copy_v3_v3(v1->profile.midco, co); + copy_v3_v3(v1->profile.middle, co); if (e->next == e2) { v1->elast = v1->efirst; } else { - int between = nip + nnip; + int between = in_plane + not_in_plane; int bet2 = between / 2; bool betodd = (between % 2) == 1; int i = 0; - /* Put first half of in-between edges at index 0, - * second half at index bp->seg. + /* Put first half of in-between edges at index 0, second half at index bp->seg. * If between is odd, put middle one at midindex */ for (e3 = e->next; e3 != e2; e3 = e3->next) { v1->elast = e3; @@ -2651,15 +2780,15 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) } } } - else { + else { /* construct == false */ ang_kind = edges_angle_kind(e, e2, bv->v); - if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == 1) || - (miter_inner != BEVEL_MITER_SHARP && ang_kind == -1)) { - if (ang_kind == 1) { + if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == ANGLE_LARGER) || + (miter_inner != BEVEL_MITER_SHARP && ang_kind == ANGLE_SMALLER)) { + if (ang_kind == ANGLE_LARGER) { emiter = e; } v1 = e->rightv; - if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) { + if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) { v2 = v1->next; v3 = v2->next; } @@ -2699,7 +2828,14 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct) vm->mesh_kind = M_POLY; } else { - vm->mesh_kind = M_ADJ; + switch (bp->vmesh_method) { + case BEVEL_VMESH_ADJ: + vm->mesh_kind = M_ADJ; + break; + case BEVEL_VMESH_CUTOFF: + vm->mesh_kind = M_CUTOFF; + break; + } } } } @@ -2885,6 +3021,194 @@ static bool adjust_the_cycle_or_chain_fast(BoundVert *vstart, int np, bool iscyc } #endif +/** Helper function to return the next Beveled EdgeHalf along a path. + * \param toward_bv Whether the direction to travel points toward or away from the BevVert + * connected to the current EdgeHalf + * \param r_bv The BevVert conencted to the EdgeHalf-- updated if we're travelling to the other + * EdgeHalf of an original edge + * \note This only returns the most parallel edge if it's the most parallel by + * at least 10 degrees. This is a somewhat arbitrary choice, but it makes sure that consistent + * orientation paths only continue in obvious ways. */ +static EdgeHalf *next_edgehalf_bev(BevelParams *bp, + EdgeHalf *start_edge, + bool toward_bv, + BevVert **r_bv) +{ + EdgeHalf *new_edge; + EdgeHalf *next_edge = NULL; + float dir_start_edge[3], dir_new_edge[3]; + float second_best_dot = 0.0f, best_dot = 0.0f; + float new_dot; + + /* Case 1: The next EdgeHalf is across a BevVert from the current EdgeHalf. */ + if (toward_bv) { + /* Skip all the logic if there's only one beveled edge at the vertex, we're at an end. */ + if ((*r_bv)->selcount == 1) { + return NULL; /* No other edges to go to. */ + } + + /* The case with only one other edge connected to the vertex is special too. */ + if ((*r_bv)->selcount == 2) { + /* Just find the next beveled edge, that's the only other option. */ + new_edge = start_edge; + do { + new_edge = new_edge->next; + } while (!new_edge->is_bev); + + return new_edge; + } + + /* Find the direction vector of the current edge (pointing INTO the BevVert). + * v1 and v2 don't necessarily have an order, so we need to check which is closer to bv. */ + if (start_edge->e->v1 == (*r_bv)->v) { + sub_v3_v3v3(dir_start_edge, start_edge->e->v1->co, start_edge->e->v2->co); + } + else { + sub_v3_v3v3(dir_start_edge, start_edge->e->v2->co, start_edge->e->v1->co); + } + normalize_v3(dir_start_edge); + + /* Find the beveled edge coming out of the BevVert that's most parallel to the current edge. */ + new_edge = start_edge->next; + while (new_edge != start_edge) { + if (!new_edge->is_bev) { + new_edge = new_edge->next; + continue; + } + /* Find direction vector of the possible next edge (pointing OUT of the BevVert). */ + if (new_edge->e->v2 == (*r_bv)->v) { + sub_v3_v3v3(dir_new_edge, new_edge->e->v1->co, new_edge->e->v2->co); + } + else { + sub_v3_v3v3(dir_new_edge, new_edge->e->v2->co, new_edge->e->v1->co); + } + normalize_v3(dir_new_edge); + + /* Use this edge if it is the most parallel to the orignial so far */ + new_dot = dot_v3v3(dir_new_edge, dir_start_edge); + if (new_dot > best_dot) { + second_best_dot = best_dot; /* For remembering if the choice was too close. */ + best_dot = new_dot; + next_edge = new_edge; + } + else if (new_dot > second_best_dot) { + second_best_dot = new_dot; + } + + new_edge = new_edge->next; + } + + /* Only return a new Edge if one was found and if the choice of next edge was not too close. */ + if ((next_edge != NULL) && compare_ff(best_dot, second_best_dot, BEVEL_SMALL_ANG_DOT)) { + return NULL; + } + else { + return next_edge; + } + } + else { + /* Case 2: The next EdgeHalf is the other side of the BMEdge. + * It's part of the same BMEdge, so we know the other EdgeHalf is also beveled. */ + next_edge = find_other_end_edge_half(bp, start_edge, r_bv); + return next_edge; + } +} + +/** Starting along any beveled edge, travel along the chain / cycle of beveled edges including that + * edge, marking consistent profile orientations along the way. Orientations are marked by setting + * whether the BoundVert that contains each profile's information is the side of the profile's + * start or not. */ +static void regularize_profile_orientation(BevelParams *bp, BMEdge *bme) +{ + BevVert *start_bv; + BevVert *bv; + EdgeHalf *start_edgehalf, *edgehalf; + bool toward_bv; + + start_bv = find_bevvert(bp, bme->v1); + start_edgehalf = find_edge_half(start_bv, bme); + if (!start_edgehalf->is_bev || start_edgehalf->visited_rpo) { + return; + } + + /* Pick a BoundVert on one side of the profile to use for the start of the profile. */ + start_edgehalf->leftv->is_profile_start = false; + start_edgehalf->visited_rpo = true; + + /* First loop starts in the away from BevVert direction and the second starts toward it. */ + for (int i = 0; i < 2; i++) { + edgehalf = start_edgehalf; + bv = start_bv; + toward_bv = (i == 0); + edgehalf = next_edgehalf_bev(bp, edgehalf, toward_bv, &bv); + + /* Keep traveling until there is no unvisited beveled edgehalf to visit next. */ + while (edgehalf && !edgehalf->visited_rpo) { + /* Mark the correct BoundVert as the start of the newly visited profile. + * The direction relative to the BevVert switches every step, so also switch + * the orientation every step. */ + if (i == 0) { + edgehalf->leftv->is_profile_start = toward_bv; + } + else { + /* The opposite side as the first direction because we're moving the other way. */ + edgehalf->leftv->is_profile_start = !toward_bv; + } + + /* The next jump will in the opposite direction relative to the BevVert. */ + toward_bv = !toward_bv; + + edgehalf->visited_rpo = true; + edgehalf = next_edgehalf_bev(bp, edgehalf, toward_bv, &bv); + } + } +} + +#ifdef DEBUG_PROFILE_ORIENTATION_DRAW +/** Draws markers on beveled edges showing the side that the profile starts on. A sphere shows + * the start side of the profile where it starts, and the lines connected to the sphere show which + * edge the orientation corresponds to. + * \note Only drawn while bevel is calculating, the debug geometry is not persistent. */ +static void debug_draw_profile_orientation(BevelParams *bp, BMesh *bm) +{ + BMIter iter; + BMEdge *bmedge; + float middle[3]; + + BM_ITER_MESH (bmedge, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(bmedge, BM_ELEM_TAG)) { + mid_v3_v3v3(middle, bmedge->v1->co, bmedge->v2->co); + + /* Draw the orientation for the first side of the edge. */ + EdgeHalf *edge_half = find_edge_half(find_bevvert(bp, bmedge->v1), bmedge); + if (edge_half->leftv->is_profile_start) { /* The left boundvert defines the profiles. */ + DRW_debug_sphere(edge_half->leftv->nv.co, 0.04f, debug_color_red); + DRW_debug_line_v3v3(middle, edge_half->leftv->nv.co, debug_color_red); + DRW_debug_line_v3v3(bmedge->v1->co, edge_half->leftv->nv.co, debug_color_red); + } + else { + DRW_debug_sphere(edge_half->rightv->nv.co, 0.04f, debug_color_red); + DRW_debug_line_v3v3(middle, edge_half->rightv->nv.co, debug_color_red); + DRW_debug_line_v3v3(bmedge->v1->co, edge_half->rightv->nv.co, debug_color_red); + } + + /* Draw the orientation for the second side of the edge. */ + edge_half = find_edge_half(find_bevvert(bp, bmedge->v2), bmedge); + if (edge_half->leftv->is_profile_start) { + DRW_debug_sphere(edge_half->leftv->nv.co, 0.05f, debug_color_blue); + DRW_debug_line_v3v3(middle, edge_half->leftv->nv.co, debug_color_blue); + DRW_debug_line_v3v3(bmedge->v2->co, edge_half->leftv->nv.co, debug_color_blue); + } + else { + DRW_debug_sphere(edge_half->rightv->nv.co, 0.05f, debug_color_blue); + DRW_debug_line_v3v3(middle, edge_half->rightv->nv.co, debug_color_blue); + DRW_debug_line_v3v3(bmedge->v2->co, edge_half->rightv->nv.co, debug_color_blue); + } + } + } +} +#endif + /* Adjust the offsets for a single cycle or chain. * For chains and some cycles, a fast solution exists. * Otherwise, we set up and solve a linear least squares problem @@ -3218,8 +3542,8 @@ static VMesh *new_adj_vmesh(MemArena *mem_arena, int count, int seg, BoundVert * vm->count = count; vm->seg = seg; vm->boundstart = bounds; - vm->mesh = (NewVert *)BLI_memarena_alloc(mem_arena, - count * (1 + seg / 2) * (1 + seg) * sizeof(NewVert)); + vm->mesh = (NewVert *)BLI_memarena_alloc( + mem_arena, (size_t)(count * (1 + seg / 2) * (1 + seg)) * sizeof(NewVert)); vm->mesh_kind = M_ADJ; return vm; } @@ -3261,10 +3585,10 @@ static NewVert *mesh_vert_canon(VMesh *vm, int i, int j, int k) static bool is_canon(VMesh *vm, int i, int j, int k) { int ns2 = vm->seg / 2; - if (vm->seg % 2 == 1) { + if (vm->seg % 2 == 1) { /* odd */ return (j <= ns2 && k <= ns2); } - else { + else { /* even */ return ((j < ns2 && k <= ns2) || (j == ns2 && k == ns2 && i == 0)); } } @@ -3296,6 +3620,9 @@ static void vmesh_copy_equiv_verts(VMesh *vm) /* Calculate and return in r_cent the centroid of the center poly */ static void vmesh_center(VMesh *vm, float r_cent[3]) { +#ifdef DEBUG_CUSTOM_PROFILE_ADJ + printf("VMESH CENTER\n"); +#endif int n, ns2, i; n = vm->count; @@ -3432,182 +3759,193 @@ static int interp_range(const float *frac, int n, const float f, float *r_rest) } /* Interpolate given vmesh to make one with target nseg border vertices on the profiles */ -static VMesh *interp_vmesh(BevelParams *bp, VMesh *vm0, int nseg) +/* HANS-TODO: This puts the center mesh vert at a slightly off location sometimes, which seems to + * be associated with the rest of that ring being shifted or connected slightly incorrectly to its + * neighbors */ +static VMesh *interp_vmesh(BevelParams *bp, VMesh *vm_in, int nseg) { - int n, ns0, nseg2, odd, i, j, k, j0, k0, k0prev, j0inc, k0inc; + int n_bndv, ns_in, nseg2, odd, i, j, k, j_in, k_in, k_in_prev, j0inc, k0inc; float *prev_frac, *frac, *new_frac, *prev_new_frac; - float f, restj, restk, restkprev; + float fraction, restj, restk, restkprev; float quad[4][3], co[3], center[3]; - VMesh *vm1; + VMesh *vm_out; BoundVert *bndv; - n = vm0->count; - ns0 = vm0->seg; + n_bndv = vm_in->count; + ns_in = vm_in->seg; nseg2 = nseg / 2; odd = nseg % 2; - vm1 = new_adj_vmesh(bp->mem_arena, n, nseg, vm0->boundstart); + vm_out = new_adj_vmesh(bp->mem_arena, n_bndv, nseg, vm_in->boundstart); - prev_frac = BLI_array_alloca(prev_frac, (ns0 + 1)); - frac = BLI_array_alloca(frac, (ns0 + 1)); + prev_frac = BLI_array_alloca(prev_frac, (ns_in + 1)); + frac = BLI_array_alloca(frac, (ns_in + 1)); new_frac = BLI_array_alloca(new_frac, (nseg + 1)); prev_new_frac = BLI_array_alloca(prev_new_frac, (nseg + 1)); - fill_vmesh_fracs(vm0, prev_frac, n - 1); - bndv = vm0->boundstart; + fill_vmesh_fracs(vm_in, prev_frac, n_bndv - 1); + bndv = vm_in->boundstart; fill_profile_fracs(bp, bndv->prev, prev_new_frac, nseg); - for (i = 0; i < n; i++) { - fill_vmesh_fracs(vm0, frac, i); + for (i = 0; i < n_bndv; i++) { + fill_vmesh_fracs(vm_in, frac, i); fill_profile_fracs(bp, bndv, new_frac, nseg); for (j = 0; j <= nseg2 - 1 + odd; j++) { for (k = 0; k <= nseg2; k++) { - f = new_frac[k]; - k0 = interp_range(frac, ns0, f, &restk); - f = prev_new_frac[nseg - j]; - k0prev = interp_range(prev_frac, ns0, f, &restkprev); - j0 = ns0 - k0prev; + /* Finding the locations where "fraction" fits into previous and current "frac" */ + fraction = new_frac[k]; + k_in = interp_range(frac, ns_in, fraction, &restk); + fraction = prev_new_frac[nseg - j]; + k_in_prev = interp_range(prev_frac, ns_in, fraction, &restkprev); + j_in = ns_in - k_in_prev; restj = -restkprev; if (restj > -BEVEL_EPSILON) { restj = 0.0f; } else { - j0 = j0 - 1; + j_in = j_in - 1; restj = 1.0f + restj; } /* Use bilinear interpolation within the source quad; could be smarter here */ if (restj < BEVEL_EPSILON && restk < BEVEL_EPSILON) { - copy_v3_v3(co, mesh_vert_canon(vm0, i, j0, k0)->co); + copy_v3_v3(co, mesh_vert_canon(vm_in, i, j_in, k_in)->co); } else { - j0inc = (restj < BEVEL_EPSILON || j0 == ns0) ? 0 : 1; - k0inc = (restk < BEVEL_EPSILON || k0 == ns0) ? 0 : 1; - copy_v3_v3(quad[0], mesh_vert_canon(vm0, i, j0, k0)->co); - copy_v3_v3(quad[1], mesh_vert_canon(vm0, i, j0, k0 + k0inc)->co); - copy_v3_v3(quad[2], mesh_vert_canon(vm0, i, j0 + j0inc, k0 + k0inc)->co); - copy_v3_v3(quad[3], mesh_vert_canon(vm0, i, j0 + j0inc, k0)->co); + j0inc = (restj < BEVEL_EPSILON || j_in == ns_in) ? 0 : 1; + k0inc = (restk < BEVEL_EPSILON || k_in == ns_in) ? 0 : 1; + copy_v3_v3(quad[0], mesh_vert_canon(vm_in, i, j_in, k_in)->co); + copy_v3_v3(quad[1], mesh_vert_canon(vm_in, i, j_in, k_in + k0inc)->co); + copy_v3_v3(quad[2], mesh_vert_canon(vm_in, i, j_in + j0inc, k_in + k0inc)->co); + copy_v3_v3(quad[3], mesh_vert_canon(vm_in, i, j_in + j0inc, k_in)->co); interp_bilinear_quad_v3(quad, restk, restj, co); } - copy_v3_v3(mesh_vert(vm1, i, j, k)->co, co); + copy_v3_v3(mesh_vert(vm_out, i, j, k)->co, co); } } bndv = bndv->next; - memcpy(prev_frac, frac, (ns0 + 1) * sizeof(float)); - memcpy(prev_new_frac, new_frac, (nseg + 1) * sizeof(float)); + memcpy(prev_frac, frac, (size_t)(ns_in + 1) * sizeof(float)); + memcpy(prev_new_frac, new_frac, (size_t)(nseg + 1) * sizeof(float)); } if (!odd) { - vmesh_center(vm0, center); - copy_v3_v3(mesh_vert(vm1, 0, nseg2, nseg2)->co, center); + vmesh_center(vm_in, center); + copy_v3_v3(mesh_vert(vm_out, 0, nseg2, nseg2)->co, center); } - vmesh_copy_equiv_verts(vm1); - return vm1; + vmesh_copy_equiv_verts(vm_out); + return vm_out; } /* Do one step of cubic subdivision (Catmull-Clark), with special rules at boundaries. * For now, this is written assuming vm0->nseg is even and > 0. - * We are allowed to modify vm0, as it will not be used after this call. + * We are allowed to modify vm_in, as it will not be used after this call. * See Levin 1999 paper: "Filling an N-sided hole using combined subdivision schemes". */ -static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm0) +static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm_in) { - int n, ns0, ns20, ns1; + int n_boundary, ns_in, ns_in2, ns_out; int i, j, k, inext; float co[3], co1[3], co2[3], acc[3]; float beta, gamma; - VMesh *vm1; + VMesh *vm_out; BoundVert *bndv; - n = vm0->count; - ns0 = vm0->seg; - ns20 = ns0 / 2; - BLI_assert(ns0 % 2 == 0); - ns1 = 2 * ns0; - vm1 = new_adj_vmesh(bp->mem_arena, n, ns1, vm0->boundstart); + n_boundary = vm_in->count; + ns_in = vm_in->seg; + ns_in2 = ns_in / 2; + BLI_assert(ns_in % 2 == 0); + ns_out = 2 * ns_in; + vm_out = new_adj_vmesh(bp->mem_arena, n_boundary, ns_out, vm_in->boundstart); - /* First we adjust the boundary vertices of the input mesh, storing in output mesh */ - for (i = 0; i < n; i++) { - copy_v3_v3(mesh_vert(vm1, i, 0, 0)->co, mesh_vert(vm0, i, 0, 0)->co); - for (k = 1; k < ns0; k++) { - /* smooth boundary rule */ - copy_v3_v3(co, mesh_vert(vm0, i, 0, k)->co); - copy_v3_v3(co1, mesh_vert(vm0, i, 0, k - 1)->co); - copy_v3_v3(co2, mesh_vert(vm0, i, 0, k + 1)->co); + /* First we adjust the boundary vertices of the input mesh, storing in output mesh. */ + for (i = 0; i < n_boundary; i++) { + copy_v3_v3(mesh_vert(vm_out, i, 0, 0)->co, mesh_vert(vm_in, i, 0, 0)->co); + for (k = 1; k < ns_in; k++) { + copy_v3_v3(co, mesh_vert(vm_in, i, 0, k)->co); - add_v3_v3v3(acc, co1, co2); - madd_v3_v3fl(acc, co, -2.0f); - madd_v3_v3fl(co, acc, -1.0f / 6.0f); + /* Smooth boundary rule. Custom profiles shouldn't be smoothed. */ + if (!bp->use_custom_profile) { + copy_v3_v3(co1, mesh_vert(vm_in, i, 0, k - 1)->co); + copy_v3_v3(co2, mesh_vert(vm_in, i, 0, k + 1)->co); - copy_v3_v3(mesh_vert_canon(vm1, i, 0, 2 * k)->co, co); + add_v3_v3v3(acc, co1, co2); + madd_v3_v3fl(acc, co, -2.0f); + madd_v3_v3fl(co, acc, -1.0f / 6.0f); + } + + copy_v3_v3(mesh_vert_canon(vm_out, i, 0, 2 * k)->co, co); } } - /* now do odd ones in output mesh, based on even ones */ - bndv = vm1->boundstart; - for (i = 0; i < n; i++) { - for (k = 1; k < ns1; k += 2) { - get_profile_point(bp, &bndv->profile, k, ns1, co); - copy_v3_v3(co1, mesh_vert_canon(vm1, i, 0, k - 1)->co); - copy_v3_v3(co2, mesh_vert_canon(vm1, i, 0, k + 1)->co); + /* Now adjust odd boundary vertices in output mesh, based on even ones. */ + bndv = vm_out->boundstart; + for (i = 0; i < n_boundary; i++) { + for (k = 1; k < ns_out; k += 2) { + get_profile_point(bp, &bndv->profile, k, ns_out, co); - add_v3_v3v3(acc, co1, co2); - madd_v3_v3fl(acc, co, -2.0f); - madd_v3_v3fl(co, acc, -1.0f / 6.0f); + /* Smooth if using a non-custom profile. */ + if (!bp->use_custom_profile) { + copy_v3_v3(co1, mesh_vert_canon(vm_out, i, 0, k - 1)->co); + copy_v3_v3(co2, mesh_vert_canon(vm_out, i, 0, k + 1)->co); - copy_v3_v3(mesh_vert_canon(vm1, i, 0, k)->co, co); + add_v3_v3v3(acc, co1, co2); + madd_v3_v3fl(acc, co, -2.0f); + madd_v3_v3fl(co, acc, -1.0f / 6.0f); + } + + copy_v3_v3(mesh_vert_canon(vm_out, i, 0, k)->co, co); } bndv = bndv->next; } - vmesh_copy_equiv_verts(vm1); + vmesh_copy_equiv_verts(vm_out); - /* Copy adjusted verts back into vm0 */ - for (i = 0; i < n; i++) { - for (k = 0; k < ns0; k++) { - copy_v3_v3(mesh_vert(vm0, i, 0, k)->co, mesh_vert(vm1, i, 0, 2 * k)->co); + /* Copy adjusted verts back into vm_in. */ + for (i = 0; i < n_boundary; i++) { + for (k = 0; k < ns_in; k++) { + copy_v3_v3(mesh_vert(vm_in, i, 0, k)->co, mesh_vert(vm_out, i, 0, 2 * k)->co); } } - vmesh_copy_equiv_verts(vm0); + vmesh_copy_equiv_verts(vm_in); /* Now we do the internal vertices, using standard Catmull-Clark * and assuming all boundary vertices have valence 4 */ /* The new face vertices */ - for (i = 0; i < n; i++) { - for (j = 0; j < ns20; j++) { - for (k = 0; k < ns20; k++) { + for (i = 0; i < n_boundary; i++) { + for (j = 0; j < ns_in2; j++) { + for (k = 0; k < ns_in2; k++) { /* face up and right from (j, k) */ avg4(co, - mesh_vert(vm0, i, j, k), - mesh_vert(vm0, i, j, k + 1), - mesh_vert(vm0, i, j + 1, k), - mesh_vert(vm0, i, j + 1, k + 1)); - copy_v3_v3(mesh_vert(vm1, i, 2 * j + 1, 2 * k + 1)->co, co); + mesh_vert(vm_in, i, j, k), + mesh_vert(vm_in, i, j, k + 1), + mesh_vert(vm_in, i, j + 1, k), + mesh_vert(vm_in, i, j + 1, k + 1)); + copy_v3_v3(mesh_vert(vm_out, i, 2 * j + 1, 2 * k + 1)->co, co); } } } - /* The new vertical edge vertices */ - for (i = 0; i < n; i++) { - for (j = 0; j < ns20; j++) { - for (k = 1; k <= ns20; k++) { + /* The new vertical edge vertices */ + for (i = 0; i < n_boundary; i++) { + for (j = 0; j < ns_in2; j++) { + for (k = 1; k <= ns_in2; k++) { /* vertical edge between (j, k) and (j+1, k) */ avg4(co, - mesh_vert(vm0, i, j, k), - mesh_vert(vm0, i, j + 1, k), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k - 1), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1)); - copy_v3_v3(mesh_vert(vm1, i, 2 * j + 1, 2 * k)->co, co); + mesh_vert(vm_in, i, j, k), + mesh_vert(vm_in, i, j + 1, k), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k - 1), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1)); + copy_v3_v3(mesh_vert(vm_out, i, 2 * j + 1, 2 * k)->co, co); } } } - /* The new horizontal edge vertices */ - for (i = 0; i < n; i++) { - for (j = 1; j < ns20; j++) { - for (k = 0; k < ns20; k++) { + /* The new horizontal edge vertices */ + for (i = 0; i < n_boundary; i++) { + for (j = 1; j < ns_in2; j++) { + for (k = 0; k < ns_in2; k++) { /* horizontal edge between (j, k) and (j, k+1) */ avg4(co, - mesh_vert(vm0, i, j, k), - mesh_vert(vm0, i, j, k + 1), - mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k + 1), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1)); - copy_v3_v3(mesh_vert(vm1, i, 2 * j, 2 * k + 1)->co, co); + mesh_vert(vm_in, i, j, k), + mesh_vert(vm_in, i, j, k + 1), + mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k + 1), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1)); + copy_v3_v3(mesh_vert(vm_out, i, 2 * j, 2 * k + 1)->co, co); } } } @@ -3615,66 +3953,66 @@ static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm0) /* The new vertices, not on border */ gamma = 0.25f; beta = -gamma; - for (i = 0; i < n; i++) { - for (j = 1; j < ns20; j++) { - for (k = 1; k <= ns20; k++) { + for (i = 0; i < n_boundary; i++) { + for (j = 1; j < ns_in2; j++) { + for (k = 1; k <= ns_in2; k++) { /* co1 = centroid of adjacent new edge verts */ avg4(co1, - mesh_vert_canon(vm1, i, 2 * j, 2 * k - 1), - mesh_vert_canon(vm1, i, 2 * j, 2 * k + 1), - mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k)); + mesh_vert_canon(vm_out, i, 2 * j, 2 * k - 1), + mesh_vert_canon(vm_out, i, 2 * j, 2 * k + 1), + mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k)); /* co2 = centroid of adjacent new face verts */ avg4(co2, - mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k - 1), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k - 1), - mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k + 1), - mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1)); + mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k - 1), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k - 1), + mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k + 1), + mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1)); /* combine with original vert with alpha, beta, gamma factors */ copy_v3_v3(co, co1); /* alpha = 1.0 */ madd_v3_v3fl(co, co2, beta); - madd_v3_v3fl(co, mesh_vert(vm0, i, j, k)->co, gamma); - copy_v3_v3(mesh_vert(vm1, i, 2 * j, 2 * k)->co, co); + madd_v3_v3fl(co, mesh_vert(vm_in, i, j, k)->co, gamma); + copy_v3_v3(mesh_vert(vm_out, i, 2 * j, 2 * k)->co, co); } } } - vmesh_copy_equiv_verts(vm1); + vmesh_copy_equiv_verts(vm_out); /* The center vertex is special */ - gamma = sabin_gamma(n); + gamma = sabin_gamma(n_boundary); beta = -gamma; /* accumulate edge verts in co1, face verts in co2 */ zero_v3(co1); zero_v3(co2); - for (i = 0; i < n; i++) { - add_v3_v3(co1, mesh_vert(vm1, i, ns0, ns0 - 1)->co); - add_v3_v3(co2, mesh_vert(vm1, i, ns0 - 1, ns0 - 1)->co); - add_v3_v3(co2, mesh_vert(vm1, i, ns0 - 1, ns0 + 1)->co); + for (i = 0; i < n_boundary; i++) { + add_v3_v3(co1, mesh_vert(vm_out, i, ns_in, ns_in - 1)->co); + add_v3_v3(co2, mesh_vert(vm_out, i, ns_in - 1, ns_in - 1)->co); + add_v3_v3(co2, mesh_vert(vm_out, i, ns_in - 1, ns_in + 1)->co); } copy_v3_v3(co, co1); - mul_v3_fl(co, 1.0f / (float)n); - madd_v3_v3fl(co, co2, beta / (2.0f * (float)n)); - madd_v3_v3fl(co, mesh_vert(vm0, 0, ns20, ns20)->co, gamma); - for (i = 0; i < n; i++) { - copy_v3_v3(mesh_vert(vm1, i, ns0, ns0)->co, co); + mul_v3_fl(co, 1.0f / (float)n_boundary); + madd_v3_v3fl(co, co2, beta / (2.0f * (float)n_boundary)); + madd_v3_v3fl(co, mesh_vert(vm_in, 0, ns_in2, ns_in2)->co, gamma); + for (i = 0; i < n_boundary; i++) { + copy_v3_v3(mesh_vert(vm_out, i, ns_in, ns_in)->co, co); } - /* Final step: sample the boundary vertices at even parameter spacing */ - bndv = vm1->boundstart; - for (i = 0; i < n; i++) { - inext = (i + 1) % n; - for (k = 0; k <= ns1; k++) { - get_profile_point(bp, &bndv->profile, k, ns1, co); - copy_v3_v3(mesh_vert(vm1, i, 0, k)->co, co); - if (k >= ns0 && k < ns1) { - copy_v3_v3(mesh_vert(vm1, inext, ns1 - k, 0)->co, co); + /* Final step: Copy the profile vertices to the VMesh's boundary */ + bndv = vm_out->boundstart; + for (i = 0; i < n_boundary; i++) { + inext = (i + 1) % n_boundary; + for (k = 0; k <= ns_out; k++) { + get_profile_point(bp, &bndv->profile, k, ns_out, co); + copy_v3_v3(mesh_vert(vm_out, i, 0, k)->co, co); + if (k >= ns_in && k < ns_out) { + copy_v3_v3(mesh_vert(vm_out, inext, ns_out - k, 0)->co, co); } } bndv = bndv->next; } - return vm1; + return vm_out; } /* Special case for cube corner, when r is PRO_SQUARE_R, meaning straight sides */ @@ -3686,7 +4024,7 @@ static VMesh *make_cube_corner_square(MemArena *mem_arena, int nseg) ns2 = nseg / 2; vm = new_adj_vmesh(mem_arena, 3, nseg, NULL); - vm->count = 0; // reset, so following loop will end up with correct count + vm->count = 0; /* Reset, so the following loop will end up with correct count. */ for (i = 0; i < 3; i++) { zero_v3(co); co[i] = 1.0f; @@ -3765,16 +4103,18 @@ static VMesh *make_cube_corner_adj_vmesh(BevelParams *bp) int i, j, k, ns2; float co[3], coc[3]; - if (r == PRO_SQUARE_R) { - return make_cube_corner_square(mem_arena, nseg); - } - else if (r == PRO_SQUARE_IN_R) { - return make_cube_corner_square_in(mem_arena, nseg); + if (!bp->use_custom_profile) { + if (r == PRO_SQUARE_R) { + return make_cube_corner_square(mem_arena, nseg); + } + else if (r == PRO_SQUARE_IN_R) { + return make_cube_corner_square_in(mem_arena, nseg); + } } - /* initial mesh has 3 sides, 2 segments */ + /* Initial mesh has 3 sides and 2 segments on each side */ vm0 = new_adj_vmesh(mem_arena, 3, 2, NULL); - vm0->count = 0; // reset, so following loop will end up with correct count + vm0->count = 0; /* Reset, so the following loop will end up with correct count. */ for (i = 0; i < 3; i++) { zero_v3(co); co[i] = 1.0f; @@ -3787,20 +4127,24 @@ static VMesh *make_cube_corner_adj_vmesh(BevelParams *bp) coc[(i + 1) % 3] = 1.0f; coc[(i + 2) % 3] = 0.0f; bndv->profile.super_r = r; - copy_v3_v3(bndv->profile.coa, bndv->nv.co); - copy_v3_v3(bndv->profile.cob, bndv->next->nv.co); - copy_v3_v3(bndv->profile.midco, coc); - copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->profile.coa); - copy_v3_v3(bndv->profile.plane_co, bndv->profile.coa); - cross_v3_v3v3(bndv->profile.plane_no, bndv->profile.coa, bndv->profile.cob); + copy_v3_v3(bndv->profile.start, bndv->nv.co); + copy_v3_v3(bndv->profile.end, bndv->next->nv.co); + copy_v3_v3(bndv->profile.middle, coc); + copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->profile.start); + copy_v3_v3(bndv->profile.plane_co, bndv->profile.start); + cross_v3_v3v3(bndv->profile.plane_no, bndv->profile.start, bndv->profile.end); copy_v3_v3(bndv->profile.proj_dir, bndv->profile.plane_no); - calculate_profile(bp, bndv); + /* No need to reverse the profile or use the miter profile spacing struct because this case + * isn't used with custom profiles. */ + calculate_profile(bp, bndv, false, false); + + /* Just building the boundaries here, so sample the profile halfway through */ get_profile_point(bp, &bndv->profile, 1, 2, mesh_vert(vm0, i, 0, 1)->co); bndv = bndv->next; } /* center vertex */ - copy_v3_fl(co, M_SQRT1_3); + copy_v3_fl(co, (float)M_SQRT1_3); if (nseg > 2) { if (r > 1.5f) { @@ -3843,7 +4187,8 @@ static int tri_corner_test(BevelParams *bp, BevVert *bv) int i; int in_plane_e = 0; - if (bp->vertex_only) { + /* The superellipse snapping of this case isn't helpful with custom profiles enabled. */ + if (bp->vertex_only || bp->use_custom_profile) { return -1; } if (bv->vmesh->count != 3) { @@ -3909,94 +4254,71 @@ static VMesh *tri_corner_adj_vmesh(BevelParams *bp, BevVert *bv) return vm; } +/* Makes the mesh that replaces the original vertex, bounded by the profiles on the sides */ static VMesh *adj_vmesh(BevelParams *bp, BevVert *bv) { - int n, ns, i; + int n_bndv, nseg, i; VMesh *vm0, *vm1; - float co[3], coa[3], cob[3], dir[3]; + float boundverts_center[3], original_vertex[3], negative_fullest[3], center_direction[3]; BoundVert *bndv; MemArena *mem_arena = bp->mem_arena; - float r, p, fullness; - /* best fullness for circles, segs = 2,4,6,8,10 */ -#define CIRCLE_FULLNESS_SEGS 11 - static const float circle_fullness[CIRCLE_FULLNESS_SEGS] = { - 0.0f, /* nsegs ==1 */ - 0.559f, /* 2 */ - 0.642f, /* 3 */ - 0.551f, /* 4 */ - 0.646f, /* 5 */ - 0.624f, /* 6 */ - 0.646f, /* 7 */ - 0.619f, /* 8 */ - 0.647f, /* 9 */ - 0.639f, /* 10 */ - 0.647f, /* 11 */ - }; + float fullness; - n = bv->vmesh->count; + n_bndv = bv->vmesh->count; /* Same bevel as that of 3 edges of vert in a cube */ - if (n == 3 && tri_corner_test(bp, bv) != -1 && bp->pro_super_r != PRO_SQUARE_IN_R) { + if (n_bndv == 3 && tri_corner_test(bp, bv) != -1 && bp->pro_super_r != PRO_SQUARE_IN_R) { return tri_corner_adj_vmesh(bp, bv); } - /* First construct an initial control mesh, with nseg==2 */ - ns = bv->vmesh->seg; - vm0 = new_adj_vmesh(mem_arena, n, 2, bv->vmesh->boundstart); + /* First construct an initial control mesh, with nseg == 2. */ + nseg = bv->vmesh->seg; + vm0 = new_adj_vmesh(mem_arena, n_bndv, 2, bv->vmesh->boundstart); + /* Find the center of the boundverts that make up the vmesh. */ bndv = vm0->boundstart; - zero_v3(co); - for (i = 0; i < n; i++) { - /* Boundaries just divide input polygon edges into 2 even segments */ + zero_v3(boundverts_center); + for (i = 0; i < n_bndv; i++) { + /* Boundaries just divide input polygon edges into 2 even segments. */ copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->nv.co); get_profile_point(bp, &bndv->profile, 1, 2, mesh_vert(vm0, i, 0, 1)->co); - add_v3_v3(co, bndv->nv.co); + add_v3_v3(boundverts_center, bndv->nv.co); bndv = bndv->next; } - /* To place center vertex: - * coa is original vertex - * co is centroid of boundary corners - * cob is reflection of coa in across co. - * Calculate 'fullness' = fraction of way - * from co to coa (if positive) or to cob (if negative). - */ - copy_v3_v3(coa, bv->v->co); - mul_v3_fl(co, 1.0f / (float)n); - sub_v3_v3v3(cob, co, coa); - add_v3_v3(cob, co); - - /* An offline optimization process found fullness that let to closest fit to sphere as - * a function of r and ns (for case of cube corner) */ - r = bp->pro_super_r; - p = bp->profile; - if (r == PRO_LINE_R) { - fullness = 0.0f; - } - else if (r == PRO_CIRCLE_R && ns > 0 && ns <= CIRCLE_FULLNESS_SEGS) { - fullness = circle_fullness[ns - 1]; - } - else { - /* linear regression fit found best linear function, separately for even/odd segs */ - if (ns % 2 == 0) { - fullness = 2.4506f * p - 0.00000300f * ns - 0.6266f; + mul_v3_fl(boundverts_center, 1.0f / (float)n_bndv); + + /* To place the center vertex: + * 'negative_fullest' is the reflection of the original vertex across the boundverts' center. + * 'fullness' is the fraction of the way from the boundvert's centroid to to the original vertex + * (if positive) or to negative_fullest (if negative). */ + copy_v3_v3(original_vertex, bv->v->co); + sub_v3_v3v3(negative_fullest, boundverts_center, original_vertex); + add_v3_v3(negative_fullest, boundverts_center); + + /* Find the vertex mesh's start center with the profile's fullness */ + fullness = bp->pro_spacing.fullness; + sub_v3_v3v3(center_direction, original_vertex, boundverts_center); + if (len_squared_v3(center_direction) > BEVEL_EPSILON_SQ) { + if (bp->use_custom_profile) { + fullness *= 2.0f; + madd_v3_v3v3fl(mesh_vert(vm0, 0, 1, 1)->co, negative_fullest, center_direction, fullness); } else { - fullness = 2.3635f * p + 0.000152f * ns - 0.6060f; + madd_v3_v3v3fl(mesh_vert(vm0, 0, 1, 1)->co, boundverts_center, center_direction, fullness); } } - sub_v3_v3v3(dir, coa, co); - if (len_squared_v3(dir) > BEVEL_EPSILON_SQ) { - madd_v3_v3fl(co, dir, fullness); + else { + copy_v3_v3(mesh_vert(vm0, 0, 1, 1)->co, boundverts_center); } - copy_v3_v3(mesh_vert(vm0, 0, 1, 1)->co, co); vmesh_copy_equiv_verts(vm0); + /* Do the subdivision process to go from the two segment start mesh to the final vertex mesh. */ vm1 = vm0; do { vm1 = cubic_subdiv(bp, vm1); - } while (vm1->seg < ns); - if (vm1->seg != ns) { - vm1 = interp_vmesh(bp, vm1, ns); + } while (vm1->seg < nseg); + if (vm1->seg != nseg) { + vm1 = interp_vmesh(bp, vm1, nseg); } return vm1; } @@ -4012,15 +4334,16 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3]) Profile *pro = &vpipe->profile; EdgeHalf *e = vpipe->ebev; - copy_v3_v3(va, pro->coa); - copy_v3_v3(vb, pro->cob); + copy_v3_v3(va, pro->start); + copy_v3_v3(vb, pro->end); + /* Get a plane with the normal pointing along the beveled edge */ sub_v3_v3v3(edir, e->e->v1->co, e->e->v2->co); - plane_from_point_normal_v3(plane, co, edir); + closest_to_plane_v3(va0, plane, va); closest_to_plane_v3(vb0, plane, vb); - closest_to_plane_v3(vmid0, plane, pro->midco); + closest_to_plane_v3(vmid0, plane, pro->middle); if (make_unit_square_map(va0, vmid0, vb0, m)) { /* Transform co and project it onto superellipse */ if (!invert_m4_m4(minv, m)) { @@ -4030,6 +4353,7 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3]) } mul_v3_m4v3(p, minv, co); snap_to_superellipsoid(p, pro->super_r, midline); + mul_v3_m4v3(snap, m, p); copy_v3_v3(co, snap); } @@ -4040,38 +4364,123 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3]) } } -/* See pipe_test for conditions that make 'pipe'; vpipe is the return value from that. +/** See pipe_test for conditions that make 'pipe'; vpipe is the return value from that. * We want to make an ADJ mesh but then snap the vertices to the profile in a plane - * perpendicular to the pipes. - * A tricky case is for the 'square' profiles and an even nseg: we want certain vertices - * to snap to the midline on the pipe, not just to one plane or the other. */ + * perpendicular to the pipes. */ static VMesh *pipe_adj_vmesh(BevelParams *bp, BevVert *bv, BoundVert *vpipe) { - int i, j, k, n, ns, ns2, ipipe1, ipipe2; +#ifdef DEBUG_CUSTOM_PROFILE_PIPE + printf("PIPE ADJ VMESH\n"); + float green[4] = {0.0f, 1.0f, 0.0f, 1.0f}; + float blue[4] = {0.0f, 0.0f, 1.0f, 1.0f}; + float red[4] = {1.0f, 0.0f, 0.0f, 1.0f}; + float white[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + float *color; +#endif + int i, j, k, n_bndv, ns, half_ns, ipipe1, ipipe2, ring; VMesh *vm; bool even, midline; + float *profile_point_pipe1, *profile_point_pipe2, f; + /* Some unecessary overhead running this subdivision with custom profile snapping later on. */ vm = adj_vmesh(bp, bv); - /* Now snap all interior coordinates to be on the epipe profile */ - n = bv->vmesh->count; + /* Now snap all interior coordinates to be on the epipe profile. */ + n_bndv = bv->vmesh->count; ns = bv->vmesh->seg; - ns2 = ns / 2; + half_ns = ns / 2; even = (ns % 2) == 0; ipipe1 = vpipe->index; ipipe2 = vpipe->next->next->index; - for (i = 0; i < n; i++) { - for (j = 1; j <= ns2; j++) { - for (k = 0; k <= ns2; k++) { + +#ifdef DEBUG_CUSTOM_PROFILE_PIPE + printf("ipipe1: %d\n", ipipe1); + printf("ipipe2: %d\n", ipipe2); +#endif + + for (i = 0; i < n_bndv; i++) { + for (j = 1; j <= half_ns; j++) { + for (k = 0; k <= half_ns; k++) { if (!is_canon(vm, i, j, k)) { continue; } - midline = even && k == ns2 && ((i == 0 && j == ns2) || (i == ipipe1 || i == ipipe2)); - snap_to_pipe_profile(vpipe, midline, mesh_vert(vm, i, j, k)->co); + if (bp->use_custom_profile) { + /* Find both profile vertices that correspond to this point. */ + if (i == ipipe1 || i == ipipe2) { + if (n_bndv == 3 && i == ipipe1) { + /* This part of the vmesh is the triangular corner between the two pipe profiles. */ + ring = max_ii(j, k); + profile_point_pipe2 = mesh_vert(vm, i, 0, ring)->co; + profile_point_pipe1 = mesh_vert(vm, i, ring, 0)->co; + /* End profile index increases with k on one side and j on the other. */ + f = ((k < j) ? min_ff(j, k) : ((2.0f * ring) - j)) / (2.0f * ring); + } + else { + /* This is part of either pipe profile boundvert area in the 4-way intersection. */ + profile_point_pipe1 = mesh_vert(vm, i, 0, k)->co; + profile_point_pipe2 = mesh_vert(vm, (i == ipipe1) ? ipipe2 : ipipe1, 0, ns - k)->co; + f = (float)j / (float)ns; /* The ring index brings us closer to the other side. */ + } + } + else { + /* The profile vertices are on both ends of each of the side profile's rings. */ + profile_point_pipe1 = mesh_vert(vm, i, j, 0)->co; + profile_point_pipe2 = mesh_vert(vm, i, j, ns)->co; + f = (float)k / (float)ns; /* Ring runs along the pipe, so segment is used here. */ + } + + /* Place the vertex by interpolatin between the two profile points using the factor. */ + interp_v3_v3v3(mesh_vert(vm, i, j, k)->co, profile_point_pipe1, profile_point_pipe2, f); +#ifdef DEBUG_CUSTOM_PROFILE_PIPE + printf("(%d, %d, %d)\n", i, j, k); + printf("f: %.3f\n", f); + printf("point 1: (%.3f, %.3f, %.3f)\n", + profile_point_pipe1[0], + profile_point_pipe1[1], + profile_point_pipe1[2]); + printf("point 2: (%.3f, %.3f, %.3f)\n", + profile_point_pipe2[0], + profile_point_pipe2[1], + profile_point_pipe2[2]); +#endif + } + else { + /* A tricky case is for the 'square' profiles and an even nseg: we want certain + * vertices to snap to the midline on the pipe, not just to one plane or the other. */ + midline = even && k == half_ns && + ((i == 0 && j == half_ns) || (i == ipipe1 || i == ipipe2)); + snap_to_pipe_profile(vpipe, midline, mesh_vert(vm, i, j, k)->co); + } } } } - +#ifdef DEBUG_CUSTOM_PROFILE_PIPE + /* Draw the locations of all the vertices after the "snapping" process */ + for (i = 0; i < n_bndv; i++) { + for (j = 1; j <= half_ns; j++) { + for (k = 1; k <= ns; k++) { + if (!is_canon(vm, i, j, k)) { + continue; + } + switch (i) { + case 0: + color = red; + break; + case 1: + color = green; + break; + case 2: + color = blue; + break; + case 3: + color = white; + break; + } + DRW_debug_sphere(mesh_vert(vm, i, j, k)->co, 0.01f, color); + } + } + } +#endif return vm; } @@ -4123,7 +4532,7 @@ static float snap_face_dist_squared(float *co, BMFace *f, BMEdge **r_snap_e, flo BMEdge *e; float closest[3]; - beste_d2 = 1e20; + beste_d2 = 1e20f; BM_ITER_ELEM (e, &iter, f, BM_EDGES_OF_FACE) { closest_to_line_segment_v3(closest, co, e->v1->co, e->v2->co); d2 = len_squared_v3v3(closest, co); @@ -4243,7 +4652,7 @@ static void closer_v3_v3v3v3(float r[3], float a[3], float b[3], float v[3]) */ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) { - int n, ns, ns2, odd, i, j, k, ikind, im1, clstride, iprev, akind; + int n_bndv, ns, ns2, odd, i, j, k, ikind, im1, clstride, iprev, ang_kind; float bndco[3], dir1[3], dir2[3], co1[3], co2[3], meet1[3], meet2[3], v1co[3], v2co[3]; float *on_edge_cur, *on_edge_prev, *p; float ns2inv, finalfrac, ang; @@ -4253,26 +4662,26 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) float *centerline; bool *cset, v1set, v2set; - n = bv->vmesh->count; + n_bndv = bv->vmesh->count; ns = bv->vmesh->seg; ns2 = ns / 2; odd = ns % 2; ns2inv = 1.0f / (float)ns2; - vm = new_adj_vmesh(bp->mem_arena, n, ns, bv->vmesh->boundstart); + vm = new_adj_vmesh(bp->mem_arena, n_bndv, ns, bv->vmesh->boundstart); clstride = 3 * (ns2 + 1); - centerline = MEM_mallocN(clstride * n * sizeof(float), "bevel"); - cset = MEM_callocN(n * sizeof(bool), "bevel"); + centerline = MEM_mallocN((size_t)(clstride * n_bndv) * sizeof(float), "bevel"); + cset = MEM_callocN((size_t)n_bndv * sizeof(bool), "bevel"); /* find on_edge, place on bndv[i]'s elast where offset line would meet, * taking min-distance-to bv->v with position where next sector's offset line would meet */ bndv = vm->boundstart; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { copy_v3_v3(bndco, bndv->nv.co); e1 = bndv->efirst; e2 = bndv->elast; - akind = 0; + ang_kind = ANGLE_STRAIGHT; if (e1 && e2) { - akind = edges_angle_kind(e1, e2, bv->v); + ang_kind = edges_angle_kind(e1, e2, bv->v); } if (bndv->is_patch_start) { mid_v3_v3v3(centerline + clstride * i, bndv->nv.co, bndv->next->nv.co); @@ -4288,13 +4697,13 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) else if (bndv->is_arc_start) { e1 = bndv->efirst; e2 = bndv->next->efirst; - copy_v3_v3(centerline + clstride * i, bndv->profile.midco); + copy_v3_v3(centerline + clstride * i, bndv->profile.middle); bndv = bndv->next; cset[i] = true; i++; /* leave cset[i] where it was - probably false, unless i == n - 1 */ } - else if (akind < 0) { + else if (ang_kind == ANGLE_SMALLER) { sub_v3_v3v3(dir1, e1->e->v1->co, e1->e->v2->co); sub_v3_v3v3(dir2, e2->e->v1->co, e2->e->v2->co); add_v3_v3v3(co1, bndco, dir1); @@ -4321,7 +4730,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) /* want on_edge[i] to be min dist to bv->v of v2co and the v1co of next iteration */ on_edge_cur = centerline + clstride * i; - iprev = (i == 0) ? n - 1 : i - 1; + iprev = (i == 0) ? n_bndv - 1 : i - 1; on_edge_prev = centerline + clstride * iprev; if (v2set) { if (cset[i]) { @@ -4346,7 +4755,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) } /* Maybe not everything was set by the previous loop */ bndv = vm->boundstart; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { if (!cset[i]) { on_edge_cur = centerline + clstride * i; e1 = bndv->next->efirst; @@ -4381,7 +4790,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) /* fill in rest of centerlines by interpolation */ copy_v3_v3(co2, bv->v->co); bndv = vm->boundstart; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { if (odd) { ang = 0.5f * angle_v3v3v3(bndv->nv.co, co1, bndv->next->nv.co); if (ang > BEVEL_SMALL_ANG) { @@ -4389,7 +4798,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) * such that the base of the triangle is 1. * This is used in interpolation along centerline in odd case. * To avoid too big a drop from bv, cap finalfrac a 0.8 arbitrarily */ - finalfrac = 0.5f / sin(ang); + finalfrac = 0.5f / sinf(ang); if (finalfrac > 0.8f) { finalfrac = 0.8f; } @@ -4412,9 +4821,9 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) /* coords of edges and mid or near-mid line */ bndv = vm->boundstart; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { copy_v3_v3(co1, bndv->nv.co); - copy_v3_v3(co2, centerline + clstride * (i == 0 ? n - 1 : i - 1)); + copy_v3_v3(co2, centerline + clstride * (i == 0 ? n_bndv - 1 : i - 1)); for (j = 0; j < ns2 + odd; j++) { interp_v3_v3v3(mesh_vert(vm, i, j, 0)->co, co1, co2, j * ns2inv); } @@ -4431,8 +4840,8 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) /* fill in interior points by interpolation from edges to centerlines */ bndv = vm->boundstart; - for (i = 0; i < n; i++) { - im1 = (i == 0) ? n - 1 : i - 1; + for (i = 0; i < n_bndv; i++) { + im1 = (i == 0) ? n_bndv - 1 : i - 1; for (j = 1; j < ns2 + odd; j++) { for (k = 1; k <= ns2; k++) { ikind = isect_line_line_v3(mesh_vert(vm, i, 0, k)->co, @@ -4466,43 +4875,43 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv) return vm; } -/* +/** * Given that the boundary is built and the boundary BMVerts have been made, * calculate the positions of the interior mesh points for the M_ADJ pattern, - * using cubic subdivision, then make the BMVerts and the new faces. */ -static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) + * using cubic subdivision, then make the BMVerts and the new faces. + */ +static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv, BoundVert *vpipe) { - int n, ns, ns2, odd, i, j, k, ring; + int n_bndv, ns, ns2, odd, i, j, k, ring; VMesh *vm1, *vm; - BoundVert *v; + BoundVert *bndv; BMVert *bmv1, *bmv2, *bmv3, *bmv4; BMFace *f, *f2, *r_f; BMEdge *bme, *bme1, *bme2, *bme3; EdgeHalf *e; - BoundVert *vpipe; int mat_nr = bp->mat_nr; - n = bv->vmesh->count; + n_bndv = bv->vmesh->count; ns = bv->vmesh->seg; ns2 = ns / 2; odd = ns % 2; - BLI_assert(n >= 3 && ns > 1); + BLI_assert(n_bndv >= 3 && ns > 1); - /* Add support for profiles in vertex only in-plane bevels */ + /* Add support for profiles in vertex only in-plane bevels. */ if (bp->vertex_only) { - v = bv->vmesh->boundstart; + bndv = bv->vmesh->boundstart; do { - Profile *pro = &v->profile; + Profile *pro = &bndv->profile; + copy_v3_v3(pro->middle, bv->v->co); pro->super_r = bp->pro_super_r; - copy_v3_v3(pro->midco, bv->v->co); - calculate_profile(bp, v); - v = v->next; - } while (v != bv->vmesh->boundstart); + bool miter_profile = bp->use_custom_profile && (bndv->is_arc_start || bndv->is_patch_start); + /* Orientation doesn't matter when only beveling vertices */ + calculate_profile(bp, bndv, false, miter_profile); + bndv = bndv->next; + } while (bndv != bv->vmesh->boundstart); } - vpipe = pipe_test(bv); - - if (bp->pro_super_r == PRO_SQUARE_R && bv->selcount >= 3 && !odd) { + if (bp->pro_super_r == PRO_SQUARE_R && bv->selcount >= 3 && !odd && !bp->use_custom_profile) { vm1 = square_out_adj_vmesh(bp, bv); } else if (vpipe) { @@ -4512,7 +4921,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) vm1 = tri_corner_adj_vmesh(bp, bv); /* the PRO_SQUARE_IN_R profile has boundary edges that merge * and no internal ring polys except possibly center ngon */ - if (bp->pro_super_r == PRO_SQUARE_IN_R) { + if (bp->pro_super_r == PRO_SQUARE_IN_R && !bp->use_custom_profile) { build_square_in_vmesh(bp, bm, bv, vm1); return; } @@ -4523,7 +4932,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) /* copy final vmesh into bv->vmesh, make BMVerts and BMFaces */ vm = bv->vmesh; - for (i = 0; i < n; i++) { + for (i = 0; i < n_bndv; i++) { for (j = 0; j <= ns2; j++) { for (k = 0; k <= ns; k++) { if (j == 0 && (k == 0 || k == ns)) { @@ -4539,16 +4948,16 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) } vmesh_copy_equiv_verts(vm); /* make the polygons */ - v = vm->boundstart; + bndv = vm->boundstart; do { - i = v->index; - f = boundvert_rep_face(v, NULL); - f2 = boundvert_rep_face(v->next, NULL); + i = bndv->index; + f = boundvert_rep_face(bndv, NULL); + f2 = boundvert_rep_face(bndv->next, NULL); if (bp->vertex_only) { - e = v->efirst; + e = bndv->efirst; } else { - e = v->ebev; + e = bndv->ebev; } bme = e ? e->e : NULL; /* For odd ns, make polys with lower left corner at (i,j,k) for @@ -4576,7 +4985,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) f2, NULL, NULL, - v->next->efirst->e, + bndv->next->efirst->e, bme, mat_nr); } @@ -4617,8 +5026,8 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) else { bme1 = k == ns2 - 1 ? bme : NULL; bme3 = NULL; - if (j == ns2 - 1 && v->prev->ebev) { - bme3 = v->prev->ebev->e; + if (j == ns2 - 1 && bndv->prev->ebev) { + bme3 = bndv->prev->ebev->e; } bme2 = bme1 != NULL ? bme1 : bme3; r_f = bev_create_quad_ex( @@ -4628,14 +5037,14 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) record_face_kind(bp, r_f, F_VERT); } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); /* Fix UVs along center lines if even number of segments */ if (!odd) { - v = vm->boundstart; + bndv = vm->boundstart; do { - i = v->index; - if (!v->any_seam) { + i = bndv->index; + if (!bndv->any_seam) { for (ring = 1; ring < ns2; ring++) { BMVert *v_uv = mesh_vert(vm, i, ring, ns2)->v; if (v_uv) { @@ -4643,7 +5052,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) } } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); bmv1 = mesh_vert(vm, 0, ns2, ns2)->v; if (bp->vertex_only || count_bound_vert_seams(bv) <= 1) { bev_merge_uvs(bm, bmv1); @@ -4656,6 +5065,179 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv) } } +/** Builds the vertex mesh when the vertex mesh type is set to "cut off" with a face closing + * off each incoming edge's profile */ +/* HANS-TODO: Make cutoff VMesh work with outer miter != sharp. This should be possible but there + * are two problems currently: + * - Miter profiles don't have plane_no filled, so down direction is incorrect. + * - Indexing profile points of miters with (i, 0, k) seems to return zero except for the first + * and last profile point. */ +/* HANS-TODO: Use repface / edge arrays for UV interpolation properly. */ +static void bevel_build_cutoff(BevelParams *bp, BMesh *bm, BevVert *bv) +{ +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("BEVEL BUILD CUTOFF\n"); + int j; +#endif + int i; + int n_bndv = bv->vmesh->count; + BoundVert *bndv; + float length; + float down_direction[3], new_vert[3]; + bool build_center_face; + /* BMFace *repface; */ + BMVert **face_bmverts = NULL; + BMEdge **bmedges = NULL; + BMFace **bmfaces = NULL; + + /* Find the locations for the corner vertices at the bottom of the cutoff faces. */ + bndv = bv->vmesh->boundstart; + do { + i = bndv->index; + + /* Find the "down" direction for this side of the cutoff face. */ + /* Find the direction along the intersection of the two adjecent profile normals. */ + cross_v3_v3v3(down_direction, bndv->profile.plane_no, bndv->prev->profile.plane_no); + if (dot_v3v3(down_direction, bv->v->no) > 0.0f) { + negate_v3(down_direction); + } + + /* Move down from the boundvert by average profile height from the two adjacent profiles. */ + length = (bndv->profile.height / sqrtf(2.0f) + bndv->prev->profile.height / sqrtf(2.0f)) / 2; + madd_v3_v3v3fl(new_vert, bndv->nv.co, down_direction, length); + + /* Use this location for this profile's first corner vert and the last profile's second. */ + copy_v3_v3(mesh_vert(bv->vmesh, i, 1, 0)->co, new_vert); + copy_v3_v3(mesh_vert(bv->vmesh, bndv->prev->index, 1, 1)->co, new_vert); + + } while ((bndv = bndv->next) != bv->vmesh->boundstart); + +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("Corner vertices:\n"); + for (j = 0; j < n_bndv; j++) { + printf(" (%.3f, %.3f, %.3f)\n", + (double)mesh_vert(bv->vmesh, j, 1, 0)->co[0], + (double)mesh_vert(bv->vmesh, j, 1, 0)->co[1], + (double)mesh_vert(bv->vmesh, j, 1, 0)->co[2]); + } +#endif + + /* Disable the center face if the corner vertices share the same location. */ + build_center_face = true; + if (n_bndv == 3) { /* Vertices only collapse with a 3-way VMesh. */ + build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 0, 1, 0)->co, + mesh_vert(bv->vmesh, 1, 1, 0)->co) > BEVEL_EPSILON; + build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 0, 1, 0)->co, + mesh_vert(bv->vmesh, 2, 1, 0)->co) > BEVEL_EPSILON; + build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 1, 1, 0)->co, + mesh_vert(bv->vmesh, 2, 1, 0)->co) > BEVEL_EPSILON; + } +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("build_center_face: %d\n", build_center_face); +#endif + + /* Create the corner vertex BMVerts. */ + if (build_center_face) { + do { + i = bndv->index; + create_mesh_bmvert(bm, bv->vmesh, i, 1, 0, bv->v); + /* The second corner vertex for the previous profile shares this BMVert. */ + mesh_vert(bv->vmesh, bndv->prev->index, 1, 1)->v = mesh_vert(bv->vmesh, i, 1, 0)->v; + + } while ((bndv = bndv->next) != bv->vmesh->boundstart); + } + else { + /* Use the same BMVert for all of the corner vertices. */ + create_mesh_bmvert(bm, bv->vmesh, 0, 1, 0, bv->v); + for (i = 1; i < n_bndv; i++) { + mesh_vert(bv->vmesh, i, 1, 0)->v = mesh_vert(bv->vmesh, 0, 1, 0)->v; + } + } + + /* Build the profile cutoff faces. */ + /* Extra one or two for corner vertices and one for last point along profile, or the size of the + * center face array if it's bigger. */ +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("Building profile cutoff faces.\n"); +#endif + face_bmverts = BLI_memarena_alloc( + bp->mem_arena, ((size_t)max_ii(bp->seg + 2 + build_center_face, n_bndv) * sizeof(BMVert *))); + bndv = bv->vmesh->boundstart; + do { + i = bndv->index; + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); + + /* Add the first corner vertex under this boundvert */ + face_bmverts[0] = mesh_vert(bv->vmesh, i, 1, 0)->v; + +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf("Profile Number %d:\n", i); + if (bndv->is_patch_start || bndv->is_arc_start) { + printf(" Miter profile\n"); + } + printf(" Corner 1: (%0.3f, %0.3f, %0.3f)\n", + (double)mesh_vert(bv->vmesh, i, 1, 0)->co[0], + (double)mesh_vert(bv->vmesh, i, 1, 0)->co[1], + (double)mesh_vert(bv->vmesh, i, 1, 0)->co[2]); +#endif + + /* Add profile point vertices to the face, including the last one. */ + for (int k = 0; k < bp->seg + 1; k++) { + face_bmverts[k + 1] = mesh_vert(bv->vmesh, i, 0, k)->v; /* Leave room for first vert. */ +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf(" Profile %d: (%0.3f, %0.3f, %0.3f)\n", + k, + (double)mesh_vert(bv->vmesh, i, 0, k)->co[0], + (double)mesh_vert(bv->vmesh, i, 0, k)->co[1], + (double)mesh_vert(bv->vmesh, i, 0, k)->co[2]); +#endif + } + + /* Add the second corner vert to complete the bottom of the face. */ + if (build_center_face) { + face_bmverts[bp->seg + 2] = mesh_vert(bv->vmesh, i, 1, 1)->v; +#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF + printf(" Corner 2: (%0.3f, %0.3f, %0.3f)\n", + (double)mesh_vert(bv->vmesh, i, 1, 1)->co[0], + (double)mesh_vert(bv->vmesh, i, 1, 1)->co[1], + (double)mesh_vert(bv->vmesh, i, 1, 1)->co[2]); +#endif + } + + /* Create the profile cutoff face for this boundvert. */ + /* repface = boundvert_rep_face(bndv, NULL); */ + bev_create_ngon(bm, + face_bmverts, + bp->seg + 2 + build_center_face, + bmfaces, + NULL, + bmedges, + bp->mat_nr, + true); + + BLI_array_free(bmedges); + BLI_array_free(bmfaces); + } while ((bndv = bndv->next) != bv->vmesh->boundstart); + + /* Create the bottom face if it should be built, reusing previous face_bmverts allocation. */ + if (build_center_face) { + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); + + /* Add all of the corner vertices to this face. */ + for (i = 0; i < n_bndv; i++) { + /* Add verts from each cutoff face. */ + face_bmverts[i] = mesh_vert(bv->vmesh, i, 1, 0)->v; + } + /* BLI_array_append(bmfaces, repface); */ + bev_create_ngon(bm, face_bmverts, n_bndv, bmfaces, NULL, bmedges, bp->mat_nr, true); + + BLI_array_free(bmedges); + BLI_array_free(bmfaces); + } +} + /* If we make a poly out of verts around bv, snapping to rep frep, will uv poly have zero area? * The uv poly is made by snapping all outside-of-frep vertices to the closest edge in frep. * Assume that this function is called when the only inside-of-frep vertex is vm->boundstart. @@ -4690,71 +5272,72 @@ static bool is_bad_uv_poly(BevVert *bv, BMFace *frep) static BMFace *bevel_build_poly(BevelParams *bp, BMesh *bm, BevVert *bv) { - BMFace *f, *frep, *frep2; + BMFace *f, *repface, *frep2; int n, k; VMesh *vm = bv->vmesh; - BoundVert *v; - BMEdge *frep_e1, *frep_e2, *frep_e; - BMVert **vv = NULL; - BMFace **vf = NULL; - BMEdge **ve = NULL; - BLI_array_staticdeclare(vv, BM_DEFAULT_NGON_STACK_SIZE); - BLI_array_staticdeclare(vf, BM_DEFAULT_NGON_STACK_SIZE); - BLI_array_staticdeclare(ve, BM_DEFAULT_NGON_STACK_SIZE); + BoundVert *bndv; + BMEdge *repface_e1, *repface_e2, *frep_e; + BMVert **bmverts = NULL; + BMEdge **bmedges = NULL; + BMFace **bmfaces = NULL; + BLI_array_staticdeclare(bmverts, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); if (bv->any_seam) { - frep = boundvert_rep_face(vm->boundstart, &frep2); - if (frep2 && frep && is_bad_uv_poly(bv, frep)) { - frep = frep2; + repface = boundvert_rep_face(vm->boundstart, &frep2); + if (frep2 && repface && is_bad_uv_poly(bv, repface)) { + repface = frep2; } - get_incident_edges(frep, bv->v, &frep_e1, &frep_e2); + get_incident_edges(repface, bv->v, &repface_e1, &repface_e2); } else { - frep = NULL; - frep_e1 = frep_e2 = NULL; + repface = NULL; + repface_e1 = repface_e2 = NULL; } - v = vm->boundstart; + bndv = vm->boundstart; n = 0; do { /* accumulate vertices for vertex ngon */ /* also accumulate faces in which uv interpolation is to happen for each */ - BLI_array_append(vv, v->nv.v); - if (frep) { - BLI_array_append(vf, frep); - frep_e = find_closer_edge(v->nv.v->co, frep_e1, frep_e2); - BLI_array_append(ve, n > 0 ? frep_e : NULL); + BLI_array_append(bmverts, bndv->nv.v); + if (repface) { + BLI_array_append(bmfaces, repface); + frep_e = find_closer_edge(bndv->nv.v->co, repface_e1, repface_e2); + BLI_array_append(bmedges, n > 0 ? frep_e : NULL); } else { - BLI_array_append(vf, boundvert_rep_face(v, NULL)); - BLI_array_append(ve, NULL); + BLI_array_append(bmfaces, boundvert_rep_face(bndv, NULL)); + BLI_array_append(bmedges, NULL); } n++; - if (v->ebev && v->ebev->seg > 1) { - for (k = 1; k < v->ebev->seg; k++) { - BLI_array_append(vv, mesh_vert(vm, v->index, 0, k)->v); - if (frep) { - BLI_array_append(vf, frep); - frep_e = find_closer_edge(mesh_vert(vm, v->index, 0, k)->v->co, frep_e1, frep_e2); - BLI_array_append(ve, k < v->ebev->seg / 2 ? NULL : frep_e); + if (bndv->ebev && bndv->ebev->seg > 1) { + for (k = 1; k < bndv->ebev->seg; k++) { + BLI_array_append(bmverts, mesh_vert(vm, bndv->index, 0, k)->v); + if (repface) { + BLI_array_append(bmfaces, repface); + frep_e = find_closer_edge( + mesh_vert(vm, bndv->index, 0, k)->v->co, repface_e1, repface_e2); + BLI_array_append(bmedges, k < bndv->ebev->seg / 2 ? NULL : frep_e); } else { - BLI_array_append(vf, boundvert_rep_face(v, NULL)); - BLI_array_append(ve, NULL); + BLI_array_append(bmfaces, boundvert_rep_face(bndv, NULL)); + BLI_array_append(bmedges, NULL); } n++; } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); if (n > 2) { - f = bev_create_ngon(bm, vv, n, vf, frep, ve, bp->mat_nr, true); + f = bev_create_ngon(bm, bmverts, n, bmfaces, repface, bmedges, bp->mat_nr, true); record_face_kind(bp, f, F_VERT); } else { f = NULL; } - BLI_array_free(vv); - BLI_array_free(vf); - BLI_array_free(ve); + BLI_array_free(bmverts); + BLI_array_free(bmedges); + BLI_array_free(bmfaces); return f; } @@ -4817,6 +5400,7 @@ static void bevel_build_trifan(BevelParams *bp, BMesh *bm, BevVert *bv) * we have to make it here. */ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv) { + VMesh *vm = bv->vmesh; BMVert *v1, *v2; BMEdge *e_eg, *bme; @@ -4832,18 +5416,20 @@ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv) ns = vm->seg; if (ns > 1) { - /* Set up profile parameters */ + /* Set up profile parameters. */ bndv = vm->boundstart; pro = &bndv->profile; pro->super_r = bp->pro_super_r; - copy_v3_v3(pro->coa, v1->co); - copy_v3_v3(pro->cob, v2->co); - copy_v3_v3(pro->midco, bv->v->co); - /* don't use projection */ + copy_v3_v3(pro->start, v1->co); + copy_v3_v3(pro->end, v2->co); + copy_v3_v3(pro->middle, bv->v->co); + /* Don't use projection. */ zero_v3(pro->plane_co); zero_v3(pro->plane_no); zero_v3(pro->proj_dir); - calculate_profile(bp, bndv); + /* there's no orientation chain to continue so the orientation of the bevel doesn't matter. */ + calculate_profile(bp, bndv, false, false); + for (k = 1; k < ns; k++) { get_profile_point(bp, pro, k, ns, co); copy_v3_v3(mesh_vert(vm, 0, 0, k)->co, co); @@ -4874,84 +5460,129 @@ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv) * for the boundary and the interior of the vertex mesh. */ static void build_vmesh(BevelParams *bp, BMesh *bm, BevVert *bv) { - MemArena *mem_arena = bp->mem_arena; VMesh *vm = bv->vmesh; - BoundVert *v, *weld1, *weld2; + BoundVert *bndv, *weld1, *weld2, *vpipe; int n, ns, ns2, i, k, weld; - float *va, *vb, co[3]; + float *v_weld1, *v_weld2, co[3]; n = vm->count; ns = vm->seg; ns2 = ns / 2; - vm->mesh = (NewVert *)BLI_memarena_alloc(mem_arena, n * (ns2 + 1) * (ns + 1) * sizeof(NewVert)); + vm->mesh = (NewVert *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(n * (ns2 + 1) * (ns + 1)) * sizeof(NewVert)); - /* special case: two beveled ends welded together */ + /* Special case: just two beveled edges welded together. */ weld = (bv->selcount == 2) && (vm->count == 2); - weld1 = weld2 = NULL; /* will hold two BoundVerts involved in weld */ + weld1 = weld2 = NULL; /* Will hold two BoundVerts involved in weld. */ - /* make (i, 0, 0) mesh verts for all i */ - v = vm->boundstart; + /* Make (i, 0, 0) mesh verts for all i boundverts. */ + bndv = vm->boundstart; do { - i = v->index; - copy_v3_v3(mesh_vert(vm, i, 0, 0)->co, v->nv.co); - create_mesh_bmvert(bm, vm, i, 0, 0, bv->v); - v->nv.v = mesh_vert(vm, i, 0, 0)->v; - if (weld && v->ebev) { + i = bndv->index; + copy_v3_v3(mesh_vert(vm, i, 0, 0)->co, bndv->nv.co); /* Mesh NewVert to boundary NewVert. */ + create_mesh_bmvert(bm, vm, i, 0, 0, bv->v); /* Create BMVert for that NewVert. */ + bndv->nv.v = mesh_vert(vm, i, 0, 0)->v; /* Use the BMVert for the BoundVert's NewVert. */ + + /* Find boundverts and move profile planes if this is a weld case. */ + if (weld && bndv->ebev) { if (!weld1) { - weld1 = v; + weld1 = bndv; } - else { - weld2 = v; - move_weld_profile_planes(bv, weld1, weld2); - calculate_profile(bp, weld1); - calculate_profile(bp, weld2); + else { /* Get the last of the two BoundVerts. */ + weld2 = bndv; + move_weld_profile_planes(bv, weld1, weld2); /* Profile recalculated in next loop. */ } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); - /* copy other ends to (i, 0, ns) for all i, and fill in profiles for edges */ - v = vm->boundstart; + /* Create new vertices and place them based on the profiles. */ + /* Copy other ends to (i, 0, ns) for all i, and fill in profiles for edges. */ + bndv = vm->boundstart; do { - i = v->index; - copy_mesh_vert(vm, i, 0, ns, v->next->index, 0, 0); - for (k = 1; k < ns; k++) { - if (v->ebev && vm->mesh_kind != M_ADJ) { - get_profile_point(bp, &v->profile, k, ns, co); - copy_v3_v3(mesh_vert(vm, i, 0, k)->co, co); - if (!weld) { - create_mesh_bmvert(bm, vm, i, 0, k, bv->v); + i = bndv->index; + /* bndv's last vert along the boundary arc is the the first of the next BoundVert's arc. */ + copy_mesh_vert(vm, i, 0, ns, bndv->next->index, 0, 0); + + /* Fix the profile orientations if it's not a miter profile. */ + if (bp->use_custom_profile && !bndv->is_arc_start && !bndv->is_patch_start) { + calculate_profile(bp, bndv, !bndv->is_profile_start, false); + } + if (vm->mesh_kind != M_ADJ) { + for (k = 1; k < ns; k++) { + if (bndv->ebev) { + get_profile_point(bp, &bndv->profile, k, ns, co); + copy_v3_v3(mesh_vert(vm, i, 0, k)->co, co); /* Get NewVert location from profile coord */ + if (!weld) { + /* This is done later with (possibly) better positions for the weld case. */ + create_mesh_bmvert(bm, vm, i, 0, k, bv->v); + } + } + else if (n == 2 && !bndv->ebev) { + /* case of one edge beveled and this is the v without ebev */ + /* want to copy the verts from other v, in reverse order */ + copy_mesh_vert(bv->vmesh, i, 0, k, 1 - i, 0, ns - k); } - } - else if (n == 2 && !v->ebev && vm->mesh_kind != M_ADJ) { - /* case of one edge beveled and this is the v without ebev */ - /* want to copy the verts from other v, in reverse order */ - copy_mesh_vert(vm, i, 0, k, 1 - i, 0, ns - k); } } - } while ((v = v->next) != vm->boundstart); + } while ((bndv = bndv->next) != vm->boundstart); + /* Build the profile for the weld case (just a connection between the two boundverts). */ if (weld) { - vm->mesh_kind = M_NONE; + bv->vmesh->mesh_kind = M_NONE; for (k = 1; k < ns; k++) { - va = mesh_vert(vm, weld1->index, 0, k)->co; - vb = mesh_vert(vm, weld2->index, 0, ns - k)->co; - /* if one of the profiles is on a flat plane, - * just use the boundary point of the other */ - if (weld1->profile.super_r == PRO_LINE_R && weld2->profile.super_r != PRO_LINE_R) { - copy_v3_v3(co, vb); - } - else if (weld2->profile.super_r == PRO_LINE_R && weld1->profile.super_r != PRO_LINE_R) { - copy_v3_v3(co, va); + v_weld1 = mesh_vert(bv->vmesh, weld1->index, 0, k)->co; + v_weld2 = mesh_vert(bv->vmesh, weld2->index, 0, ns - k)->co; + if (bp->use_custom_profile) { + /* Don't bother with special case profile check from below. */ + mid_v3_v3v3(co, v_weld1, v_weld2); } else { - mid_v3_v3v3(co, va, vb); + /* Use the point from the other profile if one is in a special case. */ + if (weld1->profile.super_r == PRO_LINE_R && weld2->profile.super_r != PRO_LINE_R) { + copy_v3_v3(co, v_weld2); + } + else if (weld2->profile.super_r == PRO_LINE_R && weld1->profile.super_r != PRO_LINE_R) { + copy_v3_v3(co, v_weld1); + } + else { + /* In case the profiles aren't snapped to the same plane, use their midpoint. */ + mid_v3_v3v3(co, v_weld1, v_weld2); + } } - copy_v3_v3(mesh_vert(vm, weld1->index, 0, k)->co, co); - create_mesh_bmvert(bm, vm, weld1->index, 0, k, bv->v); + copy_v3_v3(mesh_vert(bv->vmesh, weld1->index, 0, k)->co, co); + create_mesh_bmvert(bm, bv->vmesh, weld1->index, 0, k, bv->v); } for (k = 1; k < ns; k++) { - copy_mesh_vert(vm, weld2->index, 0, ns - k, weld1->index, 0, k); + copy_mesh_vert(bv->vmesh, weld2->index, 0, ns - k, weld1->index, 0, k); + } + } +#ifdef DEBUG_CUSTOM_PROFILE_WELD + if (weld && ns > 1) { + printf("Weld1 profile coordinates:\n"); + for (k = 0; k < ns; k++) { + printf("%0.4f, %0.4f, %0.4f\n", + (double)weld1->profile.prof_co[3 * k], + (double)weld1->profile.prof_co[3 * k + 1], + (double)weld1->profile.prof_co[3 * k + 2]); + } + printf("Weld2 profile coordinates\n"); + for (k = 0; k < ns; k++) { + printf("%0.4f, %0.4f, %0.4f\n", + (double)weld2->profile.prof_co[3 * k], + (double)weld2->profile.prof_co[3 * k + 1], + (double)weld2->profile.prof_co[3 * k + 2]); + } + } +#endif + + /* Make sure the pipe case ADJ mesh is used for both the "Grid Fill" (ADJ) and cutoff options. */ + vpipe = NULL; + if ((vm->count == 3 || vm->count == 4) && bp->seg > 1) { + /* Result is passed to bevel_build_rings to avoid overhead. */ + vpipe = pipe_test(bv); + if (vpipe) { + vm->mesh_kind = M_ADJ; } } @@ -4965,11 +5596,13 @@ static void build_vmesh(BevelParams *bp, BMesh *bm, BevVert *bv) bevel_build_poly(bp, bm, bv); break; case M_ADJ: - bevel_build_rings(bp, bm, bv); + bevel_build_rings(bp, bm, bv, vpipe); break; case M_TRI_FAN: bevel_build_trifan(bp, bm, bv); break; + case M_CUTOFF: + bevel_build_cutoff(bp, bm, bv); } } @@ -5229,9 +5862,7 @@ static void find_bevel_edge_order(BMesh *bm, BevVert *bv, BMEdge *first_bme) } } -/* - * Construction around the vertex - */ +/* Construction around the vertex */ static BevVert *bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) { BMEdge *bme; @@ -5504,7 +6135,7 @@ static BevVert *bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) return bv; } -/* Face f has at least one beveled vertex. Rebuild f */ +/* Face f has at least one beveled vertex. Rebuild f */ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) { BMIter liter, eiter, fiter; @@ -5849,7 +6480,7 @@ static void weld_cross_attrs_copy(BMesh *bm, BevVert *bv, VMesh *vm, int vmindex } BLI_assert(bme_prev && bme_next); - /* want seams and sharp edges to cross only if that way on both sides */ + /* Want seams and sharp edges to cross only if that way on both sides. */ disable_seam = BM_elem_flag_test(bme_prev, BM_ELEM_SEAM) != BM_elem_flag_test(bme_next, BM_ELEM_SEAM); enable_smooth = BM_elem_flag_test(bme_prev, BM_ELEM_SMOOTH) != @@ -5869,8 +6500,8 @@ static void weld_cross_attrs_copy(BMesh *bm, BevVert *bv, VMesh *vm, int vmindex } } -/* - * Build the polygons along the selected Edge +/** + * Build the bevel polygons along the selected Edge. */ static void bevel_build_edge_polygons(BMesh *bm, BevelParams *bp, BMEdge *bme) { @@ -6269,50 +6900,131 @@ static void find_even_superellipse_chords(int n, float r, double *xvals, double find_even_superellipse_chords_general(n, r, xvals, yvals); } -/* The superellipse used for multisegment profiles does not - * have a closed-form way to generate evenly spaced points - * along an arc. We use an expensive search procedure to find - * the parameter values that lead to bp->seg even chords. - * We also want spacing for a number of segments that is - * a power of 2 >= bp->seg (but at least 4). - * Use doubles because otherwise we cannot come close to float - * precision for final results. */ -static void set_profile_spacing(BevelParams *bp) +/** Find the profile's "fullness," which is the fraction of the space it takes up way from the + * boundvert's centroid to to the original vertex for a non-custom profile, or in the case of a + * custom profile, the average "height" of the profile points along its centerline. */ +static float find_profile_fullness(BevelParams *bp) +{ + float fullness; + int nseg = bp->seg; + + /* Precalculated fullness for circle profile radius and more common low seg values. */ +#define CIRCLE_FULLNESS_SEGS 11 + static const float circle_fullness[CIRCLE_FULLNESS_SEGS] = { + 0.0f, /* nsegs == 1 */ + 0.559f, /* 2 */ + 0.642f, /* 3 */ + 0.551f, /* 4 */ + 0.646f, /* 5 */ + 0.624f, /* 6 */ + 0.646f, /* 7 */ + 0.619f, /* 8 */ + 0.647f, /* 9 */ + 0.639f, /* 10 */ + 0.647f, /* 11 */ + }; + + if (bp->use_custom_profile) { + /* Set fullness to the average "height" of the profile's sampled points. */ + fullness = 0.0f; + for (int i = 0; i < nseg; i++) { /* Don't use the end points. */ + fullness += (float)(bp->pro_spacing.xvals[i] + bp->pro_spacing.yvals[i]) / (2.0f * nseg); + } + } + else { + /* An offline optimization process found fullness that led to closest fit to sphere as + * a function of r and ns (for case of cube corner). */ + if (bp->pro_super_r == PRO_LINE_R) { + fullness = 0.0f; + } + else if (bp->pro_super_r == PRO_CIRCLE_R && nseg > 0 && nseg <= CIRCLE_FULLNESS_SEGS) { + fullness = circle_fullness[nseg - 1]; + } + else { + /* Linear regression fit found best linear function, separately for even/odd segs. */ + if (nseg % 2 == 0) { + fullness = 2.4506f * bp->profile - 0.00000300f * nseg - 0.6266f; + } + else { + fullness = 2.3635f * bp->profile + 0.000152f * nseg - 0.6060f; + } + } + } + return fullness; +} + +/** Fills the ProfileSpacing struct with the 2D coordinates for the profile's vertices. + * The superellipse used for multisegment profiles does not have a closed-form way + * to generate evenly spaced points along an arc. We use an expensive search procedure + * to find the parameter values that lead to bp->seg even chords. + * We also want spacing for a number of segments that is a power of 2 >= bp->seg (but at least 4). + * Use doubles because otherwise we cannot come close to float precision for final results. + * \param pro_spacing: The struct to fill. Changes depending on whether there needs + to be a separate miter profile. */ +static void set_profile_spacing(BevelParams *bp, ProfileSpacing *pro_spacing, bool custom) { int seg, seg_2; seg = bp->seg; + seg_2 = power_of_2_max_i(bp->seg); if (seg > 1) { - bp->pro_spacing.xvals = (double *)BLI_memarena_alloc(bp->mem_arena, - (seg + 1) * sizeof(double)); - bp->pro_spacing.yvals = (double *)BLI_memarena_alloc(bp->mem_arena, - (seg + 1) * sizeof(double)); - find_even_superellipse_chords( - seg, bp->pro_super_r, bp->pro_spacing.xvals, bp->pro_spacing.yvals); - seg_2 = power_of_2_max_i(bp->seg); + /* Sample the input number of segments. */ + pro_spacing->xvals = (double *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(seg + 1) * sizeof(double)); + pro_spacing->yvals = (double *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(seg + 1) * sizeof(double)); + if (custom) { + /* Make sure the curve profile's sample table is full. */ + if (bp->custom_profile->segments_len != seg || !bp->custom_profile->segments) { + BKE_curveprofile_initialize((CurveProfile *)bp->custom_profile, (short)seg); + } + + /* Copy segment locations into the profile spacing struct. */ + for (int i = 0; i < seg + 1; i++) { + pro_spacing->xvals[i] = (double)bp->custom_profile->segments[i].y; + pro_spacing->yvals[i] = (double)bp->custom_profile->segments[i].x; + } + } + else { + find_even_superellipse_chords(seg, bp->pro_super_r, pro_spacing->xvals, pro_spacing->yvals); + } + + /* Sample the seg_2 segments used for subdividing the vertex meshes. */ if (seg_2 == 2) { seg_2 = 4; } bp->pro_spacing.seg_2 = seg_2; if (seg_2 == seg) { - bp->pro_spacing.xvals_2 = bp->pro_spacing.xvals; - bp->pro_spacing.yvals_2 = bp->pro_spacing.yvals; + pro_spacing->xvals_2 = pro_spacing->xvals; + pro_spacing->yvals_2 = pro_spacing->yvals; } else { - bp->pro_spacing.xvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena, - (seg_2 + 1) * sizeof(double)); - bp->pro_spacing.yvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena, - (seg_2 + 1) * sizeof(double)); - find_even_superellipse_chords( - seg_2, bp->pro_super_r, bp->pro_spacing.xvals_2, bp->pro_spacing.yvals_2); + pro_spacing->xvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(seg_2 + 1) * sizeof(double)); + pro_spacing->yvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena, + (size_t)(seg_2 + 1) * sizeof(double)); + if (custom) { + /* Make sure the curve profile widget's sample table is full of the seg_2 samples. */ + BKE_curveprofile_initialize((CurveProfile *)bp->custom_profile, (short)seg_2); + + /* Copy segment locations into the profile spacing struct. */ + for (int i = 0; i < seg_2 + 1; i++) { + pro_spacing->xvals_2[i] = (double)bp->custom_profile->segments[i].y; + pro_spacing->yvals_2[i] = (double)bp->custom_profile->segments[i].x; + } + } + else { + find_even_superellipse_chords( + seg_2, bp->pro_super_r, pro_spacing->xvals_2, pro_spacing->yvals_2); + } } } - else { - bp->pro_spacing.xvals = NULL; - bp->pro_spacing.yvals = NULL; - bp->pro_spacing.xvals_2 = NULL; - bp->pro_spacing.yvals_2 = NULL; - bp->pro_spacing.seg_2 = 0; + else { /* Only 1 segment, we don't need any profile information */ + pro_spacing->xvals = NULL; + pro_spacing->yvals = NULL; + pro_spacing->xvals_2 = NULL; + pro_spacing->yvals_2 = NULL; + pro_spacing->seg_2 = 0; } } @@ -6320,7 +7032,7 @@ static void set_profile_spacing(BevelParams *bp) * Assume we have a situation like: * * a d - * \ / + * \ / * A \ / C * \ th1 th2/ * b---------c @@ -6566,7 +7278,10 @@ void BM_mesh_bevel(BMesh *bm, const int miter_outer, const int miter_inner, const float spread, - const float smoothresh) + const float smoothresh, + const bool use_custom_profile, + const struct CurveProfile *custom_profile, + const int vmesh_method) { BMIter iter, liter; BMVert *v, *v_next; @@ -6578,9 +7293,9 @@ void BM_mesh_bevel(BMesh *bm, bp.offset = offset; bp.offset_type = offset_type; - bp.seg = segments; + bp.seg = (int)segments; bp.profile = profile; - bp.pro_super_r = -log(2.0) / log(sqrt(profile)); /* convert to superellipse exponent */ + bp.pro_super_r = -logf(2.0) / logf(sqrtf(profile)); /* convert to superellipse exponent */ bp.vertex_only = vertex_only; bp.use_weights = use_weights; bp.loop_slide = loop_slide; @@ -6598,6 +7313,15 @@ void BM_mesh_bevel(BMesh *bm, bp.spread = spread; bp.smoothresh = smoothresh; bp.face_hash = NULL; + bp.use_custom_profile = use_custom_profile; + bp.custom_profile = custom_profile; + bp.vmesh_method = vmesh_method; + + /* Disable the miters with the cutoff vertex mesh method, this combination isn't useful anyway */ + if (bp.vmesh_method == BEVEL_VMESH_CUTOFF) { + bp.miter_outer = BEVEL_MITER_SHARP; + bp.miter_inner = BEVEL_MITER_SHARP; + } if (profile >= 0.950f) { /* r ~ 692, so PRO_SQUARE_R is 1e4 */ bp.pro_super_r = PRO_SQUARE_R; @@ -6608,7 +7332,18 @@ void BM_mesh_bevel(BMesh *bm, bp.vert_hash = BLI_ghash_ptr_new(__func__); bp.mem_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 16), __func__); BLI_memarena_use_calloc(bp.mem_arena); - set_profile_spacing(&bp); + + /* Get the 2D profile point locations from either the superellipse or the custom profile */ + set_profile_spacing(&bp, &bp.pro_spacing, bp.use_custom_profile); + if (bp.seg > 1) { + bp.pro_spacing.fullness = find_profile_fullness(&bp); + } + + /* Get separate non-custom profile samples for the miter profiles if they are needed */ + if (bp.use_custom_profile && + (bp.miter_inner != BEVEL_MITER_SHARP || bp.miter_outer != BEVEL_MITER_SHARP)) { + set_profile_spacing(&bp, &bp.pro_spacing_miter, false); + } bp.face_hash = BLI_ghash_ptr_new(__func__); BLI_ghash_flag_set(bp.face_hash, GHASH_FLAG_ALLOW_DUPES); @@ -6643,6 +7378,20 @@ void BM_mesh_bevel(BMesh *bm, adjust_offsets(&bp, bm); } + /* Maintain consistent orientations for the unsymmetrical custom profiles. */ + if (bp.use_custom_profile) { + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_TAG)) { + regularize_profile_orientation(&bp, e); + } + } + } +#ifdef DEBUG_PROFILE_ORIENTATION_DRAW + if (bp.use_custom_profile) { + debug_draw_profile_orientation(&bp, bm); + } +#endif + /* Build the meshes around vertices, now that positions are final */ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(v, BM_ELEM_TAG)) { @@ -6688,14 +7437,14 @@ void BM_mesh_bevel(BMesh *bm, } if (bp.harden_normals) { - bevel_harden_normals(bm, &bp); + bevel_harden_normals(&bp, bm); } if (bp.face_strength_mode != BEVEL_FACE_STRENGTH_NONE) { bevel_set_weighted_normal_face_strength(bm, &bp); } /* When called from operator (as opposed to modifier), bm->use_toolflags - * will be set, and we to transfer the oflags to BM_ELEM_TAGs */ + * will be set, and we need to transfer the oflags to BM_ELEM_TAGs */ if (bm->use_toolflags) { BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { if (BMO_vert_flag_test(bm, v, VERT_OUT)) { @@ -6717,6 +7466,22 @@ void BM_mesh_bevel(BMesh *bm, BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { BM_elem_flag_disable(l, BM_ELEM_LONG_TAG); } + +#ifdef DEBUG_CUSTOM_PROFILE_SAMPLE + printf("Profile spacing:\n"); + printf("Seg values:\n"); + if (bp.pro_spacing.xvals != NULL) { + for (int i = 0; i < bp.seg; i++) { + printf("(%.3f, %.3f)\n", bp.pro_spacing.xvals[i], bp.pro_spacing.yvals[i]); + } + } + if (bp.pro_spacing.seg_2 != bp.seg && bp.pro_spacing.seg_2 != 0) { + printf("Seg_2 values:\n"); + for (int i = 0; i < bp.pro_spacing.seg_2; i++) { + printf("(%0.2f, %0.2f)\n", bp.pro_spacing.xvals_2[i], bp.pro_spacing.yvals_2[i]); + } + } +#endif } /* primary free */ diff --git a/source/blender/bmesh/tools/bmesh_bevel.h b/source/blender/bmesh/tools/bmesh_bevel.h index 496be9219b7..45bebbb33ce 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.h +++ b/source/blender/bmesh/tools/bmesh_bevel.h @@ -21,6 +21,7 @@ * \ingroup bmesh */ +struct CurveProfile; struct MDeformVert; void BM_mesh_bevel(BMesh *bm, @@ -42,6 +43,8 @@ void BM_mesh_bevel(BMesh *bm, const int miter_outer, const int miter_inner, const float spread, - const float smoothresh); - + const float smoothresh, + const bool use_custom_profile, + const struct CurveProfile *custom_profile, + const int vmesh_method); #endif /* __BMESH_BEVEL_H__ */ diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 8f205173011..69229f25917 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -346,6 +346,8 @@ typedef enum { /** sphere widget (used to input a unit-vector, aka normal) */ UI_BTYPE_UNITVEC = 31 << 9, UI_BTYPE_CURVE = 32 << 9, + /** Profile editing widget */ + UI_BTYPE_CURVEPROFILE = 33 << 9, UI_BTYPE_LISTBOX = 36 << 9, UI_BTYPE_LISTROW = 37 << 9, UI_BTYPE_HSVCIRCLE = 38 << 9, @@ -1973,6 +1975,7 @@ void uiTemplateCurveMapping(uiLayout *layout, bool brush, bool neg_slope, bool tone); +void uiTemplateCurveProfile(uiLayout *layout, struct PointerRNA *ptr, const char *propname); void uiTemplateColorPicker(uiLayout *layout, struct PointerRNA *ptr, const char *propname, diff --git a/source/blender/editors/interface/interface_draw.c b/source/blender/editors/interface/interface_draw.c index 72c31c7b39e..499842a570b 100644 --- a/source/blender/editors/interface/interface_draw.c +++ b/source/blender/editors/interface/interface_draw.c @@ -27,16 +27,21 @@ #include "DNA_color_types.h" #include "DNA_screen_types.h" #include "DNA_movieclip_types.h" +#include "DNA_curveprofile_types.h" #include "BLI_math.h" #include "BLI_rect.h" #include "BLI_string.h" #include "BLI_utildefines.h" +#include "BLI_polyfill_2d.h" + +#include "MEM_guardedalloc.h" #include "BKE_colorband.h" #include "BKE_colortools.h" #include "BKE_node.h" #include "BKE_tracking.h" +#include "BKE_curveprofile.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -1814,6 +1819,8 @@ static void ui_draw_but_curve_grid( immVertex2f(pos, rect->xmax, fy); fy += dy; } + /* Note: Assertion fails with here when the view is moved farther below the center. + * Missing two points from the number given with immBegin. */ immEnd(); } @@ -2113,6 +2120,231 @@ void ui_draw_but_CURVE(ARegion *ar, uiBut *but, const uiWidgetColors *wcol, cons immUnbindProgram(); } +/** Used to draw a curve profile widget. Somewhat similar to ui_draw_but_CURVE */ +void ui_draw_but_CURVEPROFILE(ARegion *ar, + uiBut *but, + const uiWidgetColors *wcol, + const rcti *rect) +{ + uint i; + float fx, fy; + CurveProfile *profile; + if (but->editprofile) { + profile = but->editprofile; + } + else { + profile = (CurveProfile *)but->poin; + } + + /* Calculate offset and zoom */ + float zoomx = (BLI_rcti_size_x(rect) - 2.0f) / BLI_rctf_size_x(&profile->view_rect); + float zoomy = (BLI_rcti_size_y(rect) - 2.0f) / BLI_rctf_size_y(&profile->view_rect); + float offsx = profile->view_rect.xmin - (1.0f / zoomx); + float offsy = profile->view_rect.ymin - (1.0f / zoomy); + + /* Exit early if too narrow */ + if (zoomx == 0.0f) { + return; + } + + /* Test needed because path can draw outside of boundary */ + int scissor[4]; + GPU_scissor_get_i(scissor); + rcti scissor_new = { + .xmin = rect->xmin, + .ymin = rect->ymin, + .xmax = rect->xmax, + .ymax = rect->ymax, + }; + rcti scissor_region = {0, ar->winx, 0, ar->winy}; + BLI_rcti_isect(&scissor_new, &scissor_region, &scissor_new); + GPU_scissor(scissor_new.xmin, + scissor_new.ymin, + BLI_rcti_size_x(&scissor_new), + BLI_rcti_size_y(&scissor_new)); + + GPU_line_width(1.0f); + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + /* Backdrop */ + float color_backdrop[4] = {0, 0, 0, 1}; + if (profile->flag & PROF_USE_CLIP) { + gl_shaded_color_get_fl((uchar *)wcol->inner, -20, color_backdrop); + immUniformColor3fv(color_backdrop); + immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + immUniformColor3ubv((uchar *)wcol->inner); + immRectf(pos, + rect->xmin + zoomx * (profile->clip_rect.xmin - offsx), + rect->ymin + zoomy * (profile->clip_rect.ymin - offsy), + rect->xmin + zoomx * (profile->clip_rect.xmax - offsx), + rect->ymin + zoomy * (profile->clip_rect.ymax - offsy)); + } + else { + rgb_uchar_to_float(color_backdrop, (uchar *)wcol->inner); + immUniformColor3fv(color_backdrop); + immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + } + + /* 0.25 step grid */ + gl_shaded_color((uchar *)wcol->inner, -16); + ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.25f); + /* 1.0 step grid */ + gl_shaded_color((uchar *)wcol->inner, -24); + ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 1.0f); + + /* Draw the path's fill */ + if (profile->table == NULL) { + BKE_curveprofile_update(profile, false); + } + CurveProfilePoint *pts = profile->table; + /* Also add the last points on the right and bottom edges to close off the fill polygon */ + bool add_left_tri = profile->view_rect.xmin < 0.0f; + bool add_bottom_tri = profile->view_rect.ymin < 0.0f; + uint tot_points = (uint)PROF_N_TABLE(profile->path_len) + 1 + add_left_tri + add_bottom_tri; + uint tot_triangles = tot_points - 2; + + /* Create array of the positions of the table's points */ + float(*table_coords)[2] = MEM_mallocN(sizeof(*table_coords) * tot_points, "table x coords"); + for (i = 0; i < (uint)PROF_N_TABLE(profile->path_len); + i++) { /* Only add the points from the table here */ + table_coords[i][0] = pts[i].x; + table_coords[i][1] = pts[i].y; + } + if (add_left_tri && add_bottom_tri) { + /* Add left side, bottom left corner, and bottom side points */ + table_coords[tot_points - 3][0] = profile->view_rect.xmin; + table_coords[tot_points - 3][1] = 1.0f; + table_coords[tot_points - 2][0] = profile->view_rect.xmin; + table_coords[tot_points - 2][1] = profile->view_rect.ymin; + table_coords[tot_points - 1][0] = 1.0f; + table_coords[tot_points - 1][1] = profile->view_rect.ymin; + } + else if (add_left_tri) { + /* Add the left side and bottom left corner points */ + table_coords[tot_points - 2][0] = profile->view_rect.xmin; + table_coords[tot_points - 2][1] = 1.0f; + table_coords[tot_points - 1][0] = profile->view_rect.xmin; + table_coords[tot_points - 1][1] = 0.0f; + } + else if (add_bottom_tri) { + /* Add the bottom side and bottom left corner points */ + table_coords[tot_points - 2][0] = 0.0f; + table_coords[tot_points - 2][1] = profile->view_rect.ymin; + table_coords[tot_points - 1][0] = 1.0f; + table_coords[tot_points - 1][1] = profile->view_rect.ymin; + } + else { + /* Just add the bottom corner point. Side points would be redundant anyway */ + table_coords[tot_points - 1][0] = 0.0f; + table_coords[tot_points - 1][1] = 0.0f; + } + + /* Calculate the table point indices of the triangles for the profile's fill */ + uint(*tri_indices)[3] = MEM_mallocN(sizeof(*tri_indices) * tot_triangles, "return tri indices"); + BLI_polyfill_calc(table_coords, tot_points, -1, tri_indices); + + /* Draw the triangles for the profile fill */ + immUniformColor3ubvAlpha((const uchar *)wcol->item, 128); + GPU_blend(true); + GPU_polygon_smooth(false); + immBegin(GPU_PRIM_TRIS, 3 * tot_triangles); + for (i = 0; i < tot_triangles; i++) { + for (uint j = 0; j < 3; j++) { + uint *tri = tri_indices[i]; + fx = rect->xmin + zoomx * (table_coords[tri[j]][0] - offsx); + fy = rect->ymin + zoomy * (table_coords[tri[j]][1] - offsy); + immVertex2f(pos, fx, fy); + } + } + immEnd(); + MEM_freeN(tri_indices); + + /* Draw the profile's path so the edge stands out a bit */ + tot_points -= (add_left_tri + add_left_tri); + GPU_line_width(1.0f); + immUniformColor3ubvAlpha((const uchar *)wcol->item, 255); + GPU_line_smooth(true); + immBegin(GPU_PRIM_LINE_STRIP, tot_points - 1); + for (i = 0; i < tot_points - 1; i++) { + fx = rect->xmin + zoomx * (table_coords[i][0] - offsx); + fy = rect->ymin + zoomy * (table_coords[i][1] - offsy); + immVertex2f(pos, fx, fy); + } + immEnd(); + immUnbindProgram(); + MEM_freeN(table_coords); + + /* New GPU instructions for control points and sampled points. */ + format = immVertexFormat(); + pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + + /* Calculate vertex colors based on text theme. */ + float color_vert[4], color_vert_select[4], color_sample[4]; + UI_GetThemeColor4fv(TH_TEXT_HI, color_vert); + UI_GetThemeColor4fv(TH_TEXT, color_vert_select); + color_sample[0] = (float)wcol->item[0] / 255.0f; + color_sample[1] = (float)wcol->item[1] / 255.0f; + color_sample[2] = (float)wcol->item[2] / 255.0f; + color_sample[3] = (float)wcol->item[3] / 255.0f; + if (len_squared_v3v3(color_vert, color_vert_select) < 0.1f) { + interp_v3_v3v3(color_vert, color_vert_select, color_backdrop, 0.75f); + } + if (len_squared_v3(color_vert) > len_squared_v3(color_vert_select)) { + /* Ensure brightest text color is used for selection. */ + swap_v3_v3(color_vert, color_vert_select); + } + + /* Draw the control points. */ + pts = profile->path; + tot_points = (uint)profile->path_len; + GPU_line_smooth(false); + GPU_blend(false); + GPU_point_size(max_ff(3.0f, min_ff(UI_DPI_FAC / but->block->aspect * 5.0f, 5.0f))); + immBegin(GPU_PRIM_POINTS, tot_points); + for (i = 0; i < tot_points; i++) { + fx = rect->xmin + zoomx * (pts[i].x - offsx); + fy = rect->ymin + zoomy * (pts[i].y - offsy); + immAttr4fv(col, (pts[i].flag & PROF_SELECT) ? color_vert_select : color_vert); + immVertex2f(pos, fx, fy); + } + immEnd(); + + /* Draw the sampled points in addition to the control points if they have been created */ + pts = profile->segments; + tot_points = (uint)profile->segments_len; + if (tot_points > 0 && pts) { + GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 3.0f, 3.0f))); + immBegin(GPU_PRIM_POINTS, tot_points); + for (i = 0; i < tot_points; i++) { + fx = rect->xmin + zoomx * (pts[i].x - offsx); + fy = rect->ymin + zoomy * (pts[i].y - offsy); + immAttr4fv(col, color_sample); + immVertex2f(pos, fx, fy); + } + immEnd(); + } + + immUnbindProgram(); + + /* restore scissortest */ + GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); + + /* Outline */ + format = immVertexFormat(); + pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor3ubv((const uchar *)wcol->outline); + imm_draw_box_wire_2d(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + + immUnbindProgram(); +} + void ui_draw_but_TRACKPREVIEW(ARegion *UNUSED(ar), uiBut *but, const uiWidgetColors *UNUSED(wcol), diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 806b5789df1..673e4c1a8da 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -32,7 +32,7 @@ #include "MEM_guardedalloc.h" #include "DNA_brush_types.h" - +#include "DNA_curveprofile_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" @@ -57,6 +57,7 @@ #include "BKE_tracking.h" #include "BKE_unit.h" #include "BKE_paint.h" +#include "BKE_curveprofile.h" #include "IMB_colormanagement.h" @@ -442,6 +443,8 @@ static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut static ColorBand but_copypaste_coba = {0}; static CurveMapping but_copypaste_curve = {0}; static bool but_copypaste_curve_alive = false; +static CurveProfile but_copypaste_profile = {0}; +static bool but_copypaste_profile_alive = false; /** \} */ @@ -1080,6 +1083,13 @@ static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data data->applied = true; } +static void ui_apply_but_CURVEPROFILE(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -1979,6 +1989,7 @@ static void ui_apply_but( float *editvec; ColorBand *editcoba; CurveMapping *editcumap; + CurveProfile *editprofile; data->retval = 0; @@ -2036,11 +2047,13 @@ static void ui_apply_but( editvec = but->editvec; editcoba = but->editcoba; editcumap = but->editcumap; + editprofile = but->editprofile; but->editstr = NULL; but->editval = NULL; but->editvec = NULL; but->editcoba = NULL; but->editcumap = NULL; + but->editprofile = NULL; /* handle different types */ switch (but->type) { @@ -2100,6 +2113,9 @@ static void ui_apply_but( case UI_BTYPE_CURVE: ui_apply_but_CURVE(C, but, data); break; + case UI_BTYPE_CURVEPROFILE: + ui_apply_but_CURVEPROFILE(C, but, data); + break; case UI_BTYPE_KEY_EVENT: case UI_BTYPE_HOTKEY_EVENT: ui_apply_but_BUT(C, but, data); @@ -2147,6 +2163,7 @@ static void ui_apply_but( but->editvec = editvec; but->editcoba = editcoba; but->editcumap = editcumap; + but->editprofile = editprofile; } /** \} */ @@ -2446,6 +2463,29 @@ static void ui_but_paste_curvemapping(bContext *C, uiBut *but) } } +static void ui_but_copy_CurveProfile(uiBut *but) +{ + if (but->poin != NULL) { + but_copypaste_profile_alive = true; + BKE_curveprofile_free_data(&but_copypaste_profile); + BKE_curveprofile_copy_data(&but_copypaste_profile, (CurveProfile *)but->poin); + } +} + +static void ui_but_paste_CurveProfile(bContext *C, uiBut *but) +{ + if (but_copypaste_profile_alive) { + if (!but->poin) { + but->poin = MEM_callocN(sizeof(CurveProfile), "CurveProfile"); + } + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + BKE_curveprofile_free_data((CurveProfile *)but->poin); + BKE_curveprofile_copy_data((CurveProfile *)but->poin, &but_copypaste_profile); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } +} + static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_len_max) { PointerRNA *opptr; @@ -2540,6 +2580,10 @@ static void ui_but_copy(bContext *C, uiBut *but, const bool copy_array) ui_but_copy_curvemapping(but); break; + case UI_BTYPE_CURVEPROFILE: + ui_but_copy_CurveProfile(but); + break; + case UI_BTYPE_BUT: if (!but->optype) { break; @@ -2623,6 +2667,10 @@ static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, cons ui_but_paste_curvemapping(C, but); break; + case UI_BTYPE_CURVEPROFILE: + ui_but_paste_CurveProfile(C, but); + break; + default: break; } @@ -2633,6 +2681,7 @@ static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, cons void ui_but_clipboard_free(void) { BKE_curvemapping_free_data(&but_copypaste_curve); + BKE_curveprofile_free_data(&but_copypaste_profile); } /** \} */ @@ -3757,6 +3806,9 @@ static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) if (but->type == UI_BTYPE_CURVE) { but->editcumap = (CurveMapping *)but->poin; } + if (but->type == UI_BTYPE_CURVEPROFILE) { + but->editprofile = (CurveProfile *)but->poin; + } else if (but->type == UI_BTYPE_COLORBAND) { data->coba = (ColorBand *)but->poin; but->editcoba = data->coba; @@ -3847,6 +3899,7 @@ static void ui_numedit_end(uiBut *but, uiHandleButtonData *data) but->editvec = NULL; but->editcoba = NULL; but->editcumap = NULL; + but->editprofile = NULL; data->dragstartx = 0; data->draglastx = 0; @@ -6803,6 +6856,281 @@ static int ui_do_but_CURVE( return WM_UI_HANDLER_CONTINUE; } +/* Same as ui_numedit_but_CURVE with some smaller changes. */ +static bool ui_numedit_but_CURVEPROFILE(uiBlock *block, + uiBut *but, + uiHandleButtonData *data, + int evtx, + int evty, + bool snap, + const bool shift) +{ + CurveProfile *profile = (CurveProfile *)but->poin; + CurveProfilePoint *pts = profile->path; + float fx, fy, zoomx, zoomy; + int mx, my, dragx, dragy; + int a; + bool changed = false; + + /* evtx evty and drag coords are absolute mousecoords, + * prevents errors when editing when layout changes */ + mx = evtx; + my = evty; + ui_window_to_block(data->region, block, &mx, &my); + dragx = data->draglastx; + dragy = data->draglasty; + ui_window_to_block(data->region, block, &dragx, &dragy); + + zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&profile->view_rect); + zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&profile->view_rect); + + if (snap) { + float d[2]; + + d[0] = mx - data->dragstartx; + d[1] = my - data->dragstarty; + + if (len_squared_v2(d) < (3.0f * 3.0f)) { + snap = false; + } + } + + fx = (mx - dragx) / zoomx; + fy = (my - dragy) / zoomy; + + if (data->dragsel != -1) { + CurveProfilePoint *point_last = NULL; + const float mval_factor = ui_mouse_scale_warp_factor(shift); + bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */ + + fx *= mval_factor; + fy *= mval_factor; + + /* Move all the points that aren't the last or the first */ + for (a = 1; a < profile->path_len - 1; a++) { + if (pts[a].flag & PROF_SELECT) { + float origx = pts[a].x, origy = pts[a].y; + pts[a].x += fx; + pts[a].y += fy; + if (snap) { + pts[a].x = 0.125f * roundf(8.0f * pts[a].x); + pts[a].y = 0.125f * roundf(8.0f * pts[a].y); + } + if (!moved_point && (pts[a].x != origx || pts[a].y != origy)) { + moved_point = true; + } + + point_last = &pts[a]; + } + } + + BKE_curveprofile_update(profile, false); + + if (moved_point) { + data->draglastx = evtx; + data->draglasty = evty; + changed = true; +#ifdef USE_CONT_MOUSE_CORRECT + /* note: using 'cmp_last' is weak since there may be multiple points selected, + * but in practice this isnt really an issue */ + if (ui_but_is_cursor_warp(but)) { + /* OK but can go outside bounds */ + data->ungrab_mval[0] = but->rect.xmin + ((point_last->x - profile->view_rect.xmin) * zoomx); + data->ungrab_mval[1] = but->rect.ymin + ((point_last->y - profile->view_rect.ymin) * zoomy); + BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); + } +#endif + } + data->dragchange = true; /* mark for selection */ + } + else { + /* clamp for clip */ + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.xmin - fx < profile->clip_rect.xmin) { + fx = profile->view_rect.xmin - profile->clip_rect.xmin; + } + else if (profile->view_rect.xmax - fx > profile->clip_rect.xmax) { + fx = profile->view_rect.xmax - profile->clip_rect.xmax; + } + if (profile->view_rect.ymin - fy < profile->clip_rect.ymin) { + fy = profile->view_rect.ymin - profile->clip_rect.ymin; + } + else if (profile->view_rect.ymax - fy > profile->clip_rect.ymax) { + fy = profile->view_rect.ymax - profile->clip_rect.ymax; + } + } + + profile->view_rect.xmin -= fx; + profile->view_rect.ymin -= fy; + profile->view_rect.xmax -= fx; + profile->view_rect.ymax -= fy; + + data->draglastx = evtx; + data->draglasty = evty; + + changed = true; + } + + return changed; +} + +/** Interaction for curve profile widget. + * \note Uses hardcoded keys rather than the keymap. */ +static int ui_do_but_CURVEPROFILE( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int mx, my, i; + + mx = event->x; + my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + /* Move selected control points. */ + if (event->type == GKEY && event->val == KM_RELEASE) { + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + return WM_UI_HANDLER_BREAK; + } + + CurveProfile *profile = (CurveProfile *)but->poin; + + /* Delete selected control points. */ + if (event->type == XKEY && event->val == KM_RELEASE) { + BKE_curveprofile_remove_by_flag(profile, PROF_SELECT); + BKE_curveprofile_update(profile, false); + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + + /* Selecting, adding, and starting point movements. */ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + CurveProfilePoint *pts; /* Path or table. */ + const float m_xy[2] = {mx, my}; + float dist_min_sq; + int i_selected = -1; + + if (event->ctrl) { + float f_xy[2]; + BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy); + + BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]); + BKE_curveprofile_update(profile, false); + } + + /* Check for selecting of a point by finding closest point in radius. */ + dist_min_sq = SQUARE(U.dpi_fac * 14.0f); /* 14 pixels radius for selecting points. */ + pts = profile->path; + for (i = 0; i < profile->path_len; i++) { + float f_xy[2]; + BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x); + const float dist_sq = len_squared_v2v2(m_xy, f_xy); + if (dist_sq < dist_min_sq) { + i_selected = i; + dist_min_sq = dist_sq; + } + } + + /* Add a point if the click was close to the path but not a control point. */ + if (i_selected == -1) { /* No control point selected. */ + float f_xy[2], f_xy_prev[2]; + pts = profile->table; + BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[0].x); + + dist_min_sq = SQUARE(U.dpi_fac * 8.0f); /* 8 pixel radius from each table point. */ + + /* Loop through the path's high resolution table and find what's near the click. */ + for (i = 1; i <= PROF_N_TABLE(profile->path_len); i++) { + copy_v2_v2(f_xy_prev, f_xy); + BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x); + + if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) { + BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy); + + CurveProfilePoint *new_pt = BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]); + BKE_curveprofile_update(profile, false); + + /* reset pts back to the control points. */ + pts = profile->path; + + /* Get the index of the newly added point. */ + for (i = 0; i < profile->path_len; i++) { + if (&pts[i] == new_pt) { + i_selected = i; + } + } + break; + } + } + } + + /* Change the flag for the point(s) if one was selected. */ + if (i_selected != -1) { + /* Deselect all if this one is deselected, except if we hold shift. */ + if (!event->shift) { + for (i = 0; i < profile->path_len; i++) { + pts[i].flag &= ~PROF_SELECT; + } + pts[i_selected].flag |= PROF_SELECT; + } + else { + pts[i_selected].flag ^= PROF_SELECT; + } + } + else { + /* Move the view. */ + data->cancel = true; + } + + data->dragsel = i_selected; + + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { /* Do control point movement. */ + if (event->type == MOUSEMOVE) { + if (mx != data->draglastx || my != data->draglasty) { + if (ui_numedit_but_CURVEPROFILE( + block, but, data, mx, my, event->ctrl != 0, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + /* Finish move. */ + if (data->dragsel != -1) { + CurveProfilePoint *pts = profile->path; + + if (data->dragchange == false) { + /* Deselect all, select one. */ + if (!event->shift) { + for (i = 0; i < profile->path_len; i++) { + pts[i].flag &= ~PROF_SELECT; + } + pts[data->dragsel].flag |= PROF_SELECT; + } + } + else { + BKE_curveprofile_update(profile, true); /* Remove doubles after move. */ + } + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int mx, int my) { Histogram *hist = (Histogram *)but->poin; @@ -7208,6 +7536,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * case UI_BTYPE_CURVE: retval = ui_do_but_CURVE(C, block, but, data, event); break; + case UI_BTYPE_CURVEPROFILE: + retval = ui_do_but_CURVEPROFILE(C, block, but, data, event); + break; case UI_BTYPE_HSVCUBE: retval = ui_do_but_HSVCUBE(C, block, but, data, event); break; @@ -7599,7 +7930,7 @@ static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonA copy_v2_fl(data->ungrab_mval, FLT_MAX); #endif - if (ELEM(but->type, UI_BTYPE_CURVE, UI_BTYPE_SEARCH_MENU)) { + if (ELEM(but->type, UI_BTYPE_CURVE, UI_BTYPE_CURVEPROFILE, UI_BTYPE_SEARCH_MENU)) { /* XXX curve is temp */ } else { diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 81979b1fc8f..3fe2750e070 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -274,6 +274,7 @@ struct uiBut { float *editvec; void *editcoba; void *editcumap; + void *editprofile; uiButPushedStateFunc pushed_state_func; void *pushed_state_arg; @@ -740,6 +741,10 @@ void ui_draw_but_CURVE(ARegion *ar, uiBut *but, const struct uiWidgetColors *wcol, const rcti *rect); +void ui_draw_but_CURVEPROFILE(ARegion *ar, + uiBut *but, + const struct uiWidgetColors *wcol, + const rcti *rect); void ui_draw_but_IMAGE(ARegion *ar, uiBut *but, const struct uiWidgetColors *wcol, diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c index 34b1070f8b4..ab20b5ac520 100644 --- a/source/blender/editors/interface/interface_query.c +++ b/source/blender/editors/interface/interface_query.c @@ -422,7 +422,8 @@ bool ui_but_is_cursor_warp(const uiBut *but) UI_BTYPE_HSVCIRCLE, UI_BTYPE_TRACK_PREVIEW, UI_BTYPE_HSVCUBE, - UI_BTYPE_CURVE)) { + UI_BTYPE_CURVE, + UI_BTYPE_CURVEPROFILE)) { return true; } } diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 021d7733fae..b205572ba06 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -35,6 +35,7 @@ #include "DNA_texture_types.h" #include "DNA_gpencil_modifier_types.h" #include "DNA_shader_fx_types.h" +#include "DNA_curveprofile_types.h" #include "BLI_utildefines.h" #include "BLI_alloca.h" @@ -69,6 +70,7 @@ #include "BKE_packedFile.h" #include "BKE_paint.h" #include "BKE_particle.h" +#include "BKE_curveprofile.h" #include "BKE_report.h" #include "BKE_screen.h" #include "BKE_shader_fx.h" @@ -4505,6 +4507,612 @@ void uiTemplateCurveMapping(uiLayout *layout, /** \} */ /* -------------------------------------------------------------------- */ +/** \name Curve Profile Template + * \{ */ + +static void CurveProfile_presets_dofunc(bContext *C, void *profile_v, int event) +{ + CurveProfile *profile = profile_v; + + profile->preset = event; + BKE_curveprofile_reset(profile); + BKE_curveprofile_update(profile, false); + + ED_undo_push(C, "CurveProfile tools"); + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static uiBlock *CurveProfile_presets_func(bContext *C, ARegion *ar, CurveProfile *profile) +{ + uiBlock *block; + short yco = 0; + short menuwidth = 12 * UI_UNIT_X; + menuwidth = 0; + + block = UI_block_begin(C, ar, __func__, UI_EMBOSS); + UI_block_func_butmenu_set(block, CurveProfile_presets_dofunc, profile); + + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Default"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_LINE, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Support Loops"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_SUPPORTS, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Cornice Moulding"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_CORNICE, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Crown Moulding"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_CROWN, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Steps"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_STEPS, + ""); + + UI_block_direction_set(block, UI_DIR_DOWN); + UI_block_bounds_set_text(block, (int)(3.0f * UI_UNIT_X)); + + return block; +} + +static uiBlock *CurveProfile_buttons_presets(bContext *C, ARegion *ar, void *profile_v) +{ + return CurveProfile_presets_func(C, ar, (CurveProfile *)profile_v); +} + +/* Only for CurveProfile tools block */ +enum { + UIPROFILE_FUNC_RESET, + UIPROFILE_FUNC_RESET_VIEW, +}; + +static void CurveProfile_tools_dofunc(bContext *C, void *profile_v, int event) +{ + CurveProfile *profile = profile_v; + + switch (event) { + case UIPROFILE_FUNC_RESET: /* reset */ + BKE_curveprofile_reset(profile); + BKE_curveprofile_update(profile, false); + break; + case UIPROFILE_FUNC_RESET_VIEW: /* reset view to clipping rect */ + profile->view_rect = profile->clip_rect; + break; + } + ED_undo_push(C, "CurveProfile tools"); + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static uiBlock *CurveProfile_tools_func(bContext *C, ARegion *ar, CurveProfile *profile) +{ + uiBlock *block; + short yco = 0; + short menuwidth = 10 * UI_UNIT_X; + + block = UI_block_begin(C, ar, __func__, UI_EMBOSS); + UI_block_func_butmenu_set(block, CurveProfile_tools_dofunc, profile); + + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Reset View"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UIPROFILE_FUNC_RESET_VIEW, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Reset Curve"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UIPROFILE_FUNC_RESET, + ""); + + UI_block_direction_set(block, UI_DIR_DOWN); + UI_block_bounds_set_text(block, (int)(3.0f * UI_UNIT_X)); + + return block; +} + +static uiBlock *CurveProfile_buttons_tools(bContext *C, ARegion *ar, void *profile_v) +{ + return CurveProfile_tools_func(C, ar, (CurveProfile *)profile_v); +} + +static void CurveProfile_buttons_zoom_in(bContext *C, void *profile_v, void *UNUSED(arg)) +{ + CurveProfile *profile = profile_v; + float d; + + /* we allow 20 times zoom */ + if (BLI_rctf_size_x(&profile->view_rect) > 0.04f * BLI_rctf_size_x(&profile->clip_rect)) { + d = 0.1154f * BLI_rctf_size_x(&profile->view_rect); + profile->view_rect.xmin += d; + profile->view_rect.xmax -= d; + d = 0.1154f * BLI_rctf_size_y(&profile->view_rect); + profile->view_rect.ymin += d; + profile->view_rect.ymax -= d; + } + + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static void CurveProfile_buttons_zoom_out(bContext *C, void *profile_v, void *UNUSED(arg)) +{ + CurveProfile *profile = profile_v; + float d, d1; + + /* Allow 20 times zoom, but don't view outside clip */ + if (BLI_rctf_size_x(&profile->view_rect) < 20.0f * BLI_rctf_size_x(&profile->clip_rect)) { + d = d1 = 0.15f * BLI_rctf_size_x(&profile->view_rect); + + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.xmin - d < profile->clip_rect.xmin) { + d1 = profile->view_rect.xmin - profile->clip_rect.xmin; + } + } + profile->view_rect.xmin -= d1; + + d1 = d; + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.xmax + d > profile->clip_rect.xmax) { + d1 = -profile->view_rect.xmax + profile->clip_rect.xmax; + } + } + profile->view_rect.xmax += d1; + + d = d1 = 0.15f * BLI_rctf_size_y(&profile->view_rect); + + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.ymin - d < profile->clip_rect.ymin) { + d1 = profile->view_rect.ymin - profile->clip_rect.ymin; + } + } + profile->view_rect.ymin -= d1; + + d1 = d; + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.ymax + d > profile->clip_rect.ymax) { + d1 = -profile->view_rect.ymax + profile->clip_rect.ymax; + } + } + profile->view_rect.ymax += d1; + } + + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static void CurveProfile_clipping_toggle(bContext *C, void *cb_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + + profile->flag ^= PROF_USE_CLIP; + + BKE_curveprofile_update(profile, false); + rna_update_cb(C, cb_v, NULL); +} + +static void CurveProfile_buttons_reverse(bContext *C, void *cb_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + + BKE_curveprofile_reverse(profile); + BKE_curveprofile_update(profile, false); + rna_update_cb(C, cb_v, NULL); +} + +static void CurveProfile_buttons_delete(bContext *C, void *cb_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + + BKE_curveprofile_remove_by_flag(profile, SELECT); + BKE_curveprofile_update(profile, false); + + rna_update_cb(C, cb_v, NULL); +} + +static void CurveProfile_buttons_setsharp(bContext *C, void *cb_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + + BKE_curveprofile_selected_handle_set(profile, HD_VECT, HD_VECT); + BKE_curveprofile_update(profile, false); + + rna_update_cb(C, cb_v, NULL); +} + +static void CurveProfile_buttons_setcurved(bContext *C, void *cb_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + + BKE_curveprofile_selected_handle_set(profile, HD_AUTO, HD_AUTO); + BKE_curveprofile_update(profile, false); + + rna_update_cb(C, cb_v, NULL); +} + +static void CurveProfile_buttons_update(bContext *C, void *arg1_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + BKE_curveprofile_update(profile, true); + rna_update_cb(C, arg1_v, NULL); +} + +static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUpdateCb *cb) +{ + CurveProfile *profile = ptr->data; + CurveProfilePoint *point = NULL; + uiLayout *row, *sub; + uiBlock *block; + uiBut *bt; + int i, icon, path_width, path_height; + bool point_last_or_first = false; + rctf bounds; + + block = uiLayoutGetBlock(layout); + + UI_block_emboss_set(block, UI_EMBOSS); + + uiLayoutRow(layout, false); + + /* Preset selector */ + /* There is probably potential to use simpler "uiItemR" functions here, but automatic updating + * after a preset is selected would be more complicated. */ + bt = uiDefBlockBut( + block, CurveProfile_buttons_presets, profile, "Preset", 0, 0, UI_UNIT_X, UI_UNIT_X, ""); + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + + row = uiLayoutRow(layout, false); + + /* (Left aligned) */ + sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); + + /* Zoom in */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_ZOOM_IN, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Zoom in")); + UI_but_func_set(bt, CurveProfile_buttons_zoom_in, profile, NULL); + + /* Zoom out */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_ZOOM_OUT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Zoom out")); + UI_but_func_set(bt, CurveProfile_buttons_zoom_out, profile, NULL); + + /* (Right aligned) */ + sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_RIGHT); + + /* Reset view, reset curve */ + bt = uiDefIconBlockBut( + block, CurveProfile_buttons_tools, profile, 0, 0, 0, 0, UI_UNIT_X, UI_UNIT_X, TIP_("Tools")); + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + + /* Flip path */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_ARROW_LEFTRIGHT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Reverse Path")); + UI_but_funcN_set(bt, CurveProfile_buttons_reverse, MEM_dupallocN(cb), profile); + + /* Clipping toggle */ + icon = (profile->flag & PROF_USE_CLIP) ? ICON_CLIPUV_HLT : ICON_CLIPUV_DEHLT; + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + icon, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Toggle Profile Clipping")); + UI_but_funcN_set(bt, CurveProfile_clipping_toggle, MEM_dupallocN(cb), profile); + + UI_block_funcN_set(block, rna_update_cb, MEM_dupallocN(cb), NULL); + + /* The path itself */ + path_width = max_ii(uiLayoutGetWidth(layout), UI_UNIT_X); + path_width = min_ii(path_width, (int)(16.0f * UI_UNIT_X)); + path_height = path_width; + uiLayoutRow(layout, false); + uiDefBut(block, + UI_BTYPE_CURVEPROFILE, + 0, + "", + 0, + 0, + (short)path_width, + (short)path_height, + profile, + 0.0f, + 1.0f, + -1, + 0, + ""); + + /* Position sliders for (first) selected point */ + for (i = 0; i < profile->path_len; i++) { + if (profile->path[i].flag & PROF_SELECT) { + point = &profile->path[i]; + break; + } + } + if (i == 0 || i == profile->path_len - 1) { + point_last_or_first = true; + } + + /* Selected point data */ + if (point) { + if (profile->flag & PROF_USE_CLIP) { + bounds = profile->clip_rect; + } + else { + bounds.xmin = bounds.ymin = -1000.0; + bounds.xmax = bounds.ymax = 1000.0; + } + + uiLayoutRow(layout, true); + UI_block_funcN_set(block, CurveProfile_buttons_update, MEM_dupallocN(cb), profile); + + /* Sharp / Smooth */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_LINCURVE, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Set the point's handle type to sharp.")); + if (point_last_or_first) { + UI_but_flag_enable(bt, UI_BUT_DISABLED); + } + UI_but_funcN_set(bt, CurveProfile_buttons_setsharp, MEM_dupallocN(cb), profile); + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_SMOOTHCURVE, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Set the point's handle type to sharp.")); + UI_but_funcN_set(bt, CurveProfile_buttons_setcurved, MEM_dupallocN(cb), profile); + if (point_last_or_first) { + UI_but_flag_enable(bt, UI_BUT_DISABLED); + } + + /* Position */ + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + "X:", + 0, + 2 * UI_UNIT_Y, + UI_UNIT_X * 10, + UI_UNIT_Y, + &point->x, + bounds.xmin, + bounds.xmax, + 1, + 5, + ""); + if (point_last_or_first) { + UI_but_flag_enable(bt, UI_BUT_DISABLED); + } + + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + "Y:", + 0, + 1 * UI_UNIT_Y, + UI_UNIT_X * 10, + UI_UNIT_Y, + &point->y, + bounds.ymin, + bounds.ymax, + 1, + 5, + ""); + if (point_last_or_first) { + UI_but_flag_enable(bt, UI_BUT_DISABLED); + } + + /* Delete points */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_X, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Delete points")); + UI_but_funcN_set(bt, CurveProfile_buttons_delete, MEM_dupallocN(cb), profile); + if (point_last_or_first) { + UI_but_flag_enable(bt, UI_BUT_DISABLED); + } + } + + uiItemR(layout, ptr, "use_sample_straight_edges", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "use_sample_even_lengths", 0, NULL, ICON_NONE); + + UI_block_funcN_set(block, NULL, NULL, NULL); +} + +/** Template for a path creation widget intended for custom bevel profiles. + * This section is quite similar to uiTemplateCurveMapping, but with reduced complexity */ +void uiTemplateCurveProfile(uiLayout *layout, PointerRNA *ptr, const char *propname) +{ + RNAUpdateCb *cb; + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + PointerRNA cptr; + ID *id; + uiBlock *block = uiLayoutGetBlock(layout); + + if (!prop) { + RNA_warning( + "Curve Profile property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + if (RNA_property_type(prop) != PROP_POINTER) { + RNA_warning( + "Curve Profile is not a pointer: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + cptr = RNA_property_pointer_get(ptr, prop); + if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_CurveProfile)) { + return; + } + + /* Share update functionality with the CurveMapping widget template. */ + cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb"); + cb->ptr = *ptr; + cb->prop = prop; + + id = cptr.owner_id; + UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); + + CurveProfile_buttons_layout(layout, &cptr, cb); + + UI_block_lock_clear(block); + + MEM_freeN(cb); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name ColorPicker Template * \{ */ diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index 4cef9ace66e..7f7352517c8 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -4774,6 +4774,10 @@ void ui_draw_but(const bContext *C, ARegion *ar, uiStyle *style, uiBut *but, rct ui_draw_but_CURVE(ar, but, &tui->wcol_regular, rect); break; + case UI_BTYPE_CURVEPROFILE: + ui_draw_but_CURVEPROFILE(ar, but, &tui->wcol_regular, rect); + break; + case UI_BTYPE_PROGRESS_BAR: wt = widget_type(UI_WTYPE_PROGRESSBAR); fstyle = &style->widgetlabel; diff --git a/source/blender/editors/mesh/editmesh_bevel.c b/source/blender/editors/mesh/editmesh_bevel.c index 7afd72f33c9..acdf667b410 100644 --- a/source/blender/editors/mesh/editmesh_bevel.c +++ b/source/blender/editors/mesh/editmesh_bevel.c @@ -33,8 +33,10 @@ #include "BKE_unit.h" #include "BKE_layer.h" #include "BKE_mesh.h" +#include "BKE_curveprofile.h" #include "DNA_mesh_types.h" +#include "DNA_curveprofile_types.h" #include "RNA_define.h" #include "RNA_access.h" @@ -43,6 +45,7 @@ #include "WM_types.h" #include "UI_interface.h" +#include "UI_resources.h" #include "ED_mesh.h" #include "ED_numinput.h" @@ -96,6 +99,8 @@ typedef struct { short gizmo_flag; short value_mode; /* Which value does mouse movement and numeric input affect? */ float segments; /* Segments as float so smooth mouse pan works in small increments */ + + CurveProfile *custom_profile; } BevelData; enum { @@ -114,6 +119,8 @@ enum { BEV_MODAL_MARK_SHARP_TOGGLE, BEV_MODAL_OUTER_MITER_CHANGE, BEV_MODAL_INNER_MITER_CHANGE, + BEV_MODAL_CUSTOM_PROFILE_TOGGLE, + BEV_MODAL_VERTEX_MESH_CHANGE, }; static float get_bevel_offset(wmOperator *op) @@ -129,15 +136,15 @@ static float get_bevel_offset(wmOperator *op) return val; } -static void edbm_bevel_update_header(bContext *C, wmOperator *op) +static void edbm_bevel_update_status_text(bContext *C, wmOperator *op) { - char header[UI_MAX_DRAW_STR]; + char status_text[UI_MAX_DRAW_STR]; char buf[UI_MAX_DRAW_STR]; char *p = buf; int available_len = sizeof(buf); Scene *sce = CTX_data_scene(C); char offset_str[NUM_STR_REP_LEN]; - const char *mode_str, *omiter_str, *imiter_str; + const char *mode_str, *omiter_str, *imiter_str, *vmesh_str; PropertyRNA *prop; #define WM_MODALKEY(_id) \ @@ -167,22 +174,27 @@ static void edbm_bevel_update_header(bContext *C, wmOperator *op) prop = RNA_struct_find_property(op->ptr, "miter_inner"); RNA_property_enum_name_gettexted( C, op->ptr, prop, RNA_property_enum_get(op->ptr, prop), &imiter_str); - - BLI_snprintf(header, - sizeof(header), - TIP_("%s: confirm, " - "%s: cancel, " - "%s: mode (%s), " - "%s: width (%s), " - "%s: segments (%d), " - "%s: profile (%.3f), " - "%s: clamp overlap (%s), " - "%s: vertex only (%s), " - "%s: outer miter (%s), " - "%s: inner miter (%s), " - "%s: harden normals (%s), " - "%s: mark seam (%s), " - "%s: mark sharp (%s)"), + prop = RNA_struct_find_property(op->ptr, "vmesh_method"); + RNA_property_enum_name_gettexted( + C, op->ptr, prop, RNA_property_enum_get(op->ptr, prop), &vmesh_str); + + BLI_snprintf(status_text, + sizeof(status_text), + TIP_("%s: Confirm, " + "%s: Cancel, " + "%s: Mode (%s), " + "%s: Width (%s), " + "%s: Segments (%d), " + "%s: Profile (%.3f), " + "%s: Clamp Overlap (%s), " + "%s: Vertex Only (%s), " + "%s: Outer Miter (%s), " + "%s: Inner Miter (%s), " + "%s: Harden Normals (%s), " + "%s: Mark Seam (%s), " + "%s: Mark Sharp (%s), " + "%s: Custom Profile (%s), " + "%s: Intersection (%s)"), WM_MODALKEY(BEV_MODAL_CONFIRM), WM_MODALKEY(BEV_MODAL_CANCEL), WM_MODALKEY(BEV_MODAL_OFFSET_MODE_CHANGE), @@ -206,16 +218,21 @@ static void edbm_bevel_update_header(bContext *C, wmOperator *op) WM_MODALKEY(BEV_MODAL_MARK_SEAM_TOGGLE), WM_bool_as_string(RNA_boolean_get(op->ptr, "mark_seam")), WM_MODALKEY(BEV_MODAL_MARK_SHARP_TOGGLE), - WM_bool_as_string(RNA_boolean_get(op->ptr, "mark_sharp"))); + WM_bool_as_string(RNA_boolean_get(op->ptr, "mark_sharp")), + WM_MODALKEY(BEV_MODAL_CUSTOM_PROFILE_TOGGLE), + WM_bool_as_string(RNA_boolean_get(op->ptr, "use_custom_profile")), + WM_MODALKEY(BEV_MODAL_VERTEX_MESH_CHANGE), + vmesh_str); #undef WM_MODALKEY - ED_workspace_status_text(C, header); + ED_workspace_status_text(C, status_text); } static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal) { Scene *scene = CTX_data_scene(C); + ToolSettings *ts = CTX_data_tool_settings(C); BevelData *opdata; ViewLayer *view_layer = CTX_data_view_layer(C); float pixels_per_inch; @@ -230,6 +247,9 @@ static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal) uint objects_used_len = 0; opdata->max_obj_scale = FLT_MIN; + /* Put the Curve Profile from the toolsettings into the opdata struct */ + opdata->custom_profile = ts->custom_bevel_profile_preset; + { uint ob_store_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( @@ -318,6 +338,8 @@ static bool edbm_bevel_calc(wmOperator *op) const int miter_outer = RNA_enum_get(op->ptr, "miter_outer"); const int miter_inner = RNA_enum_get(op->ptr, "miter_inner"); const float spread = RNA_float_get(op->ptr, "spread"); + const bool use_custom_profile = RNA_boolean_get(op->ptr, "use_custom_profile"); + const int vmesh_method = RNA_enum_get(op->ptr, "vmesh_method"); for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) { em = opdata->ob_store[ob_index].em; @@ -344,7 +366,8 @@ static bool edbm_bevel_calc(wmOperator *op) "bevel geom=%hev offset=%f segments=%i vertex_only=%b offset_type=%i profile=%f " "clamp_overlap=%b material=%i loop_slide=%b mark_seam=%b mark_sharp=%b " "harden_normals=%b face_strength_mode=%i " - "miter_outer=%i miter_inner=%i spread=%f smoothresh=%f", + "miter_outer=%i miter_inner=%i spread=%f smoothresh=%f use_custom_profile=%b " + "custom_profile=%p vmesh_method=%i", BM_ELEM_SELECT, offset, segments, @@ -361,7 +384,10 @@ static bool edbm_bevel_calc(wmOperator *op) miter_outer, miter_inner, spread, - me->smoothresh); + me->smoothresh, + use_custom_profile, + opdata->custom_profile, + vmesh_method); BMO_op_exec(em->bm, &bmop); @@ -389,7 +415,6 @@ static bool edbm_bevel_calc(wmOperator *op) static void edbm_bevel_exit(bContext *C, wmOperator *op) { BevelData *opdata = op->customdata; - ScrArea *sa = CTX_wm_area(C); if (sa) { @@ -430,7 +455,7 @@ static void edbm_bevel_cancel(bContext *C, wmOperator *op) ED_region_tag_redraw(CTX_wm_region(C)); } -/* bevel! yay!!*/ +/* bevel! yay!! */ static int edbm_bevel_exec(bContext *C, wmOperator *op) { if (!edbm_bevel_init(C, op, false)) { @@ -500,7 +525,7 @@ static int edbm_bevel_invoke(bContext *C, wmOperator *op, const wmEvent *event) edbm_bevel_calc_initial_length(op, event, false); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); if (!edbm_bevel_calc(op)) { edbm_bevel_cancel(C, op); @@ -598,13 +623,9 @@ wmKeyMap *bevel_modal_keymap(wmKeyConfig *keyconf) static const EnumPropertyItem modal_items[] = { {BEV_MODAL_CANCEL, "CANCEL", 0, "Cancel", "Cancel bevel"}, {BEV_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", "Confirm bevel"}, - {BEV_MODAL_VALUE_OFFSET, "VALUE_OFFSET", 0, "Value is offset", "Value changes offset"}, - {BEV_MODAL_VALUE_PROFILE, "VALUE_PROFILE", 0, "Value is profile", "Value changes profile"}, - {BEV_MODAL_VALUE_SEGMENTS, - "VALUE_SEGMENTS", - 0, - "Value is segments", - "Value changes segments"}, + {BEV_MODAL_VALUE_OFFSET, "VALUE_OFFSET", 0, "Change offset", "Value changes offset"}, + {BEV_MODAL_VALUE_PROFILE, "VALUE_PROFILE", 0, "Change profile", "Value changes profile"}, + {BEV_MODAL_VALUE_SEGMENTS, "VALUE_SEGMENTS", 0, "Change segments", "Value changes segments"}, {BEV_MODAL_SEGMENTS_UP, "SEGMENTS_UP", 0, "Increase segments", "Increase segments"}, {BEV_MODAL_SEGMENTS_DOWN, "SEGMENTS_DOWN", 0, "Decrease segments", "Decrease segments"}, {BEV_MODAL_OFFSET_MODE_CHANGE, @@ -647,12 +668,18 @@ wmKeyMap *bevel_modal_keymap(wmKeyConfig *keyconf) 0, "Change inner miter", "Cycle through inner miter kinds"}, + {BEV_MODAL_CUSTOM_PROFILE_TOGGLE, "CUSTOM_PROFILE_TOGGLE", 0, "Toggle custom profile", ""}, + {BEV_MODAL_VERTEX_MESH_CHANGE, + "VERTEX_MESH_CHANGE", + 0, + "Change intersection method", + "Cycle through intersection methods"}, {0, NULL, 0, NULL, NULL}, }; wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "Bevel Modal Map"); - /* this function is called for each spacetype, only needs to add map once */ + /* This function is called for each spacetype, only needs to add map once */ if (keymap && keymap->modal_items) { return NULL; } @@ -682,14 +709,14 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) handleNumInput(C, &opdata->num_input[opdata->value_mode], event)) { edbm_bevel_numinput_set_value(op); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); return OPERATOR_RUNNING_MODAL; } else if (etype == MOUSEMOVE) { if (!has_numinput) { edbm_bevel_mouse_set_value(op, event); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; } } @@ -703,7 +730,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) } RNA_int_set(op->ptr, "segments", (int)opdata->segments); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; } else if (etype == EVT_MODAL_MAP) { @@ -723,7 +750,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) opdata->segments = opdata->segments + 1; RNA_int_set(op->ptr, "segments", (int)opdata->segments); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; @@ -731,7 +758,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) opdata->segments = max_ff(opdata->segments - 1, 1); RNA_int_set(op->ptr, "segments", (int)opdata->segments); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; @@ -758,7 +785,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) edbm_bevel_mouse_set_value(op, event); } edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; @@ -766,7 +793,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) bool clamp_overlap = RNA_boolean_get(op->ptr, "clamp_overlap"); RNA_boolean_set(op->ptr, "clamp_overlap", !clamp_overlap); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; } @@ -790,7 +817,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) bool vertex_only = RNA_boolean_get(op->ptr, "vertex_only"); RNA_boolean_set(op->ptr, "vertex_only", !vertex_only); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; } @@ -799,7 +826,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) bool mark_seam = RNA_boolean_get(op->ptr, "mark_seam"); RNA_boolean_set(op->ptr, "mark_seam", !mark_seam); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; } @@ -808,7 +835,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) bool mark_sharp = RNA_boolean_get(op->ptr, "mark_sharp"); RNA_boolean_set(op->ptr, "mark_sharp", !mark_sharp); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; } @@ -824,7 +851,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) } RNA_enum_set(op->ptr, "miter_inner", miter_inner); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; } @@ -837,7 +864,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) } RNA_enum_set(op->ptr, "miter_outer", miter_outer); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); handled = true; break; } @@ -846,7 +873,29 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) bool harden_normals = RNA_boolean_get(op->ptr, "harden_normals"); RNA_boolean_set(op->ptr, "harden_normals", !harden_normals); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); + handled = true; + break; + } + + case BEV_MODAL_CUSTOM_PROFILE_TOGGLE: { + bool use_custom_profile = RNA_boolean_get(op->ptr, "use_custom_profile"); + RNA_boolean_set(op->ptr, "use_custom_profile", !use_custom_profile); + edbm_bevel_calc(op); + edbm_bevel_update_status_text(C, op); + handled = true; + break; + } + + case BEV_MODAL_VERTEX_MESH_CHANGE: { + int vmesh_method = RNA_enum_get(op->ptr, "vmesh_method"); + vmesh_method++; + if (vmesh_method > BEVEL_VMESH_CUTOFF) { + vmesh_method = BEVEL_VMESH_ADJ; + } + RNA_enum_set(op->ptr, "vmesh_method", vmesh_method); + edbm_bevel_calc(op); + edbm_bevel_update_status_text(C, op); handled = true; break; } @@ -858,13 +907,84 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) handleNumInput(C, &opdata->num_input[opdata->value_mode], event)) { edbm_bevel_numinput_set_value(op); edbm_bevel_calc(op); - edbm_bevel_update_header(C, op); + edbm_bevel_update_status_text(C, op); return OPERATOR_RUNNING_MODAL; } return OPERATOR_RUNNING_MODAL; } +static void edbm_bevel_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + uiLayout *row, *col, *split; + PointerRNA ptr, toolsettings_ptr; + PropertyRNA *prop; + const char *offset_name; + + RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr); + + if (RNA_enum_get(&ptr, "offset_type") == BEVEL_AMT_PERCENT) { + uiItemR(layout, &ptr, "offset_pct", 0, NULL, ICON_NONE); + } + else { + switch (RNA_enum_get(&ptr, "offset_type")) { + case BEVEL_AMT_DEPTH: + offset_name = "Depth"; + break; + case BEVEL_AMT_WIDTH: + offset_name = "Width"; + break; + case BEVEL_AMT_OFFSET: + offset_name = "Offset"; + break; + } + prop = RNA_struct_find_property(op->ptr, "offset_type"); + RNA_property_enum_name_gettexted( + C, op->ptr, prop, RNA_property_enum_get(op->ptr, prop), &offset_name); + uiItemR(layout, &ptr, "offset", 0, offset_name, ICON_NONE); + } + row = uiLayoutRow(layout, true); + uiItemR(row, &ptr, "offset_type", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + + split = uiLayoutSplit(layout, 0.5f, true); + col = uiLayoutColumn(split, true); + uiItemR(col, &ptr, "vertex_only", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "clamp_overlap", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "loop_slide", 0, NULL, ICON_NONE); + col = uiLayoutColumn(split, true); + uiItemR(col, &ptr, "mark_seam", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "mark_sharp", 0, NULL, ICON_NONE); + uiItemR(col, &ptr, "harden_normals", 0, NULL, ICON_NONE); + + uiItemR(layout, &ptr, "segments", 0, NULL, ICON_NONE); + uiItemR(layout, &ptr, "profile", UI_ITEM_R_SLIDER, NULL, ICON_NONE); + uiItemR(layout, &ptr, "material", 0, NULL, ICON_NONE); + + uiItemL(layout, "Miter Type:", ICON_NONE); + uiItemR(layout, &ptr, "miter_outer", 0, "Outer", ICON_NONE); + uiItemR(layout, &ptr, "miter_inner", 0, "Inner", ICON_NONE); + if (RNA_enum_get(&ptr, "miter_inner") == BEVEL_MITER_ARC) { + uiItemR(layout, &ptr, "spread", 0, NULL, ICON_NONE); + } + + uiItemL(layout, "Face Strength Mode:", ICON_NONE); + row = uiLayoutRow(layout, true); + uiItemR(row, &ptr, "face_strength_mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + + uiItemL(layout, "Intersection Type:", ICON_NONE); + row = uiLayoutRow(layout, true); + uiItemR(row, &ptr, "vmesh_method", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + + uiItemR(layout, &ptr, "use_custom_profile", 0, NULL, ICON_NONE); + if (RNA_boolean_get(&ptr, "use_custom_profile")) { + /* Get an RNA pointer to ToolSettings to give to the curve profile template code */ + Scene *scene = CTX_data_scene(C); + RNA_pointer_create(&scene->id, &RNA_ToolSettings, scene->toolsettings, &toolsettings_ptr); + uiTemplateCurveProfile(layout, &toolsettings_ptr, "custom_bevel_profile_preset"); + } +} + void MESH_OT_bevel(wmOperatorType *ot) { PropertyRNA *prop; @@ -906,10 +1026,19 @@ void MESH_OT_bevel(wmOperatorType *ot) {0, NULL, 0, NULL, NULL}, }; + static EnumPropertyItem vmesh_method_items[] = { + {BEVEL_VMESH_ADJ, "ADJ", 0, "Grid Fill", "Default patterned fill"}, + {BEVEL_VMESH_CUTOFF, + "CUTOFF", + 0, + "Cutoff", + "A cut-off at each profile's end before the intersection"}, + {0, NULL, 0, NULL, NULL}, + }; + /* identifiers */ ot->name = "Bevel"; - ot->description = - "Cut into selected items at an angle to create flat or rounded bevel or chamfer"; + ot->description = "Cut into selected items at an angle to create bevel or chamfer"; ot->idname = "MESH_OT_bevel"; /* api callbacks */ @@ -919,19 +1048,23 @@ void MESH_OT_bevel(wmOperatorType *ot) ot->cancel = edbm_bevel_cancel; ot->poll = ED_operator_editmesh; ot->poll_property = edbm_bevel_poll_property; + ot->ui = edbm_bevel_ui; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR_XY | OPTYPE_BLOCKING; + /* properties */ RNA_def_enum( ot->srna, "offset_type", offset_type_items, 0, "Width Type", "What distance Width measures"); prop = RNA_def_property(ot->srna, "offset", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_range(prop, 0.0, 1e6); - RNA_def_property_ui_range(prop, 0.0f, 100.0, 1, 3); + RNA_def_property_ui_range(prop, 0.0, 100.0, 1, 3); RNA_def_property_ui_text(prop, "Width", "Bevel amount"); + prop = RNA_def_property(ot->srna, "offset_pct", PROP_FLOAT, PROP_PERCENTAGE); RNA_def_property_range(prop, 0.0, 100); RNA_def_property_ui_text(prop, "Width Percent", "Bevel amount for percentage method"); + RNA_def_int(ot->srna, "segments", 1, @@ -941,6 +1074,7 @@ void MESH_OT_bevel(wmOperatorType *ot) "Segments for curved edge", 1, 100); + RNA_def_float(ot->srna, "profile", 0.5f, @@ -950,16 +1084,22 @@ void MESH_OT_bevel(wmOperatorType *ot) "Controls profile shape (0.5 = round)", PROFILE_HARD_MIN, 1.0f); + RNA_def_boolean(ot->srna, "vertex_only", false, "Vertex Only", "Bevel only vertices"); + RNA_def_boolean(ot->srna, "clamp_overlap", false, "Clamp Overlap", "Do not allow beveled edges/vertices to overlap each other"); + RNA_def_boolean( - ot->srna, "loop_slide", true, "Loop Slide", "Prefer slide along edge to even widths"); + ot->srna, "loop_slide", true, "Loop Slide", "Prefer sliding along edges to even widths"); + RNA_def_boolean(ot->srna, "mark_seam", false, "Mark Seams", "Mark Seams along beveled edges"); + RNA_def_boolean(ot->srna, "mark_sharp", false, "Mark Sharp", "Mark beveled edges as sharp"); + RNA_def_int(ot->srna, "material", -1, @@ -969,29 +1109,34 @@ void MESH_OT_bevel(wmOperatorType *ot) "Material for bevel faces (-1 means use adjacent faces)", -1, 100); + RNA_def_boolean(ot->srna, "harden_normals", false, "Harden Normals", "Match normals of new faces to adjacent faces"); + RNA_def_enum(ot->srna, "face_strength_mode", face_strength_mode_items, BEVEL_FACE_STRENGTH_NONE, "Face Strength Mode", "Whether to set face strength, and which faces to set face strength on"); + RNA_def_enum(ot->srna, "miter_outer", miter_outer_items, BEVEL_MITER_SHARP, "Outer Miter", "Pattern to use for outside of miters"); + RNA_def_enum(ot->srna, "miter_inner", miter_inner_items, BEVEL_MITER_SHARP, "Inner Miter", "Pattern to use for inside of miters"); + RNA_def_float(ot->srna, "spread", 0.1f, @@ -1001,6 +1146,20 @@ void MESH_OT_bevel(wmOperatorType *ot) "Amount to spread arcs for arc inner miters", 0.0f, 100.0f); + + RNA_def_boolean(ot->srna, + "use_custom_profile", + false, + "Custom Profile", + "Use a custom profile for the bevel"); + + RNA_def_enum(ot->srna, + "vmesh_method", + vmesh_method_items, + BEVEL_VMESH_ADJ, + "Vertex Mesh Method", + "The method to use to create meshes at intersections"); + prop = RNA_def_boolean(ot->srna, "release_confirm", 0, "Confirm on Release", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } diff --git a/source/blender/makesdna/DNA_curveprofile_types.h b/source/blender/makesdna/DNA_curveprofile_types.h new file mode 100644 index 00000000000..d7e3591a9b8 --- /dev/null +++ b/source/blender/makesdna/DNA_curveprofile_types.h @@ -0,0 +1,91 @@ +/* + * 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. + * + * Copyright (C) 2019 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup DNA + */ + +#ifndef DNA_PROFILEPATH_TYPES_H +#define DNA_PROFILEPATH_TYPES_H + +#include "DNA_vec_types.h" + +/** Number of points in high resolution table is dynamic up to a maximum. */ +#define PROF_TABLE_MAX 512 +/** Number of table points per control point. */ +#define PROF_RESOL 16 +/** Dynamic size of widget's high resolution table. Input should be profile->totpoint. */ +#define PROF_N_TABLE(n_pts) min_ii(PROF_TABLE_MAX, (((n_pts - 1)) * PROF_RESOL) + 1) + +/** Each control point that makes up the profile. + * \note The flags use the same enum as Bezier curves, but they aren't garanteed + * to have identical functionality, and all types aren't implemented. */ +typedef struct CurveProfilePoint { + /** Location of the point, keep together. */ + float x, y; + /** Flag selection state and others. */ + short flag; + /** Flags for both handle's type (eBezTriple_Handle). */ + char h1, h2; +} CurveProfilePoint; + +/** #CurveProfilePoint.flag */ +enum { + PROF_SELECT = (1 << 0), +}; + +/** Defines a profile */ +typedef struct CurveProfile { + /** Number of user-added points that define the profile. */ + short path_len; + /** Number of sampled points. */ + short segments_len; + /** Preset to use when reset. */ + int preset; + /** Sequence of points defining the shape of the curve. */ + CurveProfilePoint *path; + /** Display and evaluation table at higher resolution for curves. */ + CurveProfilePoint *table; + /** The positions of the sampled points. Used to display a preview of where they will be. */ + CurveProfilePoint *segments; + /** Flag for mode states, sampling options, etc... */ + int flag; + /** Used for keeping track how many times the widget is changed. */ + int changed_timestamp; + /** Widget's current view, and clipping rect (is default rect too). */ + rctf view_rect, clip_rect; +} CurveProfile; + +/** #CurveProfile.flag */ +enum { + PROF_USE_CLIP = (1 << 0), /* Keep control points inside bounding rectangle. */ + PROF_SYMMETRY_MODE = (1 << 1), /* Unused for now. */ + PROF_SAMPLE_STRAIGHT_EDGES = (1 << 2), /* Sample extra points on straight edges. */ + PROF_SAMPLE_EVEN_LENGTHS = (1 << 3), /* Put segments evenly spaced along the path. */ +}; + +typedef enum eCurveProfilePresets { + PROF_PRESET_LINE = 0, /* Default simple line between end points. */ + PROF_PRESET_SUPPORTS = 1, /* Support loops for a regular curved profile. */ + PROF_PRESET_CORNICE = 2, /* Moulding type example. */ + PROF_PRESET_CROWN = 3, /* Second moulding example. */ + PROF_PRESET_STEPS = 4, /* Dynamic number of steps defined by totsegments. */ +} eCurveProfilePresets; + +#endif diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 9c4d7bcd3b1..a8db46238d8 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -377,10 +377,11 @@ typedef struct BevelModifierData { short mat; short edge_flags; short face_str_mode; - /* patterns to use for mitering non-reflex and reflex miter edges */ + /** Patterns to use for mitering non-reflex and reflex miter edges */ short miter_inner; short miter_outer; - char _pad0[2]; + /** The method to use for creating >2-way intersections */ + short vmesh_method; /** Controls profile shape (0->1, .5 is round). */ float profile; /** if the MOD_BEVEL_ANGLE is set, @@ -390,6 +391,10 @@ typedef struct BevelModifierData { /** if the MOD_BEVEL_VWEIGHT option is set, * this will be the name of the vert group, MAX_VGROUP_NAME */ char defgrp_name[64]; + + /** Curve info for the custom profile */ + struct CurveProfile *custom_profile; + } BevelModifierData; /* BevelModifierData->flags and BevelModifierData->lim_flags */ @@ -399,8 +404,8 @@ enum { MOD_BEVEL_ANGLE = (1 << 3), MOD_BEVEL_WEIGHT = (1 << 4), MOD_BEVEL_VGROUP = (1 << 5), - /* unused = (1 << 7), */ - /* unused = (1 << 8), */ + MOD_BEVEL_CUSTOM_PROFILE = (1 << 7), + MOD_BEVEL_SAMPLE_STRAIGHT = (1 << 8), /* unused = (1 << 9), */ /* unused = (1 << 10), */ /* unused = (1 << 11), */ @@ -439,6 +444,12 @@ enum { MOD_BEVEL_MITER_ARC, }; +/* BevelModifier->vmesh_method */ +enum { + MOD_BEVEL_VMESH_ADJ, + MOD_BEVEL_VMESH_CUTOFF, +}; + typedef struct SmokeModifierData { ModifierData modifier; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 2ca9e3b2ef7..e60b16453b2 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -33,7 +33,8 @@ extern "C" { #endif -#include "DNA_color_types.h" /* color management */ +#include "DNA_color_types.h" /* color management */ +#include "DNA_curveprofile_types.h" #include "DNA_customdata_types.h" /* Scene's runtime cddata masks. */ #include "DNA_vec_types.h" #include "DNA_listBase.h" @@ -50,6 +51,7 @@ struct Brush; struct Collection; struct ColorSpace; struct CurveMapping; +struct CurveProfile; struct CustomData_MeshMasks; struct Editing; struct Image; @@ -1515,6 +1517,11 @@ typedef struct ToolSettings { /* Normal Editing */ float normal_vector[3]; char _pad6[4]; + + /* Custom Curve Profile for bevel tool: + * Temporary until there is a proper preset system that stores the profiles or maybe stores + * entire bevel configurations. */ + struct CurveProfile *custom_bevel_profile_preset; } ToolSettings; /* *************************************************************** */ diff --git a/source/blender/makesdna/intern/makesdna.c b/source/blender/makesdna/intern/makesdna.c index 80d37d89f14..d6f52e48ebe 100644 --- a/source/blender/makesdna/intern/makesdna.c +++ b/source/blender/makesdna/intern/makesdna.c @@ -131,6 +131,7 @@ static const char *includefiles[] = { "DNA_layer_types.h", "DNA_workspace_types.h", "DNA_lightprobe_types.h", + "DNA_curveprofile_types.h", /* see comment above before editing! */ @@ -1590,6 +1591,7 @@ int main(int argc, char **argv) #include "DNA_layer_types.h" #include "DNA_workspace_types.h" #include "DNA_lightprobe_types.h" +#include "DNA_curveprofile_types.h" /* end of list */ diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index a1369d84786..5bf6fb40c6a 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -209,6 +209,8 @@ extern StructRNA RNA_CurveMapPoint; extern StructRNA RNA_CurveMapping; extern StructRNA RNA_CurveModifier; extern StructRNA RNA_CurvePoint; +extern StructRNA RNA_CurveProfile; +extern StructRNA RNA_CurveProfilePoint; extern StructRNA RNA_DampedTrackConstraint; extern StructRNA RNA_DataTransferModifier; extern StructRNA RNA_DecimateModifier; diff --git a/source/blender/makesrna/RNA_enum_types.h b/source/blender/makesrna/RNA_enum_types.h index 458f031ceae..9290b81f1af 100644 --- a/source/blender/makesrna/RNA_enum_types.h +++ b/source/blender/makesrna/RNA_enum_types.h @@ -216,6 +216,8 @@ extern const EnumPropertyItem rna_enum_dt_layers_select_dst_items[]; extern const EnumPropertyItem rna_enum_abc_compression_items[]; extern const EnumPropertyItem rna_enum_context_mode_items[]; +extern const EnumPropertyItem rna_enum_curveprofile_preset_items[]; + /* API calls */ int rna_node_tree_type_to_enum(struct bNodeTreeType *typeinfo); int rna_node_tree_idname_to_enum(const char *idname); diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 4bb53404724..7c06582dd6a 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -68,6 +68,7 @@ set(DEFSRC rna_palette.c rna_particle.c rna_pose.c + rna_curveprofile.c rna_render.c rna_rigidbody.c rna_rna.c diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index 2d4da942610..009a723551e 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -4255,6 +4255,7 @@ static RNAProcessItem PROCESS_ITEMS[] = { {"rna_palette.c", NULL, RNA_def_palette}, {"rna_particle.c", NULL, RNA_def_particle}, {"rna_pose.c", "rna_pose_api.c", RNA_def_pose}, + {"rna_curveprofile.c", NULL, RNA_def_profile}, {"rna_lightprobe.c", NULL, RNA_def_lightprobe}, {"rna_render.c", NULL, RNA_def_render}, {"rna_rigidbody.c", NULL, RNA_def_rigidbody}, diff --git a/source/blender/makesrna/intern/rna_curveprofile.c b/source/blender/makesrna/intern/rna_curveprofile.c new file mode 100644 index 00000000000..71e1aac5aba --- /dev/null +++ b/source/blender/makesrna/intern/rna_curveprofile.c @@ -0,0 +1,322 @@ +/* + * 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. + */ + +/** \file + * \ingroup RNA + */ + +#include <stdlib.h> +#include <stdio.h> + +#include "DNA_curveprofile_types.h" +#include "DNA_curve_types.h" +#include "DNA_texture_types.h" + +#include "BLI_utildefines.h" + +#include "RNA_define.h" +#include "rna_internal.h" + +#include "WM_api.h" +#include "WM_types.h" + +#ifdef RNA_RUNTIME + +# include "RNA_access.h" + +# include "DNA_image_types.h" +# include "DNA_material_types.h" +# include "DNA_movieclip_types.h" +# include "DNA_node_types.h" +# include "DNA_object_types.h" +# include "DNA_particle_types.h" +# include "DNA_sequence_types.h" + +# include "MEM_guardedalloc.h" + +# include "BKE_colorband.h" +# include "BKE_curveprofile.h" +# include "BKE_image.h" +# include "BKE_movieclip.h" +# include "BKE_node.h" +# include "BKE_sequencer.h" +# include "BKE_linestyle.h" + +# include "DEG_depsgraph.h" + +# include "ED_node.h" + +# include "IMB_colormanagement.h" +# include "IMB_imbuf.h" + +static void rna_CurveProfile_clip_set(PointerRNA *ptr, bool value) +{ + CurveProfile *profile = (CurveProfile *)ptr->data; + + if (value) { + profile->flag |= PROF_USE_CLIP; + } + else { + profile->flag &= ~PROF_USE_CLIP; + } + + BKE_curveprofile_update(profile, false); +} + +static void rna_CurveProfile_sample_straight_set(PointerRNA *ptr, bool value) +{ + CurveProfile *profile = (CurveProfile *)ptr->data; + + if (value) { + profile->flag |= PROF_SAMPLE_STRAIGHT_EDGES; + } + else { + profile->flag &= ~PROF_SAMPLE_STRAIGHT_EDGES; + } + + BKE_curveprofile_update(profile, false); +} + +static void rna_CurveProfile_sample_even_set(PointerRNA *ptr, bool value) +{ + CurveProfile *profile = (CurveProfile *)ptr->data; + + if (value) { + profile->flag |= PROF_SAMPLE_EVEN_LENGTHS; + } + else { + profile->flag &= ~PROF_SAMPLE_EVEN_LENGTHS; + } + + BKE_curveprofile_update(profile, false); +} + +static void rna_CurveProfile_remove_point(CurveProfile *profile, + ReportList *reports, + PointerRNA *point_ptr) +{ + CurveProfilePoint *point = point_ptr->data; + if (BKE_curveprofile_remove_point(profile, point) == false) { + BKE_report(reports, RPT_ERROR, "Unable to remove path point"); + return; + } + + RNA_POINTER_INVALIDATE(point_ptr); +} + +static void rna_CurveProfile_evaluate(struct CurveProfile *profile, + ReportList *reports, + float length_portion, + float *location) +{ + if (!profile->table) { + BKE_report(reports, RPT_ERROR, "CurveProfile table not initialized, call initialize()"); + } + BKE_curveprofile_evaluate_length_portion(profile, length_portion, &location[0], &location[1]); +} + +static void rna_CurveProfile_initialize(struct CurveProfile *profile, int segments_len) +{ + BKE_curveprofile_initialize(profile, (short)segments_len); +} + +static void rna_CurveProfile_update(struct CurveProfile *profile) +{ + BKE_curveprofile_update(profile, false); +} + +#else + +static const EnumPropertyItem prop_handle_type_items[] = { + {HD_AUTO, "AUTO", 0, "Auto Handle", ""}, + {HD_VECT, "VECTOR", 0, "Vector Handle", ""}, + {0, NULL, 0, NULL, NULL}, +}; + +static void rna_def_curveprofilepoint(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "CurveProfilePoint", NULL); + RNA_def_struct_ui_text(srna, "CurveProfilePoint", "Point of a path used to define a profile"); + + prop = RNA_def_property(srna, "location", PROP_FLOAT, PROP_XYZ); + RNA_def_property_float_sdna(prop, NULL, "x"); + RNA_def_property_array(prop, 2); + RNA_def_property_ui_text(prop, "Location", "X/Y coordinates of the path point"); + + prop = RNA_def_property(srna, "handle_type_1", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "h1"); + RNA_def_property_enum_items(prop, prop_handle_type_items); + RNA_def_property_ui_text( + prop, "First Handle Type", "Path interpolation at this point: Bezier or vector"); + + prop = RNA_def_property(srna, "handle_type_2", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "h2"); + RNA_def_property_enum_items(prop, prop_handle_type_items); + RNA_def_property_ui_text( + prop, "Second Handle Type", "Path interpolation at this point: Bezier or vector"); + + prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_SELECT); + RNA_def_property_ui_text(prop, "Select", "Selection state of the path point"); +} + +static void rna_def_curveprofile_points_api(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + PropertyRNA *parm; + FunctionRNA *func; + + RNA_def_property_srna(cprop, "CurveProfilePoints"); + srna = RNA_def_struct(brna, "CurveProfilePoints", NULL); + RNA_def_struct_sdna(srna, "CurveProfile"); + RNA_def_struct_ui_text(srna, "Profile Point", "Collection of Profile Points"); + + func = RNA_def_function(srna, "add", "BKE_curveprofile_insert"); + RNA_def_function_ui_description(func, "Add point to the profile"); + parm = RNA_def_float(func, + "x", + 0.0f, + -FLT_MAX, + FLT_MAX, + "X Position", + "X Position for new point", + -FLT_MAX, + FLT_MAX); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_float(func, + "y", + 0.0f, + -FLT_MAX, + FLT_MAX, + "Y Position", + "Y Position for new point", + -FLT_MAX, + FLT_MAX); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, "point", "CurveProfilePoint", "", "New point"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_CurveProfile_remove_point"); + RNA_def_function_flag(func, FUNC_USE_REPORTS); + RNA_def_function_ui_description(func, "Delete point from the profile"); + parm = RNA_def_pointer(func, "point", "CurveProfilePoint", "", "Point to remove"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0); +} + +static void rna_def_curveprofile(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + PropertyRNA *parm; + FunctionRNA *func; + + static const EnumPropertyItem rna_enum_curveprofile_preset_items[] = { + {PROF_PRESET_LINE, "LINE", 0, "Line", "Default"}, + {PROF_PRESET_SUPPORTS, "SUPPORTS", 0, "Support Loops", "Loops on each side of the profile"}, + {PROF_PRESET_CORNICE, "CORNICE", 0, "Cornice Moulding", ""}, + {PROF_PRESET_CROWN, "CROWN", 0, "Crown Moulding", ""}, + {PROF_PRESET_STEPS, "STEPS", 0, "Steps", "A number of steps defined by the segments"}, + {0, NULL, 0, NULL, NULL}, + }; + + srna = RNA_def_struct(brna, "CurveProfile", NULL); + RNA_def_struct_ui_text(srna, "CurveProfile", "Profile Path editor used to build a profile path"); + + prop = RNA_def_property(srna, "preset", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "preset"); + RNA_def_property_enum_items(prop, rna_enum_curveprofile_preset_items); + RNA_def_property_ui_text(prop, "Preset", ""); + + prop = RNA_def_property(srna, "use_clip", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_USE_CLIP); + RNA_def_property_ui_text(prop, "Clip", "Force the path view to fit a defined boundary"); + RNA_def_property_boolean_funcs(prop, NULL, "rna_CurveProfile_clip_set"); + + prop = RNA_def_property(srna, "use_sample_straight_edges", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_SAMPLE_STRAIGHT_EDGES); + RNA_def_property_ui_text(prop, "Sample Straight Edges", "Sample edges with vector handles"); + RNA_def_property_boolean_funcs(prop, NULL, "rna_CurveProfile_sample_straight_set"); + + prop = RNA_def_property(srna, "use_sample_even_lengths", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_SAMPLE_EVEN_LENGTHS); + RNA_def_property_ui_text(prop, "Sample Even Lengths", "Sample edges with even lengths"); + RNA_def_property_boolean_funcs(prop, NULL, "rna_CurveProfile_sample_even_set"); + + func = RNA_def_function(srna, "update", "rna_CurveProfile_update"); + RNA_def_function_ui_description(func, "Update the profile"); + + func = RNA_def_function(srna, "initialize", "rna_CurveProfile_initialize"); + parm = RNA_def_int(func, + "totsegments", + 1, + 1, + 1000, + "", + "The number of segment values to" + " initialize the segments table with", + 1, + 100); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); + RNA_def_function_ui_description(func, "Set the number of display segments and fill tables"); + + prop = RNA_def_property(srna, "points", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "path", "path_len"); + RNA_def_property_struct_type(prop, "CurveProfilePoint"); + RNA_def_property_ui_text(prop, "Points", "Profile control points"); + rna_def_curveprofile_points_api(brna, prop); + + prop = RNA_def_property(srna, "segments", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, NULL, "segments", "segments_len"); + RNA_def_property_struct_type(prop, "CurveProfilePoint"); + RNA_def_property_ui_text(prop, "Segments", "Segments sampled from control points"); + + func = RNA_def_function(srna, "evaluate", "rna_CurveProfile_evaluate"); + RNA_def_function_flag(func, FUNC_USE_REPORTS); + RNA_def_function_ui_description(func, "Evaluate the at the given portion of the path length"); + parm = RNA_def_float(func, + "length_portion", + 0.0f, + 0.0f, + 1.0f, + "Length Portion", + "Portion of the path length to travel before evaluation", + 0.0f, + 1.0f); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_float_vector(func, + "location", + 2, + NULL, + -100.0f, + 100.0f, + "Location", + "The location at the given portion of the profile", + -100.0f, + 100.0f); + RNA_def_function_output(func, parm); +} + +void RNA_def_profile(BlenderRNA *brna) +{ + rna_def_curveprofilepoint(brna); + rna_def_curveprofile(brna); +} + +#endif diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index 04a59a48b63..94394a4826b 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -178,6 +178,7 @@ void RNA_def_packedfile(struct BlenderRNA *brna); void RNA_def_palette(struct BlenderRNA *brna); void RNA_def_particle(struct BlenderRNA *brna); void RNA_def_pose(struct BlenderRNA *brna); +void RNA_def_profile(struct BlenderRNA *brna); void RNA_def_lightprobe(struct BlenderRNA *brna); void RNA_def_render(struct BlenderRNA *brna); void RNA_def_rigidbody(struct BlenderRNA *brna); diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index 6e81b1343f5..789946d3268 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -37,6 +37,7 @@ #include "BLT_translation.h" #include "BKE_animsys.h" +#include "BKE_curveprofile.h" #include "BKE_data_transfer.h" #include "BKE_dynamicpaint.h" #include "BKE_effect.h" @@ -433,7 +434,6 @@ const EnumPropertyItem rna_enum_axis_flag_xyz_items[] = { }; #ifdef RNA_RUNTIME - # include "DNA_particle_types.h" # include "DNA_curve_types.h" # include "DNA_smoke_types.h" @@ -998,6 +998,18 @@ static PointerRNA rna_CollisionModifier_settings_get(PointerRNA *ptr) return rna_pointer_inherit_refine(ptr, &RNA_CollisionSettings, ob->pd); } +/* Special update function for setting the number of segments of the modifier that also resamples + * the segments in the custom profile. */ +static void rna_BevelModifier_update_segments(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + BevelModifierData *bmd = (BevelModifierData *)ptr->data; + if (RNA_boolean_get(ptr, "use_custom_profile")) { + short segments = (short)RNA_int_get(ptr, "segments"); + BKE_curveprofile_initialize(bmd->custom_profile, segments); + } + rna_Modifier_update(bmain, scene, ptr); +} + static void rna_UVProjectModifier_num_projectors_set(PointerRNA *ptr, int value) { UVProjectModifierData *md = (UVProjectModifierData *)ptr->data; @@ -3583,10 +3595,26 @@ static void rna_def_modifier_bevel(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; - static EnumPropertyItem prop_miter_items[] = { - {MOD_BEVEL_MITER_SHARP, "MITER_SHARP", 0, "Sharp", "Default sharp miter"}, - {MOD_BEVEL_MITER_PATCH, "MITER_PATCH", 0, "Patch", "Miter with extra corner"}, - {MOD_BEVEL_MITER_ARC, "MITER_ARC", 0, "Arc", "Miter with curved arc"}, + static const EnumPropertyItem prop_miter_outer_items[] = { + {MOD_BEVEL_MITER_SHARP, "MITER_SHARP", 0, "Sharp", "Outside of miter is sharp"}, + {MOD_BEVEL_MITER_PATCH, "MITER_PATCH", 0, "Patch", "Outside of miter is squared-off patch"}, + {MOD_BEVEL_MITER_ARC, "MITER_ARC", 0, "Arc", "Outside of miter is arc"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem prop_miter_inner_items[] = { + {MOD_BEVEL_MITER_SHARP, "MITER_SHARP", 0, "Sharp", "Inside of miter is sharp"}, + {MOD_BEVEL_MITER_ARC, "MITER_ARC", 0, "Arc", "Inside of miter is arc"}, + {0, NULL, 0, NULL, NULL}, + }; + + static EnumPropertyItem prop_vmesh_method_items[] = { + {MOD_BEVEL_VMESH_ADJ, "ADJ", 0, "Grid Fill", "Default patterned fill"}, + {MOD_BEVEL_VMESH_CUTOFF, + "CUTOFF", + 0, + "Cutoff", + "A cut-off at the end of each profile before the intersection"}, {0, NULL, 0, NULL, NULL}, }; @@ -3614,7 +3642,7 @@ static void rna_def_modifier_bevel(BlenderRNA *brna) RNA_def_property_int_sdna(prop, NULL, "res"); RNA_def_property_range(prop, 1, 100); RNA_def_property_ui_text(prop, "Segments", "Number of segments for round edges/verts"); - RNA_def_property_update(prop, 0, "rna_Modifier_update"); + RNA_def_property_update(prop, 0, "rna_BevelModifier_update_segments"); prop = RNA_def_property(srna, "use_only_vertices", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flags", MOD_BEVEL_VERT); @@ -3648,7 +3676,7 @@ static void rna_def_modifier_bevel(BlenderRNA *brna) prop = RNA_def_property(srna, "offset_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "val_flags"); RNA_def_property_enum_items(prop, prop_val_type_items); - RNA_def_property_ui_text(prop, "Amount Type", "What distance Width measures"); + RNA_def_property_ui_text(prop, "Width Type", "What distance Width measures"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); prop = RNA_def_property(srna, "profile", PROP_FLOAT, PROP_FACTOR); @@ -3693,13 +3721,13 @@ static void rna_def_modifier_bevel(BlenderRNA *brna) prop = RNA_def_property(srna, "miter_outer", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "miter_outer"); - RNA_def_property_enum_items(prop, prop_miter_items); + RNA_def_property_enum_items(prop, prop_miter_outer_items); RNA_def_property_ui_text(prop, "Outer Miter", "Pattern to use for outside of miters"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); prop = RNA_def_property(srna, "miter_inner", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "miter_inner"); - RNA_def_property_enum_items(prop, prop_miter_items); + RNA_def_property_enum_items(prop, prop_miter_inner_items); RNA_def_property_ui_text(prop, "Inner Miter", "Pattern to use for inside of miters"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); @@ -3709,6 +3737,25 @@ static void rna_def_modifier_bevel(BlenderRNA *brna) RNA_def_property_ui_range(prop, 0.0f, 100.0f, 0.1, 4); RNA_def_property_ui_text(prop, "Spread", "Spread distance for inner miter arcs"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "use_custom_profile", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", MOD_BEVEL_CUSTOM_PROFILE); + RNA_def_property_ui_text( + prop, "Custom Profile", "Whether to use a user inputed curve for the bevel's profile"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "custom_profile", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "CurveProfile"); + RNA_def_property_pointer_sdna(prop, NULL, "custom_profile"); + RNA_def_property_ui_text(prop, "Custom Profile Path", "The path for the custom profile"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "vmesh_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "vmesh_method"); + RNA_def_property_enum_items(prop, prop_vmesh_method_items); + RNA_def_property_ui_text( + prop, "Vertex Mesh Method", "The method to use to create the mesh at intersections"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); } static void rna_def_modifier_shrinkwrap(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index b9bc7b2bbf5..4d3de4ed6e6 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -3307,6 +3307,12 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_NEVER_NULL); RNA_def_property_struct_type(prop, "MeshStatVis"); RNA_def_property_ui_text(prop, "Mesh Statistics Visualization", NULL); + + /* CurveProfile */ + prop = RNA_def_property(srna, "custom_bevel_profile_preset", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "custom_bevel_profile_preset"); + RNA_def_property_struct_type(prop, "CurveProfile"); + RNA_def_property_ui_text(prop, "Curve Profile Widget", "Used for defining a profile's path"); } static void rna_def_unified_paint_settings(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_ui_api.c b/source/blender/makesrna/intern/rna_ui_api.c index 94fe43a0a77..c734720fdcd 100644 --- a/source/blender/makesrna/intern/rna_ui_api.c +++ b/source/blender/makesrna/intern/rna_ui_api.c @@ -1220,6 +1220,10 @@ void RNA_api_ui_layout(StructRNA *srna) RNA_def_boolean(func, "use_negative_slope", false, "", "Use a negative slope by default"); RNA_def_boolean(func, "show_tone", false, "", "Show tone options"); + func = RNA_def_function(srna, "template_curveprofile", "uiTemplateCurveProfile"); + RNA_def_function_ui_description(func, "A profile path editor used for custom profiles"); + api_ui_item_rna_common(func); + func = RNA_def_function(srna, "template_color_ramp", "uiTemplateColorRamp"); RNA_def_function_ui_description(func, "Item. A color ramp widget"); api_ui_item_rna_common(func); diff --git a/source/blender/modifiers/intern/MOD_bevel.c b/source/blender/modifiers/intern/MOD_bevel.c index 0c00bb572be..17384f133b1 100644 --- a/source/blender/modifiers/intern/MOD_bevel.c +++ b/source/blender/modifiers/intern/MOD_bevel.c @@ -31,6 +31,7 @@ #include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "DNA_curveprofile_types.h" #include "BKE_deform.h" #include "BKE_mesh.h" @@ -40,6 +41,7 @@ #include "bmesh.h" #include "bmesh_tools.h" +#include "BKE_curveprofile.h" #include "DEG_depsgraph_query.h" @@ -47,7 +49,7 @@ static void initData(ModifierData *md) { BevelModifierData *bmd = (BevelModifierData *)md; - bmd->value = 0.1f; + bmd->value = 1.0f; bmd->res = 1; bmd->flags = 0; bmd->val_flags = MOD_BEVEL_AMT_OFFSET; @@ -62,11 +64,16 @@ static void initData(ModifierData *md) bmd->profile = 0.5f; bmd->bevel_angle = DEG2RADF(30.0f); bmd->defgrp_name[0] = '\0'; + bmd->custom_profile = BKE_curveprofile_add(PROF_PRESET_LINE); } static void copyData(const ModifierData *md_src, ModifierData *md_dst, const int flag) { + const BevelModifierData *bmd_src = (const BevelModifierData *)md_src; + BevelModifierData *bmd_dst = (BevelModifierData *)md_dst; + modifier_copyData_generic(md_src, md_dst, flag); + bmd_dst->custom_profile = BKE_curveprofile_copy(bmd_src->custom_profile); } static void requiredDataMask(Object *UNUSED(ob), @@ -109,6 +116,8 @@ static Mesh *applyModifier(ModifierData *md, const ModifierEvalContext *ctx, Mes const int miter_outer = bmd->miter_outer; const int miter_inner = bmd->miter_inner; const float spread = bmd->spread; + const bool use_custom_profile = (bmd->flags & MOD_BEVEL_CUSTOM_PROFILE); + const int vmesh_method = bmd->vmesh_method; bm = BKE_mesh_to_bmesh_ex(mesh, &(struct BMeshCreateParams){0}, @@ -210,7 +219,10 @@ static Mesh *applyModifier(ModifierData *md, const ModifierEvalContext *ctx, Mes miter_outer, miter_inner, spread, - mesh->smoothresh); + mesh->smoothresh, + use_custom_profile, + bmd->custom_profile, + vmesh_method); result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); @@ -229,6 +241,18 @@ static bool dependsOnNormals(ModifierData *UNUSED(md)) return true; } +static void freeData(ModifierData *md) +{ + BevelModifierData *bmd = (BevelModifierData *)md; + BKE_curveprofile_free(bmd->custom_profile); +} + +static bool isDisabled(const Scene *UNUSED(scene), ModifierData *md, bool UNUSED(userRenderParams)) +{ + BevelModifierData *bmd = (BevelModifierData *)md; + return (bmd->value == 0.0f); +} + ModifierTypeInfo modifierType_Bevel = { /* name */ "Bevel", /* structName */ "BevelModifierData", @@ -236,19 +260,16 @@ ModifierTypeInfo modifierType_Bevel = { /* type */ eModifierTypeType_Constructive, /* flags */ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode | eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_AcceptsCVs, - /* copyData */ copyData, - /* deformVerts */ NULL, /* deformMatrices */ NULL, /* deformVertsEM */ NULL, /* deformMatricesEM */ NULL, /* applyModifier */ applyModifier, - /* initData */ initData, /* requiredDataMask */ requiredDataMask, - /* freeData */ NULL, - /* isDisabled */ NULL, + /* freeData */ freeData, + /* isDisabled */ isDisabled, /* updateDepsgraph */ NULL, /* dependsOnTime */ NULL, /* dependsOnNormals */ dependsOnNormals, |