From 5abb3c96cfa5e16f4432241aafab8712c70945e9 Mon Sep 17 00:00:00 2001 From: Chris Blackbourn Date: Sun, 7 May 2023 17:53:05 +1200 Subject: [PATCH] UV: Add pinned island support to uv packer New pinned modes: * Ignore pinned islands * Normal (default) * Lock scale * Lock rotation * Lock position of pinned UV islands. --- .../editors/uvedit/uvedit_unwrap_ops.cc | 38 ++- source/blender/geometry/GEO_uv_pack.hh | 24 +- source/blender/geometry/intern/uv_pack.cc | 323 +++++++++++++----- .../geometry/intern/uv_parametrizer.cc | 12 +- 4 files changed, 294 insertions(+), 103 deletions(-) diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc index 3f1e3cedeee..ae05ed542b5 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc @@ -1152,6 +1152,7 @@ static void uvedit_pack_islands_multi(const Scene *scene, blender::geometry::UVPackIsland_Params *params) { blender::Vector island_vector; + blender::Vector pinned_vector; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; @@ -1175,7 +1176,8 @@ static void uvedit_pack_islands_multi(const Scene *scene, bool only_selected_faces = params->only_selected_faces; bool only_selected_uvs = params->only_selected_uvs; - if (params->ignore_pinned && params->pin_unselected) { + const bool ignore_pinned = params->pin_method == ED_UVPACK_PIN_IGNORED; + if (ignore_pinned && params->pin_unselected) { only_selected_faces = false; only_selected_uvs = false; } @@ -1192,12 +1194,14 @@ static void uvedit_pack_islands_multi(const Scene *scene, /* Remove from linked list and append to blender::Vector. */ LISTBASE_FOREACH_MUTABLE (struct FaceIsland *, island, &island_list) { BLI_remlink(&island_list, island); - if (params->ignore_pinned && island_has_pins(scene, island, params)) { + const bool pinned = island_has_pins(scene, island, params); + if (ignore_pinned && pinned) { MEM_freeN(island->faces); MEM_freeN(island); continue; } island_vector.append(island); + pinned_vector.append(pinned); } } @@ -1241,6 +1245,7 @@ static void uvedit_pack_islands_multi(const Scene *scene, blender::geometry::PackIsland *pack_island = new blender::geometry::PackIsland(); pack_island->caller_index = i; pack_island->aspect_y = face_island->aspect_y; + pack_island->pinned = pinned_vector[i]; pack_island_vector.append(pack_island); for (int i = 0; i < face_island->faces_len; i++) { @@ -1307,7 +1312,8 @@ static void uvedit_pack_islands_multi(const Scene *scene, for (int64_t i : pack_island_vector.index_range()) { blender::geometry::PackIsland *pack_island = pack_island_vector[i]; FaceIsland *island = island_vector[pack_island->caller_index]; - pack_island->build_transformation(scale, pack_island->angle, matrix); + const float island_scale = pack_island->can_scale_(*params) ? scale : 1.0f; + pack_island->build_transformation(island_scale, pack_island->angle, matrix); invert_m2_m2(matrix_inverse, matrix); /* Add base_offset, post transform. */ @@ -1399,7 +1405,7 @@ static int pack_islands_exec(bContext *C, wmOperator *op) pack_island_params.rotate = RNA_boolean_get(op->ptr, "rotate"); pack_island_params.scale_to_fit = RNA_boolean_get(op->ptr, "scale"); pack_island_params.merge_overlap = RNA_boolean_get(op->ptr, "merge_overlap"); - pack_island_params.ignore_pinned = false; + pack_island_params.pin_method = eUVPackIsland_PinMethod(RNA_enum_get(op->ptr, "pin_method")); pack_island_params.margin_method = eUVPackIsland_MarginMethod( RNA_enum_get(op->ptr, "margin_method")); pack_island_params.margin = RNA_float_get(op->ptr, "margin"); @@ -1445,6 +1451,20 @@ static const EnumPropertyItem pack_shape_method_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +static const EnumPropertyItem pinned_islands_method_items[] = { + {ED_UVPACK_PIN_NORMAL, "NORMAL", 0, "Normal", "Pin information is not used"}, + {ED_UVPACK_PIN_IGNORED, "IGNORED", 0, "Ignored", "Pinned islands are not packed"}, + {ED_UVPACK_PIN_LOCK_SCALE, "SCALE", 0, "Locked scale", "Pinned islands won't rescale"}, + {ED_UVPACK_PIN_LOCK_ROTATION, "ROTATION", 0, "Locked rotation", "Pinned islands won't rotate"}, + {ED_UVPACK_PIN_LOCK_ROTATION_SCALE, + "ROTATION_SCALE", + 0, + "Locked rotation and scale", + "Pinned islands will translate only"}, + {ED_UVPACK_PIN_LOCK_ALL, "LOCKED", 0, "Locked position", "Pinned islands are locked in place"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + void UV_OT_pack_islands(wmOperatorType *ot) { static const EnumPropertyItem pack_target[] = { @@ -1487,6 +1507,12 @@ void UV_OT_pack_islands(wmOperatorType *ot) ""); RNA_def_float_factor( ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f); + RNA_def_enum(ot->srna, + "pin_method", + pinned_islands_method_items, + ED_UVPACK_PIN_NORMAL, + "Pinned Islands", + ""); RNA_def_enum(ot->srna, "shape_method", pack_shape_method_items, @@ -2203,7 +2229,7 @@ void ED_uvedit_live_unwrap(const Scene *scene, Object **objects, int objects_len blender::geometry::UVPackIsland_Params pack_island_params; pack_island_params.setFromUnwrapOptions(options); pack_island_params.rotate = true; - pack_island_params.ignore_pinned = true; + pack_island_params.pin_method = ED_UVPACK_PIN_IGNORED; pack_island_params.margin_method = ED_UVPACK_MARGIN_SCALED; pack_island_params.margin = scene->toolsettings->uvcalc_margin; @@ -2345,7 +2371,7 @@ static int unwrap_exec(bContext *C, wmOperator *op) blender::geometry::UVPackIsland_Params pack_island_params; pack_island_params.setFromUnwrapOptions(options); pack_island_params.rotate = true; - pack_island_params.ignore_pinned = true; + pack_island_params.pin_method = ED_UVPACK_PIN_IGNORED; pack_island_params.margin_method = eUVPackIsland_MarginMethod( RNA_enum_get(op->ptr, "margin_method")); pack_island_params.margin = RNA_float_get(op->ptr, "margin"); diff --git a/source/blender/geometry/GEO_uv_pack.hh b/source/blender/geometry/GEO_uv_pack.hh index 5444e0b81a5..76bda04046d 100644 --- a/source/blender/geometry/GEO_uv_pack.hh +++ b/source/blender/geometry/GEO_uv_pack.hh @@ -35,6 +35,16 @@ enum eUVPackIsland_ShapeMethod { ED_UVPACK_SHAPE_CONCAVE, }; +enum eUVPackIsland_PinMethod { + ED_UVPACK_PIN_IGNORED = 0, + ED_UVPACK_PIN_NORMAL, + ED_UVPACK_PIN_LOCK_ROTATION, + ED_UVPACK_PIN_LOCK_ROTATION_SCALE, + ED_UVPACK_PIN_LOCK_SCALE, + ED_UVPACK_PIN_LOCK_TRANSLATION, + ED_UVPACK_PIN_LOCK_ALL, /* Lock translation, rotation and scale. */ +}; + namespace blender::geometry { /** See also #UnwrapOptions. */ @@ -58,8 +68,8 @@ class UVPackIsland_Params { bool use_seams; /** (In 3D Viewport or UV Editor) use aspect ratio from face. */ bool correct_aspect; - /** Ignore islands which have any pinned UVs. */ - bool ignore_pinned; + /** How will pinned islands be treated. */ + eUVPackIsland_PinMethod pin_method; /** Treat unselected UVs as if they were pinned. */ bool pin_unselected; /** Overlapping islands stick together. */ @@ -83,6 +93,8 @@ class PackIsland { /** Aspect ratio, required for rotation. */ float aspect_y; + /** Are any of the UVs pinned? */ + bool pinned; /** Output pre-translation. */ float2 pre_translate; /** Output angle in radians. */ @@ -93,9 +105,9 @@ class PackIsland { void add_triangle(const float2 uv0, const float2 uv1, const float2 uv2); void add_polygon(const blender::Span uvs, MemArena *arena, Heap *heap); - void build_transformation(const float scale, const float rotation, float r_matrix[2][2]) const; + void build_transformation(const float scale, const double rotation, float r_matrix[2][2]) const; void build_inverse_transformation(const float scale, - const float rotation, + const double rotation, float r_matrix[2][2]) const; float2 get_diagonal_support(const float scale, const float rotation, const float margin) const; @@ -112,6 +124,10 @@ class PackIsland { void place_(const float scale, const uv_phi phi); void finalize_geometry_(const UVPackIsland_Params ¶ms, MemArena *arena, Heap *heap); + bool can_rotate_(const UVPackIsland_Params ¶ms) const; + bool can_scale_(const UVPackIsland_Params ¶ms) const; + bool can_translate_(const UVPackIsland_Params ¶ms) const; + blender::Vector triangle_vertices_; private: diff --git a/source/blender/geometry/intern/uv_pack.cc b/source/blender/geometry/intern/uv_pack.cc index 4854809bd22..192706e7c87 100644 --- a/source/blender/geometry/intern/uv_pack.cc +++ b/source/blender/geometry/intern/uv_pack.cc @@ -93,6 +93,7 @@ PackIsland::PackIsland() { /* Initialize to the identity transform. */ aspect_y = 1.0f; + pinned = false; pre_translate = float2(0.0f); angle = 0.0f; caller_index = -31415927; /* Accidentally -pi */ @@ -152,6 +153,16 @@ void PackIsland::add_polygon(const blender::Span uvs, MemArena *arena, H BLI_heap_clear(heap, nullptr); } +static bool can_rotate(const Span islands, const UVPackIsland_Params ¶ms) +{ + for (const PackIsland *island : islands) { + if (!island->can_rotate_(params)) { + return false; + } + } + return true; +} + /** Angle rounding helper for "D4" transforms. */ static float angle_match(float angle_radians, float target_radians) { @@ -177,7 +188,7 @@ static float plusminus_90_angle(float angle_radians) void PackIsland::calculate_pre_rotation_(const UVPackIsland_Params ¶ms) { pre_rotate_ = 0.0f; - if (!params.rotate) { + if (!can_rotate_(params)) { return; /* Nothing to do. */ } @@ -300,7 +311,7 @@ UVPackIsland_Params::UVPackIsland_Params() only_selected_faces = false; use_seams = false; correct_aspect = false; - ignore_pinned = false; + pin_method = ED_UVPACK_PIN_NORMAL; pin_unselected = false; merge_overlap = false; margin = 0.001f; @@ -585,13 +596,14 @@ static void pack_gobel(const Span aabbs, /* Attempt to find an "Optimal" packing of the islands, e.g. assuming squares or circles. */ static void pack_island_optimal_pack(const Span aabbs, const UVPackIsland_Params ¶ms, + const bool all_can_rotate, MutableSpan r_phis, float *r_max_u, float *r_max_v) { *r_max_u = 0.0f; *r_max_v = 0.0f; - if (!params.rotate) { + if (!all_can_rotate) { /* Alpaca will produce an optimal layout when the inputs are uniform squares. */ pack_islands_alpaca_turbo(0, aabbs, params.target_aspect_y, r_phis, r_max_u, r_max_v); return; @@ -647,9 +659,6 @@ static void pack_island_optimal_pack(const Span aabbs, /* Wrapper around #BLI_box_pack_2d. */ static void pack_island_box_pack_2d(const Span aabbs, - const Span islands, - const float scale, - const float margin, const float target_aspect_y, MutableSpan r_phis, float *r_max_u, @@ -657,14 +666,13 @@ static void pack_island_box_pack_2d(const Span aabbs, { /* Allocate storage. */ BoxPack *box_array = static_cast( - MEM_mallocN(sizeof(*box_array) * islands.size(), __func__)); + MEM_mallocN(sizeof(*box_array) * aabbs.size(), __func__)); /* Prepare for box_pack_2d. */ for (const int64_t i : aabbs.index_range()) { - PackIsland *island = islands[aabbs[i]->index]; BoxPack *box = box_array + i; - box->w = (island->half_diagonal_.x * 2 * scale + 2 * margin) / target_aspect_y; - box->h = island->half_diagonal_.y * 2 * scale + 2 * margin; + box->w = aabbs[i]->uv_diagonal.x / target_aspect_y; + box->h = aabbs[i]->uv_diagonal.y; } const bool sort_boxes = false; /* Use existing ordering from `aabbs`. */ @@ -677,7 +685,6 @@ static void pack_island_box_pack_2d(const Span aabbs, if (std::max(box_max_u / target_aspect_y, box_max_v) < std::max(*r_max_u / target_aspect_y, *r_max_v)) { - *r_max_u = box_max_u; *r_max_v = box_max_v; /* Write back box_pack UVs. */ @@ -876,7 +883,7 @@ float Occupancy::trace_island(const PackIsland *island, const float margin, const bool write) const { - float2 diagonal_support = island->get_diagonal_support(scale, phi.rotation, margin); + const float2 diagonal_support = island->get_diagonal_support(scale, phi.rotation, margin); if (!write) { if (phi.translation.x < diagonal_support.x || phi.translation.y < diagonal_support.y) { @@ -888,17 +895,20 @@ float Occupancy::trace_island(const PackIsland *island, float2 pivot_transformed; mul_v2_m2v2(pivot_transformed, matrix, island->pivot_); - float2 delta = phi.translation - pivot_transformed; - uint vert_count = uint(island->triangle_vertices_.size()); /* `uint` is faster than `int`. */ + /* TODO: Support `ED_UVPACK_SHAPE_AABB`. */ + + const float2 delta = phi.translation - pivot_transformed; + const uint vert_count = uint( + island->triangle_vertices_.size()); /* `uint` is faster than `int`. */ for (uint i = 0; i < vert_count; i += 3) { - uint j = (i + triangle_hint_) % vert_count; + const uint j = (i + triangle_hint_) % vert_count; float2 uv0; float2 uv1; float2 uv2; mul_v2_m2v2(uv0, matrix, island->triangle_vertices_[j]); mul_v2_m2v2(uv1, matrix, island->triangle_vertices_[j + 1]); mul_v2_m2v2(uv2, matrix, island->triangle_vertices_[j + 2]); - float extent = trace_triangle(uv0 + delta, uv1 + delta, uv2 + delta, margin, write); + const float extent = trace_triangle(uv0 + delta, uv1 + delta, uv2 + delta, margin, write); if (!write && extent >= 0.0f) { triangle_hint_ = j; @@ -994,9 +1004,9 @@ class UVMinimumEnclosingSquareFinder { /** Calculates the square associated with a rotation of `angle`. * \return Size of square. */ - float update(const float angle) + float update(const double angle) { - float2 dir(cosf(angle), sinf(angle)); + float2 dir(cos(angle), sin(angle)); /* TODO: Once convexhull_2d bugs are fixed, we can use "rotating calipers" to go faster. */ rctf bounds; @@ -1069,6 +1079,10 @@ static bool rotate_inside_square(const Span island_indices, if (params.shape_method == ED_UVPACK_SHAPE_AABB) { /* AABB margin calculations are not preserved under rotations. */ if (island_indices.size() > 1) { /* Unless there's only one island...*/ + + if (params.target_aspect_y != 1.0f) { + /* TODO: Check for possible 90 degree rotation. */ + } return false; } } @@ -1085,8 +1099,8 @@ static bool rotate_inside_square(const Span island_indices, if (island->aspect_y != aspect_y) { return false; /* Aspect ratios are not preserved under rotation. */ } - - island->build_transformation(scale, r_phis[i].rotation, matrix); + const float island_scale = island->can_scale_(params) ? scale : 1.0f; + island->build_transformation(island_scale, r_phis[i].rotation, matrix); float2 pivot_transformed; mul_v2_m2v2(pivot_transformed, matrix, island->pivot_); float2 delta = r_phis[i].translation - pivot_transformed; @@ -1112,12 +1126,12 @@ static bool rotate_inside_square(const Span island_indices, return false; /* Nothing to do. */ } - /* Can use islands[0] because all islands have the same aspect_ratio. */ - islands[0]->build_transformation(scale, square_finder.best_angle, matrix); - /* Transform phis. */ for (const int64_t j : island_indices.index_range()) { const int64_t i = island_indices[j]->index; + const PackIsland *island = islands[i]; + const float island_scale = island->can_scale_(params) ? scale : 1.0f; + island->build_transformation(island_scale, square_finder.best_angle, matrix); r_phis[i].rotation += square_finder.best_angle; mul_m2_v2(matrix, r_phis[i].translation); r_phis[i].translation.x -= square_finder.best_bounds.xmin; @@ -1163,6 +1177,7 @@ static void pack_island_xatlas(const Span island_indices, int scan_line = 0; /* Current "scan_line" of occupancy bitmap. */ int traced_islands = 0; /* Which islands are currently traced in `occupancy`. */ int i = 0; + bool placed_can_rotate = true; /* The following `while` loop is setting up a three-way race: * `for (scan_line = 0; scan_line < bitmap_radix; scan_line++)` @@ -1175,20 +1190,43 @@ static void pack_island_xatlas(const Span island_indices, while (traced_islands < i) { /* Trace an island that's been solved. (Greedy.) */ const int64_t island_index = island_indices[traced_islands]->index; - occupancy.trace_island(islands[island_index], r_phis[island_index], scale, margin, true); + PackIsland *island = islands[island_index]; + const float island_scale = island->can_scale_(params) ? scale : 1.0f; + occupancy.trace_island(island, r_phis[island_index], island_scale, margin, true); traced_islands++; } PackIsland *island = islands[island_indices[i]->index]; + if (!island->can_translate_(params)) { + uv_phi phi; + phi.translation = island->pivot_; + phi.rotation = 0.0f; + r_phis[island_indices[i]->index] = phi; + i++; + placed_can_rotate = false; + continue; + } + const float island_scale = island->can_scale_(params) ? scale : 1.0f; uv_phi phi; int max_90_multiple = 1; - if (params.rotate && i && (i < 50)) { - max_90_multiple = 4; + if (island->can_rotate_(params)) { + if (i && (i < 50)) { + max_90_multiple = 4; + } } + else { + placed_can_rotate = false; + } + for (int angle_90_multiple = 0; angle_90_multiple < max_90_multiple; angle_90_multiple++) { - phi = find_best_fit_for_island( - island, scan_line, occupancy, scale, angle_90_multiple, margin, params.target_aspect_y); + phi = find_best_fit_for_island(island, + scan_line, + occupancy, + island_scale, + angle_90_multiple, + margin, + params.target_aspect_y); if (phi.is_valid()) { break; } @@ -1227,7 +1265,7 @@ static void pack_island_xatlas(const Span island_indices, r_phis[island_indices[i]->index] = phi; i++; /* Next island. */ - if (i == square_milestone) { + if (i == square_milestone && placed_can_rotate) { if (rotate_inside_square(island_indices.take_front(i), islands, params, @@ -1244,7 +1282,8 @@ static void pack_island_xatlas(const Span island_indices, } /* Update top-right corner. */ - float2 top_right = island->get_diagonal_support(scale, phi.rotation, margin) + phi.translation; + float2 top_right = island->get_diagonal_support(island_scale, phi.rotation, margin) + + phi.translation; max_u = std::max(top_right.x, max_u); max_v = std::max(top_right.y, max_v); @@ -1295,48 +1334,74 @@ static float pack_islands_scale_margin(const Span islands, * - Call #pack_islands_alpaca_* on the remaining islands. */ + const bool all_can_rotate = can_rotate(islands, params); + bool all_can_translate = true; + /* First, copy information from our input into the AABB structure. */ Array aabbs(islands.size()); for (const int64_t i : islands.index_range()) { PackIsland *pack_island = islands[i]; + float island_scale = scale; + if (!pack_island->can_scale_(params)) { + island_scale = 1.0f; + all_can_translate = false; + } UVAABBIsland *aabb = new UVAABBIsland(); aabb->index = i; - aabb->uv_diagonal.x = pack_island->half_diagonal_.x * 2 * scale + 2 * margin; - aabb->uv_diagonal.y = pack_island->half_diagonal_.y * 2 * scale + 2 * margin; + aabb->uv_diagonal.x = pack_island->half_diagonal_.x * 2 * island_scale + 2 * margin; + aabb->uv_diagonal.y = pack_island->half_diagonal_.y * 2 * island_scale + 2 * margin; aabb->aspect_y = pack_island->aspect_y; aabbs[i] = aabb; } /* Sort from "biggest" to "smallest". */ - if (params.rotate) { - std::stable_sort(aabbs.begin(), aabbs.end(), [](const UVAABBIsland *a, const UVAABBIsland *b) { - /* Choose the AABB with the longest large edge. */ - float a_u = a->uv_diagonal.x * a->aspect_y; - float a_v = a->uv_diagonal.y; - float b_u = b->uv_diagonal.x * b->aspect_y; - float b_v = b->uv_diagonal.y; - if (a_u > a_v) { - std::swap(a_u, a_v); - } - if (b_u > b_v) { - std::swap(b_u, b_v); - } - float diff_u = a_u - b_u; - float diff_v = a_v - b_v; - diff_v += diff_u * 0.05f; /* Robust sort, smooth over round-off errors. */ - if (diff_v == 0.0f) { /* Tie break. */ - return diff_u > 0.0f; - } - return diff_v > 0.0f; - }); + if (all_can_rotate) { + std::stable_sort(aabbs.begin(), + aabbs.end(), + [¶ms, &islands](const UVAABBIsland *a, const UVAABBIsland *b) { + const bool can_translate_a = islands[a->index]->can_translate_(params); + const bool can_translate_b = islands[b->index]->can_translate_(params); + if (can_translate_a != can_translate_b) { + return can_translate_b; /* Locked islands are placed first. */ + } + /* TODO: Fix when (params.target_aspect_y != 1.0f) */ + + /* Choose the AABB with the longest large edge. */ + float a_u = a->uv_diagonal.x * a->aspect_y; + float a_v = a->uv_diagonal.y; + float b_u = b->uv_diagonal.x * b->aspect_y; + float b_v = b->uv_diagonal.y; + if (a_u > a_v) { + std::swap(a_u, a_v); + } + if (b_u > b_v) { + std::swap(b_u, b_v); + } + float diff_u = a_u - b_u; + float diff_v = a_v - b_v; + diff_v += diff_u * 0.05f; /* Robust sort, smooth over round-off errors. */ + if (diff_v == 0.0f) { /* Tie break. */ + return diff_u > 0.0f; + } + return diff_v > 0.0f; + }); } else { - std::stable_sort(aabbs.begin(), aabbs.end(), [](const UVAABBIsland *a, const UVAABBIsland *b) { - /* Choose the AABB with larger rectangular area. */ - return b->uv_diagonal.x * b->uv_diagonal.y < a->uv_diagonal.x * a->uv_diagonal.y; - }); + std::stable_sort(aabbs.begin(), + aabbs.end(), + [¶ms, &islands](const UVAABBIsland *a, const UVAABBIsland *b) { + const bool can_translate_a = islands[a->index]->can_translate_(params); + const bool can_translate_b = islands[b->index]->can_translate_(params); + if (can_translate_a != can_translate_b) { + return can_translate_b; /* Locked islands are placed first. */ + } + + /* Choose the AABB with larger rectangular area. */ + return b->uv_diagonal.x * b->uv_diagonal.y < + a->uv_diagonal.x * a->uv_diagonal.y; + }); } /* Partition `islands`, largest islands will go to a slow packer, the rest alpaca_turbo. @@ -1352,8 +1417,14 @@ static float pack_islands_scale_margin(const Span islands, float optimal_pack_u = 0.0f; float optimal_pack_v = 0.0f; - pack_island_optimal_pack( - aabbs.as_span().take_front(max_box_pack), params, r_phis, &optimal_pack_u, &optimal_pack_v); + if (all_can_translate) { + pack_island_optimal_pack(aabbs.as_span().take_front(max_box_pack), + params, + all_can_rotate, + r_phis, + &optimal_pack_u, + &optimal_pack_v); + } /* Call xatlas (slow for large N.) */ float max_u = 1e30f; @@ -1375,36 +1446,37 @@ static float pack_islands_scale_margin(const Span islands, } /* Call box_pack_2d (slow for large N.) */ - pack_island_box_pack_2d(aabbs.as_span().take_front(max_box_pack), - islands, - scale, - margin, - params.target_aspect_y, - r_phis, - &max_u, - &max_v); + if (all_can_translate) { + pack_island_box_pack_2d( + aabbs.as_span().take_front(max_box_pack), params.target_aspect_y, r_phis, &max_u, &max_v); - if (std::max(optimal_pack_u / params.target_aspect_y, optimal_pack_v) < - std::max(max_u / params.target_aspect_y, max_v)) - { - - pack_island_optimal_pack( - aabbs.as_span().take_front(max_box_pack), params, r_phis, &max_u, &max_v); + if (std::max(optimal_pack_u / params.target_aspect_y, optimal_pack_v) < + std::max(max_u / params.target_aspect_y, max_v)) + { + pack_island_optimal_pack(aabbs.as_span().take_front(max_box_pack), + params, + all_can_rotate, + r_phis, + &max_u, + &max_v); + } } /* At this stage, `max_u` and `max_v` contain the box_pack/xatlas UVs. */ - rotate_inside_square(aabbs.as_span().take_front(max_box_pack), - islands, - params, - scale, - margin, - r_phis, - &max_u, - &max_v); + if (all_can_rotate) { + rotate_inside_square(aabbs.as_span().take_front(max_box_pack), + islands, + params, + scale, + margin, + r_phis, + &max_u, + &max_v); + } /* Call Alpaca. */ - if (params.rotate) { + if (all_can_rotate) { pack_islands_alpaca_rotate( max_box_pack, aabbs, params.target_aspect_y, r_phis, &max_u, &max_v); } @@ -1422,6 +1494,9 @@ static float pack_islands_margin_fraction(const Span &islands, const float margin_fraction, const UVPackIsland_Params ¶ms) { + + /* TODO: Support Pack To Original Bounding Box */ + /* * Root finding using a combined search / modified-secant method. * First, use a robust search procedure to bracket the root within a factor of 10. @@ -1501,6 +1576,9 @@ static float pack_islands_margin_fraction(const Span &islands, scale_low = scale; value_low = value; phis_low = phis_target; + if (value == 0.0f) { + break; /* Target hit exactly. */ + } } else { scale_high = scale; @@ -1519,7 +1597,9 @@ static float pack_islands_margin_fraction(const Span &islands, if (phis_low) { /* Write back best pack as a side-effect. */ for (const int64_t i : islands.index_range()) { - islands[i]->place_(scale_low, (*phis_low)[i]); + PackIsland *island = islands[i]; + const float island_scale = island->can_scale_(params) ? scale_low : 1.0f; + island->place_(island_scale, (*phis_low)[i]); } } return scale_low; @@ -1607,6 +1687,7 @@ class OverlapMerger { PackIsland *result = new PackIsland(); result->aspect_y = sqrtf(a->aspect_y * b->aspect_y); result->caller_index = -1; + result->pinned = a->pinned || b->pinned; add_geometry(result, a); add_geometry(result, b); result->calculate_pivot_(); @@ -1714,24 +1795,36 @@ float pack_islands(const Span &islands, const UVPackIsland_Params BLI_assert_unreachable(); } + /* TODO: Only line-search if *some* islands can scale and *some* are locked. */ + switch (params.pin_method) { + case ED_UVPACK_PIN_LOCK_ALL: + case ED_UVPACK_PIN_LOCK_SCALE: + case ED_UVPACK_PIN_LOCK_ROTATION_SCALE: + return pack_islands_margin_fraction(islands, margin, params); + default: + break; + } + blender::Array phis(islands.size()); const float scale = 1.0f; const float max_uv = pack_islands_scale_margin(islands, scale, margin, params, phis); + const float result = params.scale_to_fit ? 1.0f / max_uv : 1.0f; for (const int64_t i : islands.index_range()) { + BLI_assert(result == 1.0f || islands[i]->can_scale_(params)); islands[i]->place_(scale, phis[i]); } - return params.scale_to_fit ? 1.0f / max_uv : 1.0f; + return result; } /** \} */ void PackIsland::build_transformation(const float scale, - const float angle, + const double angle, float (*r_matrix)[2]) const { - const float cos_angle = cosf(angle); - const float sin_angle = sinf(angle); + const double cos_angle = cos(angle); + const double sin_angle = sin(angle); r_matrix[0][0] = cos_angle * scale; r_matrix[0][1] = -sin_angle * scale * aspect_y; r_matrix[1][0] = sin_angle * scale / aspect_y; @@ -1745,11 +1838,11 @@ void PackIsland::build_transformation(const float scale, } void PackIsland::build_inverse_transformation(const float scale, - const float angle, + const double angle, float (*r_matrix)[2]) const { - const float cos_angle = cosf(angle); - const float sin_angle = sinf(angle); + const double cos_angle = cos(angle); + const double sin_angle = sin(angle); r_matrix[0][0] = cos_angle / scale; r_matrix[0][1] = sin_angle / scale * aspect_y; @@ -1763,4 +1856,54 @@ void PackIsland::build_inverse_transformation(const float scale, #endif } +bool PackIsland::can_rotate_(const UVPackIsland_Params ¶ms) const +{ + if (!params.rotate) { + return false; + } + if (!pinned) { + return true; + } + switch (params.pin_method) { + case ED_UVPACK_PIN_LOCK_ALL: + case ED_UVPACK_PIN_LOCK_ROTATION: + case ED_UVPACK_PIN_LOCK_ROTATION_SCALE: + return false; + default: + return true; + } +} + +bool PackIsland::can_scale_(const UVPackIsland_Params ¶ms) const +{ + if (!params.scale_to_fit) { + return false; + } + if (!pinned) { + return true; + } + switch (params.pin_method) { + case ED_UVPACK_PIN_LOCK_ALL: + case ED_UVPACK_PIN_LOCK_SCALE: + case ED_UVPACK_PIN_LOCK_ROTATION_SCALE: + return false; + default: + return true; + } +} + +bool PackIsland::can_translate_(const UVPackIsland_Params ¶ms) const +{ + if (!pinned) { + return true; + } + switch (params.pin_method) { + case ED_UVPACK_PIN_LOCK_ALL: + case ED_UVPACK_PIN_LOCK_TRANSLATION: + return false; + default: + return true; + } +} + } // namespace blender::geometry diff --git a/source/blender/geometry/intern/uv_parametrizer.cc b/source/blender/geometry/intern/uv_parametrizer.cc index 43cb17f1a72..6b71e89c962 100644 --- a/source/blender/geometry/intern/uv_parametrizer.cc +++ b/source/blender/geometry/intern/uv_parametrizer.cc @@ -343,8 +343,12 @@ static void fix_large_angle(const float v_fix[3], *r_a2 += fix_amount * (1.0f - weight); } -static void p_triangle_angles( - const float v1[3], const float v2[3], const float v3[3], double *r_a1, double *r_a2, double *r_a3) +static void p_triangle_angles(const float v1[3], + const float v2[3], + const float v3[3], + double *r_a1, + double *r_a2, + double *r_a3) { *r_a1 = angle_v3v3v3(v3, v1, v2); *r_a2 = angle_v3v3v3(v1, v2, v3); @@ -4122,6 +4126,7 @@ void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, boo geometry::PackIsland *pack_island = new geometry::PackIsland(); pack_island->caller_index = i; pack_island->aspect_y = handle->aspect_y; + pack_island->pinned = chart->has_pins; for (PFace *f = chart->faces; f; f = f->nextlink) { PVert *v0 = f->edge->vert; @@ -4137,10 +4142,11 @@ void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, boo for (const int64_t i : pack_island_vector.index_range()) { PackIsland *pack_island = pack_island_vector[i]; + const float island_scale = pack_island->can_scale_(params) ? scale : 1.0f; PChart *chart = handle->charts[pack_island->caller_index]; float matrix[2][2]; - pack_island->build_transformation(scale, pack_island->angle, matrix); + pack_island->build_transformation(island_scale, pack_island->angle, matrix); for (PVert *v = chart->verts; v; v = v->nextlink) { blender::geometry::mul_v2_m2_add_v2v2(v->uv, matrix, v->uv, pack_island->pre_translate); }