forked from blender/blender
main sync #3
@ -26,7 +26,6 @@ struct Object;
|
||||
struct Scene;
|
||||
struct SpaceImage;
|
||||
struct ToolSettings;
|
||||
struct UVPackIsland_Params;
|
||||
struct View2D;
|
||||
struct ViewLayer;
|
||||
struct bContext;
|
||||
@ -356,26 +355,6 @@ bool uv_coords_isect_udim(const struct Image *image,
|
||||
const int udim_grid[2],
|
||||
const float coords[2]);
|
||||
|
||||
/**
|
||||
* Pack UV islands from multiple objects.
|
||||
*
|
||||
* \param scene: Scene containing the objects to be packed.
|
||||
* \param objects: Array of Objects to pack.
|
||||
* \param objects_len: Length of `objects` array.
|
||||
* \param bmesh_override: BMesh array aligned with `objects`.
|
||||
* Optional, when non-null this overrides object's BMesh.
|
||||
* This is needed to perform UV packing on objects that aren't in edit-mode.
|
||||
* \param udim_params: Parameters to specify UDIM target and UDIM source image.
|
||||
* \param params: Parameters and options to pass to the packing engine.
|
||||
*
|
||||
*/
|
||||
void ED_uvedit_pack_islands_multi(const struct Scene *scene,
|
||||
Object **objects,
|
||||
uint objects_len,
|
||||
struct BMesh **bmesh_override,
|
||||
const struct UVMapUDIM_Params *closest_udim,
|
||||
const struct UVPackIsland_Params *params);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -11,223 +11,14 @@
|
||||
* This API uses #BMesh data structures and doesn't have limitations for manifold meshes.
|
||||
*/
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_meshdata_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
#include "BLI_boxpack_2d.h"
|
||||
#include "BLI_convexhull_2d.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math.h"
|
||||
#include "BLI_rect.h"
|
||||
|
||||
#include "BKE_customdata.h"
|
||||
#include "BKE_editmesh.h"
|
||||
#include "BKE_image.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
#include "BLI_math.h"
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
|
||||
#include "ED_uvedit.h" /* Own include. */
|
||||
|
||||
#include "GEO_uv_pack.hh"
|
||||
|
||||
#include "WM_api.h"
|
||||
#include "WM_types.h"
|
||||
|
||||
#include "bmesh.h"
|
||||
|
||||
static void mul_v2_m2_add_v2v2(float r[2],
|
||||
const float mat[2][2],
|
||||
const float a[2],
|
||||
const float b[2])
|
||||
{
|
||||
/* Compute `r = mat * (a + b)` with high precision. */
|
||||
const double x = double(a[0]) + double(b[0]);
|
||||
const double y = double(a[1]) + double(b[1]);
|
||||
|
||||
r[0] = float(mat[0][0] * x + mat[1][0] * y);
|
||||
r[1] = float(mat[0][1] * x + mat[1][1] * y);
|
||||
}
|
||||
|
||||
static void island_uv_transform(FaceIsland *island,
|
||||
const float matrix[2][2], /* Scale and rotation. */
|
||||
const float pre_translate[2] /* (pre) Translation. */
|
||||
)
|
||||
{
|
||||
/* Use a pre-transform to compute `A * (x+b)`
|
||||
*
|
||||
* \note Ordinarily, we'd use a post_transform like `A * x + b`
|
||||
* In general, post-transforms are easier to work with when using homogenous co-ordinates.
|
||||
*
|
||||
* When UV mapping into the unit square, post-transforms can lose precision on small islands.
|
||||
* Instead we're using a pre-transform to maintain precision.
|
||||
*
|
||||
* To convert post-transform to pre-transform, use `A * x + b == A * (x + c), c = A^-1 * b`
|
||||
*/
|
||||
|
||||
const int cd_loop_uv_offset = island->offsets.uv;
|
||||
const int faces_len = island->faces_len;
|
||||
for (int i = 0; i < faces_len; i++) {
|
||||
BMFace *f = island->faces[i];
|
||||
BMLoop *l;
|
||||
BMIter iter;
|
||||
BM_ITER_ELEM (l, &iter, f, BM_LOOPS_OF_FACE) {
|
||||
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
|
||||
mul_v2_m2_add_v2v2(luv, matrix, luv, pre_translate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name UV Face Array Utilities
|
||||
* \{ */
|
||||
|
||||
static void bm_face_array_calc_bounds(BMFace **faces,
|
||||
const int faces_len,
|
||||
const int cd_loop_uv_offset,
|
||||
rctf *r_bounds_rect)
|
||||
{
|
||||
BLI_assert(cd_loop_uv_offset >= 0);
|
||||
float bounds_min[2], bounds_max[2];
|
||||
INIT_MINMAX2(bounds_min, bounds_max);
|
||||
for (int i = 0; i < faces_len; i++) {
|
||||
BMFace *f = faces[i];
|
||||
BM_face_uv_minmax(f, bounds_min, bounds_max, cd_loop_uv_offset);
|
||||
}
|
||||
r_bounds_rect->xmin = bounds_min[0];
|
||||
r_bounds_rect->ymin = bounds_min[1];
|
||||
r_bounds_rect->xmax = bounds_max[0];
|
||||
r_bounds_rect->ymax = bounds_max[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of un-ordered UV coordinates,
|
||||
* without duplicating coordinates for loops that share a vertex.
|
||||
*/
|
||||
static float (*bm_face_array_calc_unique_uv_coords(
|
||||
BMFace **faces, int faces_len, const int cd_loop_uv_offset, int *r_coords_len))[2]
|
||||
{
|
||||
BLI_assert(cd_loop_uv_offset >= 0);
|
||||
int coords_len_alloc = 0;
|
||||
for (int i = 0; i < faces_len; i++) {
|
||||
BMFace *f = faces[i];
|
||||
BMLoop *l_iter, *l_first;
|
||||
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
|
||||
do {
|
||||
BM_elem_flag_enable(l_iter, BM_ELEM_TAG);
|
||||
} while ((l_iter = l_iter->next) != l_first);
|
||||
coords_len_alloc += f->len;
|
||||
}
|
||||
|
||||
float(*coords)[2] = static_cast<float(*)[2]>(
|
||||
MEM_mallocN(sizeof(*coords) * coords_len_alloc, __func__));
|
||||
int coords_len = 0;
|
||||
|
||||
for (int i = 0; i < faces_len; i++) {
|
||||
BMFace *f = faces[i];
|
||||
BMLoop *l_iter, *l_first;
|
||||
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
|
||||
do {
|
||||
if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) {
|
||||
/* Already walked over, continue. */
|
||||
continue;
|
||||
}
|
||||
|
||||
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
|
||||
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
|
||||
copy_v2_v2(coords[coords_len++], luv);
|
||||
|
||||
/* Un tag all connected so we don't add them twice.
|
||||
* Note that we will tag other loops not part of `faces` but this is harmless,
|
||||
* since we're only turning off a tag. */
|
||||
BMVert *v_pivot = l_iter->v;
|
||||
BMEdge *e_first = v_pivot->e;
|
||||
const BMEdge *e = e_first;
|
||||
do {
|
||||
if (e->l != nullptr) {
|
||||
const BMLoop *l_radial = e->l;
|
||||
do {
|
||||
if (l_radial->v == l_iter->v) {
|
||||
if (BM_elem_flag_test(l_radial, BM_ELEM_TAG)) {
|
||||
const float *luv_radial = BM_ELEM_CD_GET_FLOAT_P(l_radial, cd_loop_uv_offset);
|
||||
if (equals_v2v2(luv, luv_radial)) {
|
||||
/* Don't add this UV when met in another face in `faces`. */
|
||||
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ((l_radial = l_radial->radial_next) != e->l);
|
||||
}
|
||||
} while ((e = BM_DISK_EDGE_NEXT(e, v_pivot)) != e_first);
|
||||
} while ((l_iter = l_iter->next) != l_first);
|
||||
}
|
||||
*r_coords_len = coords_len;
|
||||
return coords;
|
||||
}
|
||||
|
||||
static void face_island_uv_rotate_fit_aabb(FaceIsland *island)
|
||||
{
|
||||
BMFace **faces = island->faces;
|
||||
const int faces_len = island->faces_len;
|
||||
const float aspect_y = island->aspect_y;
|
||||
const int cd_loop_uv_offset = island->offsets.uv;
|
||||
|
||||
/* Calculate unique coordinates since calculating a convex hull can be an expensive operation. */
|
||||
int coords_len;
|
||||
float(*coords)[2] = bm_face_array_calc_unique_uv_coords(
|
||||
faces, faces_len, cd_loop_uv_offset, &coords_len);
|
||||
|
||||
/* Correct aspect ratio. */
|
||||
if (aspect_y != 1.0f) {
|
||||
for (int i = 0; i < coords_len; i++) {
|
||||
coords[i][1] /= aspect_y;
|
||||
}
|
||||
}
|
||||
|
||||
float angle = BLI_convexhull_aabb_fit_points_2d(coords, coords_len);
|
||||
|
||||
/* Rotate coords by `angle` before computing bounding box. */
|
||||
if (angle != 0.0f) {
|
||||
float matrix[2][2];
|
||||
angle_to_mat2(matrix, angle);
|
||||
matrix[0][1] *= aspect_y;
|
||||
matrix[1][1] *= aspect_y;
|
||||
for (int i = 0; i < coords_len; i++) {
|
||||
mul_m2_v2(matrix, coords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute new AABB. */
|
||||
float bounds_min[2], bounds_max[2];
|
||||
INIT_MINMAX2(bounds_min, bounds_max);
|
||||
for (int i = 0; i < coords_len; i++) {
|
||||
minmax_v2v2_v2(bounds_min, bounds_max, coords[i]);
|
||||
}
|
||||
|
||||
float size[2];
|
||||
sub_v2_v2v2(size, bounds_max, bounds_min);
|
||||
if (size[1] < size[0]) {
|
||||
angle += DEG2RADF(90.0f);
|
||||
}
|
||||
|
||||
MEM_freeN(coords);
|
||||
|
||||
/* Apply rotation back to BMesh. */
|
||||
if (angle != 0.0f) {
|
||||
float matrix[2][2];
|
||||
float pre_translate[2] = {0, 0};
|
||||
angle_to_mat2(matrix, angle);
|
||||
matrix[1][0] *= 1.0f / aspect_y;
|
||||
/* matrix[1][1] *= aspect_y / aspect_y; */
|
||||
matrix[0][1] *= aspect_y;
|
||||
island_uv_transform(island, matrix, pre_translate);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name UDIM packing helper functions
|
||||
* \{ */
|
||||
@ -261,58 +52,6 @@ bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const floa
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number.
|
||||
*/
|
||||
static float uv_nearest_image_tile_distance(const Image *image,
|
||||
const float coords[2],
|
||||
float nearest_tile_co[2])
|
||||
{
|
||||
BKE_image_find_nearest_tile_with_offset(image, coords, nearest_tile_co);
|
||||
|
||||
/* Add 0.5 to get tile center coordinates. */
|
||||
float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
|
||||
add_v2_fl(nearest_tile_center_co, 0.5f);
|
||||
|
||||
return len_squared_v2v2(coords, nearest_tile_center_co);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number.
|
||||
*/
|
||||
static float uv_nearest_grid_tile_distance(const int udim_grid[2],
|
||||
const float coords[2],
|
||||
float nearest_tile_co[2])
|
||||
{
|
||||
const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
|
||||
|
||||
if (coords[0] > udim_grid[0]) {
|
||||
nearest_tile_co[0] = udim_grid[0] - 1;
|
||||
}
|
||||
else if (coords[0] < 0) {
|
||||
nearest_tile_co[0] = 0;
|
||||
}
|
||||
else {
|
||||
nearest_tile_co[0] = coords_floor[0];
|
||||
}
|
||||
|
||||
if (coords[1] > udim_grid[1]) {
|
||||
nearest_tile_co[1] = udim_grid[1] - 1;
|
||||
}
|
||||
else if (coords[1] < 0) {
|
||||
nearest_tile_co[1] = 0;
|
||||
}
|
||||
else {
|
||||
nearest_tile_co[1] = coords_floor[1];
|
||||
}
|
||||
|
||||
/* Add 0.5 to get tile center coordinates. */
|
||||
float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
|
||||
add_v2_fl(nearest_tile_center_co, 0.5f);
|
||||
|
||||
return len_squared_v2v2(coords, nearest_tile_center_co);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@ -436,211 +175,3 @@ int bm_mesh_calc_uv_islands(const Scene *scene,
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
static bool island_has_pins(const Scene *scene,
|
||||
FaceIsland *island,
|
||||
const UVPackIsland_Params *params)
|
||||
{
|
||||
const bool pin_unselected = params->pin_unselected;
|
||||
const bool only_selected_faces = params->only_selected_faces;
|
||||
BMLoop *l;
|
||||
BMIter iter;
|
||||
const int pin_offset = island->offsets.pin;
|
||||
for (int i = 0; i < island->faces_len; i++) {
|
||||
BMFace *efa = island->faces[i];
|
||||
if (pin_unselected && only_selected_faces && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
|
||||
return true;
|
||||
}
|
||||
BM_ITER_ELEM (l, &iter, island->faces[i], BM_LOOPS_OF_FACE) {
|
||||
if (BM_ELEM_CD_GET_BOOL(l, pin_offset)) {
|
||||
return true;
|
||||
}
|
||||
if (pin_unselected && !uvedit_uv_select_test(scene, l, island->offsets)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Public UV Island Packing
|
||||
*
|
||||
* \note This behavior loosely follows #geometry::uv_parametrizer_pack.
|
||||
* \{ */
|
||||
|
||||
void ED_uvedit_pack_islands_multi(const Scene *scene,
|
||||
Object **objects,
|
||||
const uint objects_len,
|
||||
BMesh **bmesh_override,
|
||||
const UVMapUDIM_Params *closest_udim,
|
||||
const UVPackIsland_Params *params)
|
||||
{
|
||||
blender::Vector<FaceIsland *> island_vector;
|
||||
|
||||
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
||||
Object *obedit = objects[ob_index];
|
||||
BMesh *bm = nullptr;
|
||||
if (bmesh_override) {
|
||||
/* Note: obedit is still required for aspect ratio and ID_RECALC_GEOMETRY. */
|
||||
bm = bmesh_override[ob_index];
|
||||
}
|
||||
else {
|
||||
BMEditMesh *em = BKE_editmesh_from_object(obedit);
|
||||
bm = em->bm;
|
||||
}
|
||||
BLI_assert(bm);
|
||||
|
||||
const BMUVOffsets offsets = BM_uv_map_get_offsets(bm);
|
||||
if (offsets.uv == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float aspect_y = params->correct_aspect ? ED_uvedit_get_aspect_y(obedit) : 1.0f;
|
||||
|
||||
bool only_selected_faces = params->only_selected_faces;
|
||||
bool only_selected_uvs = params->only_selected_uvs;
|
||||
if (params->ignore_pinned && params->pin_unselected) {
|
||||
only_selected_faces = false;
|
||||
only_selected_uvs = false;
|
||||
}
|
||||
ListBase island_list = {nullptr};
|
||||
bm_mesh_calc_uv_islands(scene,
|
||||
bm,
|
||||
&island_list,
|
||||
only_selected_faces,
|
||||
only_selected_uvs,
|
||||
params->use_seams,
|
||||
aspect_y,
|
||||
offsets);
|
||||
|
||||
/* 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)) {
|
||||
MEM_freeN(island->faces);
|
||||
MEM_freeN(island);
|
||||
continue;
|
||||
}
|
||||
island_vector.append(island);
|
||||
}
|
||||
}
|
||||
|
||||
if (island_vector.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Coordinates of bounding box containing all selected UVs. */
|
||||
float selection_min_co[2], selection_max_co[2];
|
||||
INIT_MINMAX2(selection_min_co, selection_max_co);
|
||||
|
||||
for (int index = 0; index < island_vector.size(); index++) {
|
||||
FaceIsland *island = island_vector[index];
|
||||
if (closest_udim) {
|
||||
/* Only calculate selection bounding box if using closest_udim. */
|
||||
for (int i = 0; i < island->faces_len; i++) {
|
||||
BMFace *f = island->faces[i];
|
||||
BM_face_uv_minmax(f, selection_min_co, selection_max_co, island->offsets.uv);
|
||||
}
|
||||
}
|
||||
|
||||
if (params->rotate) {
|
||||
face_island_uv_rotate_fit_aabb(island);
|
||||
}
|
||||
|
||||
bm_face_array_calc_bounds(
|
||||
island->faces, island->faces_len, island->offsets.uv, &island->bounds_rect);
|
||||
}
|
||||
|
||||
/* Center of bounding box containing all selected UVs. */
|
||||
float selection_center[2];
|
||||
if (closest_udim) {
|
||||
selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f;
|
||||
selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f;
|
||||
}
|
||||
|
||||
float scale[2] = {1.0f, 1.0f};
|
||||
blender::Vector<blender::geometry::PackIsland *> pack_island_vector;
|
||||
for (int i = 0; i < island_vector.size(); i++) {
|
||||
FaceIsland *face_island = island_vector[i];
|
||||
blender::geometry::PackIsland *pack_island = new blender::geometry::PackIsland();
|
||||
pack_island->bounds_rect = face_island->bounds_rect;
|
||||
pack_island_vector.append(pack_island);
|
||||
}
|
||||
BoxPack *box_array = pack_islands(pack_island_vector, *params, scale);
|
||||
|
||||
float base_offset[2] = {0.0f, 0.0f};
|
||||
copy_v2_v2(base_offset, params->udim_base_offset);
|
||||
|
||||
if (closest_udim) {
|
||||
const Image *image = closest_udim->image;
|
||||
const int *udim_grid = closest_udim->grid_shape;
|
||||
/* Check if selection lies on a valid UDIM grid tile. */
|
||||
bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center);
|
||||
if (is_valid_udim) {
|
||||
base_offset[0] = floorf(selection_center[0]);
|
||||
base_offset[1] = floorf(selection_center[1]);
|
||||
}
|
||||
/* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */
|
||||
else {
|
||||
float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX};
|
||||
float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX;
|
||||
if (image) {
|
||||
nearest_image_tile_dist = uv_nearest_image_tile_distance(
|
||||
image, selection_center, nearest_image_tile_co);
|
||||
}
|
||||
|
||||
float nearest_grid_tile_co[2] = {0.0f, 0.0f};
|
||||
nearest_grid_tile_dist = uv_nearest_grid_tile_distance(
|
||||
udim_grid, selection_center, nearest_grid_tile_co);
|
||||
|
||||
base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
|
||||
nearest_image_tile_co[0] :
|
||||
nearest_grid_tile_co[0];
|
||||
base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
|
||||
nearest_image_tile_co[1] :
|
||||
nearest_grid_tile_co[1];
|
||||
}
|
||||
}
|
||||
|
||||
float matrix[2][2];
|
||||
float matrix_inverse[2][2];
|
||||
float pre_translate[2];
|
||||
for (int i = 0; i < island_vector.size(); i++) {
|
||||
FaceIsland *island = island_vector[box_array[i].index];
|
||||
matrix[0][0] = scale[0];
|
||||
matrix[0][1] = 0.0f;
|
||||
matrix[1][0] = 0.0f;
|
||||
matrix[1][1] = scale[1];
|
||||
invert_m2_m2(matrix_inverse, matrix);
|
||||
|
||||
/* Add base_offset, post transform. */
|
||||
mul_v2_m2v2(pre_translate, matrix_inverse, base_offset);
|
||||
|
||||
/* Translate to box_array from bounds_rect. */
|
||||
pre_translate[0] += box_array[i].x - island->bounds_rect.xmin;
|
||||
pre_translate[1] += box_array[i].y - island->bounds_rect.ymin;
|
||||
island_uv_transform(island, matrix, pre_translate);
|
||||
}
|
||||
|
||||
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
||||
Object *obedit = objects[ob_index];
|
||||
DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data);
|
||||
}
|
||||
|
||||
for (FaceIsland *island : island_vector) {
|
||||
MEM_freeN(island->faces);
|
||||
MEM_freeN(island);
|
||||
}
|
||||
|
||||
for (int i = 0; i < pack_island_vector.size(); i++) {
|
||||
blender::geometry::PackIsland *pack_island = pack_island_vector[i];
|
||||
pack_island_vector[i] = nullptr;
|
||||
delete pack_island;
|
||||
}
|
||||
|
||||
MEM_freeN(box_array);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_convexhull_2d.h"
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math.h"
|
||||
@ -1016,6 +1017,455 @@ void UV_OT_minimize_stretch(wmOperatorType *ot)
|
||||
|
||||
/** \} */
|
||||
|
||||
/** Compute `r = mat * (a + b)` with high precision. */
|
||||
static void mul_v2_m2_add_v2v2(float r[2],
|
||||
const float mat[2][2],
|
||||
const float a[2],
|
||||
const float b[2])
|
||||
{
|
||||
const double x = double(a[0]) + double(b[0]);
|
||||
const double y = double(a[1]) + double(b[1]);
|
||||
|
||||
r[0] = float(mat[0][0] * x + mat[1][0] * y);
|
||||
r[1] = float(mat[0][1] * x + mat[1][1] * y);
|
||||
}
|
||||
|
||||
static void island_uv_transform(FaceIsland *island,
|
||||
const float matrix[2][2], /* Scale and rotation. */
|
||||
const float pre_translate[2] /* (pre) Translation. */
|
||||
)
|
||||
{
|
||||
/* Use a pre-transform to compute `A * (x+b)`
|
||||
*
|
||||
* \note Ordinarily, we'd use a post_transform like `A * x + b`
|
||||
* In general, post-transforms are easier to work with when using homogenous co-ordinates.
|
||||
*
|
||||
* When UV mapping into the unit square, post-transforms can lose precision on small islands.
|
||||
* Instead we're using a pre-transform to maintain precision.
|
||||
*
|
||||
* To convert post-transform to pre-transform, use `A * x + b == A * (x + c), c = A^-1 * b`
|
||||
*/
|
||||
|
||||
const int cd_loop_uv_offset = island->offsets.uv;
|
||||
const int faces_len = island->faces_len;
|
||||
for (int i = 0; i < faces_len; i++) {
|
||||
BMFace *f = island->faces[i];
|
||||
BMLoop *l;
|
||||
BMIter iter;
|
||||
BM_ITER_ELEM (l, &iter, f, BM_LOOPS_OF_FACE) {
|
||||
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
|
||||
mul_v2_m2_add_v2v2(luv, matrix, luv, pre_translate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bm_face_array_calc_bounds(BMFace **faces,
|
||||
const int faces_len,
|
||||
const int cd_loop_uv_offset,
|
||||
rctf *r_bounds_rect)
|
||||
{
|
||||
BLI_assert(cd_loop_uv_offset >= 0);
|
||||
float bounds_min[2], bounds_max[2];
|
||||
INIT_MINMAX2(bounds_min, bounds_max);
|
||||
for (int i = 0; i < faces_len; i++) {
|
||||
BMFace *f = faces[i];
|
||||
BM_face_uv_minmax(f, bounds_min, bounds_max, cd_loop_uv_offset);
|
||||
}
|
||||
r_bounds_rect->xmin = bounds_min[0];
|
||||
r_bounds_rect->ymin = bounds_min[1];
|
||||
r_bounds_rect->xmax = bounds_max[0];
|
||||
r_bounds_rect->ymax = bounds_max[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of un-ordered UV coordinates,
|
||||
* without duplicating coordinates for loops that share a vertex.
|
||||
*/
|
||||
static float (*bm_face_array_calc_unique_uv_coords(
|
||||
BMFace **faces, int faces_len, const int cd_loop_uv_offset, int *r_coords_len))[2]
|
||||
{
|
||||
BLI_assert(cd_loop_uv_offset >= 0);
|
||||
int coords_len_alloc = 0;
|
||||
for (int i = 0; i < faces_len; i++) {
|
||||
BMFace *f = faces[i];
|
||||
BMLoop *l_iter, *l_first;
|
||||
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
|
||||
do {
|
||||
BM_elem_flag_enable(l_iter, BM_ELEM_TAG);
|
||||
} while ((l_iter = l_iter->next) != l_first);
|
||||
coords_len_alloc += f->len;
|
||||
}
|
||||
|
||||
float(*coords)[2] = static_cast<float(*)[2]>(
|
||||
MEM_mallocN(sizeof(*coords) * coords_len_alloc, __func__));
|
||||
int coords_len = 0;
|
||||
|
||||
for (int i = 0; i < faces_len; i++) {
|
||||
BMFace *f = faces[i];
|
||||
BMLoop *l_iter, *l_first;
|
||||
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
|
||||
do {
|
||||
if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) {
|
||||
/* Already walked over, continue. */
|
||||
continue;
|
||||
}
|
||||
|
||||
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
|
||||
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
|
||||
copy_v2_v2(coords[coords_len++], luv);
|
||||
|
||||
/* Un tag all connected so we don't add them twice.
|
||||
* Note that we will tag other loops not part of `faces` but this is harmless,
|
||||
* since we're only turning off a tag. */
|
||||
BMVert *v_pivot = l_iter->v;
|
||||
BMEdge *e_first = v_pivot->e;
|
||||
const BMEdge *e = e_first;
|
||||
do {
|
||||
if (e->l != nullptr) {
|
||||
const BMLoop *l_radial = e->l;
|
||||
do {
|
||||
if (l_radial->v == l_iter->v) {
|
||||
if (BM_elem_flag_test(l_radial, BM_ELEM_TAG)) {
|
||||
const float *luv_radial = BM_ELEM_CD_GET_FLOAT_P(l_radial, cd_loop_uv_offset);
|
||||
if (equals_v2v2(luv, luv_radial)) {
|
||||
/* Don't add this UV when met in another face in `faces`. */
|
||||
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while ((l_radial = l_radial->radial_next) != e->l);
|
||||
}
|
||||
} while ((e = BM_DISK_EDGE_NEXT(e, v_pivot)) != e_first);
|
||||
} while ((l_iter = l_iter->next) != l_first);
|
||||
}
|
||||
*r_coords_len = coords_len;
|
||||
return coords;
|
||||
}
|
||||
|
||||
static void face_island_uv_rotate_fit_aabb(FaceIsland *island)
|
||||
{
|
||||
BMFace **faces = island->faces;
|
||||
const int faces_len = island->faces_len;
|
||||
const float aspect_y = island->aspect_y;
|
||||
const int cd_loop_uv_offset = island->offsets.uv;
|
||||
|
||||
/* Calculate unique coordinates since calculating a convex hull can be an expensive operation. */
|
||||
int coords_len;
|
||||
float(*coords)[2] = bm_face_array_calc_unique_uv_coords(
|
||||
faces, faces_len, cd_loop_uv_offset, &coords_len);
|
||||
|
||||
/* Correct aspect ratio. */
|
||||
if (aspect_y != 1.0f) {
|
||||
for (int i = 0; i < coords_len; i++) {
|
||||
coords[i][1] /= aspect_y;
|
||||
}
|
||||
}
|
||||
|
||||
float angle = BLI_convexhull_aabb_fit_points_2d(coords, coords_len);
|
||||
|
||||
/* Rotate coords by `angle` before computing bounding box. */
|
||||
if (angle != 0.0f) {
|
||||
float matrix[2][2];
|
||||
angle_to_mat2(matrix, angle);
|
||||
matrix[0][1] *= aspect_y;
|
||||
matrix[1][1] *= aspect_y;
|
||||
for (int i = 0; i < coords_len; i++) {
|
||||
mul_m2_v2(matrix, coords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute new AABB. */
|
||||
float bounds_min[2], bounds_max[2];
|
||||
INIT_MINMAX2(bounds_min, bounds_max);
|
||||
for (int i = 0; i < coords_len; i++) {
|
||||
minmax_v2v2_v2(bounds_min, bounds_max, coords[i]);
|
||||
}
|
||||
|
||||
float size[2];
|
||||
sub_v2_v2v2(size, bounds_max, bounds_min);
|
||||
if (size[1] < size[0]) {
|
||||
angle += DEG2RADF(90.0f);
|
||||
}
|
||||
|
||||
MEM_freeN(coords);
|
||||
|
||||
/* Apply rotation back to BMesh. */
|
||||
if (angle != 0.0f) {
|
||||
float matrix[2][2];
|
||||
float pre_translate[2] = {0, 0};
|
||||
angle_to_mat2(matrix, angle);
|
||||
matrix[1][0] *= 1.0f / aspect_y;
|
||||
/* matrix[1][1] *= aspect_y / aspect_y; */
|
||||
matrix[0][1] *= aspect_y;
|
||||
island_uv_transform(island, matrix, pre_translate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number.
|
||||
*/
|
||||
static float uv_nearest_image_tile_distance(const Image *image,
|
||||
const float coords[2],
|
||||
float nearest_tile_co[2])
|
||||
{
|
||||
BKE_image_find_nearest_tile_with_offset(image, coords, nearest_tile_co);
|
||||
|
||||
/* Add 0.5 to get tile center coordinates. */
|
||||
float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
|
||||
add_v2_fl(nearest_tile_center_co, 0.5f);
|
||||
|
||||
return len_squared_v2v2(coords, nearest_tile_center_co);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number.
|
||||
*/
|
||||
static float uv_nearest_grid_tile_distance(const int udim_grid[2],
|
||||
const float coords[2],
|
||||
float nearest_tile_co[2])
|
||||
{
|
||||
const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
|
||||
|
||||
if (coords[0] > udim_grid[0]) {
|
||||
nearest_tile_co[0] = udim_grid[0] - 1;
|
||||
}
|
||||
else if (coords[0] < 0) {
|
||||
nearest_tile_co[0] = 0;
|
||||
}
|
||||
else {
|
||||
nearest_tile_co[0] = coords_floor[0];
|
||||
}
|
||||
|
||||
if (coords[1] > udim_grid[1]) {
|
||||
nearest_tile_co[1] = udim_grid[1] - 1;
|
||||
}
|
||||
else if (coords[1] < 0) {
|
||||
nearest_tile_co[1] = 0;
|
||||
}
|
||||
else {
|
||||
nearest_tile_co[1] = coords_floor[1];
|
||||
}
|
||||
|
||||
/* Add 0.5 to get tile center coordinates. */
|
||||
float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
|
||||
add_v2_fl(nearest_tile_center_co, 0.5f);
|
||||
|
||||
return len_squared_v2v2(coords, nearest_tile_center_co);
|
||||
}
|
||||
|
||||
static bool island_has_pins(const Scene *scene,
|
||||
FaceIsland *island,
|
||||
const UVPackIsland_Params *params)
|
||||
{
|
||||
const bool pin_unselected = params->pin_unselected;
|
||||
const bool only_selected_faces = params->only_selected_faces;
|
||||
BMLoop *l;
|
||||
BMIter iter;
|
||||
const int pin_offset = island->offsets.pin;
|
||||
for (int i = 0; i < island->faces_len; i++) {
|
||||
BMFace *efa = island->faces[i];
|
||||
if (pin_unselected && only_selected_faces && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
|
||||
return true;
|
||||
}
|
||||
BM_ITER_ELEM (l, &iter, island->faces[i], BM_LOOPS_OF_FACE) {
|
||||
if (BM_ELEM_CD_GET_BOOL(l, pin_offset)) {
|
||||
return true;
|
||||
}
|
||||
if (pin_unselected && !uvedit_uv_select_test(scene, l, island->offsets)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack UV islands from multiple objects.
|
||||
*
|
||||
* \param scene: Scene containing the objects to be packed.
|
||||
* \param objects: Array of Objects to pack.
|
||||
* \param objects_len: Length of `objects` array.
|
||||
* \param bmesh_override: BMesh array aligned with `objects`.
|
||||
* Optional, when non-null this overrides object's BMesh.
|
||||
* This is needed to perform UV packing on objects that aren't in edit-mode.
|
||||
* \param udim_params: Parameters to specify UDIM target and UDIM source image.
|
||||
* \param params: Parameters and options to pass to the packing engine.
|
||||
*
|
||||
*/
|
||||
void ED_uvedit_pack_islands_multi(const Scene *scene,
|
||||
Object **objects,
|
||||
const int objects_len,
|
||||
BMesh **bmesh_override,
|
||||
const UVMapUDIM_Params *closest_udim,
|
||||
const UVPackIsland_Params *params)
|
||||
{
|
||||
blender::Vector<FaceIsland *> island_vector;
|
||||
|
||||
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
||||
Object *obedit = objects[ob_index];
|
||||
BMesh *bm = nullptr;
|
||||
if (bmesh_override) {
|
||||
/* Note: obedit is still required for aspect ratio and ID_RECALC_GEOMETRY. */
|
||||
bm = bmesh_override[ob_index];
|
||||
}
|
||||
else {
|
||||
BMEditMesh *em = BKE_editmesh_from_object(obedit);
|
||||
bm = em->bm;
|
||||
}
|
||||
BLI_assert(bm);
|
||||
|
||||
const BMUVOffsets offsets = BM_uv_map_get_offsets(bm);
|
||||
if (offsets.uv == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float aspect_y = params->correct_aspect ? ED_uvedit_get_aspect_y(obedit) : 1.0f;
|
||||
|
||||
bool only_selected_faces = params->only_selected_faces;
|
||||
bool only_selected_uvs = params->only_selected_uvs;
|
||||
if (params->ignore_pinned && params->pin_unselected) {
|
||||
only_selected_faces = false;
|
||||
only_selected_uvs = false;
|
||||
}
|
||||
ListBase island_list = {nullptr};
|
||||
bm_mesh_calc_uv_islands(scene,
|
||||
bm,
|
||||
&island_list,
|
||||
only_selected_faces,
|
||||
only_selected_uvs,
|
||||
params->use_seams,
|
||||
aspect_y,
|
||||
offsets);
|
||||
|
||||
/* 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)) {
|
||||
MEM_freeN(island->faces);
|
||||
MEM_freeN(island);
|
||||
continue;
|
||||
}
|
||||
island_vector.append(island);
|
||||
}
|
||||
}
|
||||
|
||||
if (island_vector.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Coordinates of bounding box containing all selected UVs. */
|
||||
float selection_min_co[2], selection_max_co[2];
|
||||
INIT_MINMAX2(selection_min_co, selection_max_co);
|
||||
|
||||
for (int index = 0; index < island_vector.size(); index++) {
|
||||
FaceIsland *island = island_vector[index];
|
||||
if (closest_udim) {
|
||||
/* Only calculate selection bounding box if using closest_udim. */
|
||||
for (int i = 0; i < island->faces_len; i++) {
|
||||
BMFace *f = island->faces[i];
|
||||
BM_face_uv_minmax(f, selection_min_co, selection_max_co, island->offsets.uv);
|
||||
}
|
||||
}
|
||||
|
||||
if (params->rotate) {
|
||||
face_island_uv_rotate_fit_aabb(island);
|
||||
}
|
||||
|
||||
bm_face_array_calc_bounds(
|
||||
island->faces, island->faces_len, island->offsets.uv, &island->bounds_rect);
|
||||
}
|
||||
|
||||
/* Center of bounding box containing all selected UVs. */
|
||||
float selection_center[2];
|
||||
if (closest_udim) {
|
||||
selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f;
|
||||
selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f;
|
||||
}
|
||||
|
||||
float scale[2] = {1.0f, 1.0f};
|
||||
blender::Vector<blender::geometry::PackIsland *> pack_island_vector;
|
||||
for (int i = 0; i < island_vector.size(); i++) {
|
||||
FaceIsland *face_island = island_vector[i];
|
||||
blender::geometry::PackIsland *pack_island = new blender::geometry::PackIsland();
|
||||
pack_island->bounds_rect = face_island->bounds_rect;
|
||||
pack_island_vector.append(pack_island);
|
||||
}
|
||||
BoxPack *box_array = pack_islands(pack_island_vector, *params, scale);
|
||||
|
||||
float base_offset[2] = {0.0f, 0.0f};
|
||||
copy_v2_v2(base_offset, params->udim_base_offset);
|
||||
|
||||
if (closest_udim) {
|
||||
const Image *image = closest_udim->image;
|
||||
const int *udim_grid = closest_udim->grid_shape;
|
||||
/* Check if selection lies on a valid UDIM grid tile. */
|
||||
bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center);
|
||||
if (is_valid_udim) {
|
||||
base_offset[0] = floorf(selection_center[0]);
|
||||
base_offset[1] = floorf(selection_center[1]);
|
||||
}
|
||||
/* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */
|
||||
else {
|
||||
float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX};
|
||||
float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX;
|
||||
if (image) {
|
||||
nearest_image_tile_dist = uv_nearest_image_tile_distance(
|
||||
image, selection_center, nearest_image_tile_co);
|
||||
}
|
||||
|
||||
float nearest_grid_tile_co[2] = {0.0f, 0.0f};
|
||||
nearest_grid_tile_dist = uv_nearest_grid_tile_distance(
|
||||
udim_grid, selection_center, nearest_grid_tile_co);
|
||||
|
||||
base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
|
||||
nearest_image_tile_co[0] :
|
||||
nearest_grid_tile_co[0];
|
||||
base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
|
||||
nearest_image_tile_co[1] :
|
||||
nearest_grid_tile_co[1];
|
||||
}
|
||||
}
|
||||
|
||||
float matrix[2][2];
|
||||
float matrix_inverse[2][2];
|
||||
float pre_translate[2];
|
||||
for (int i = 0; i < island_vector.size(); i++) {
|
||||
FaceIsland *island = island_vector[box_array[i].index];
|
||||
matrix[0][0] = scale[0];
|
||||
matrix[0][1] = 0.0f;
|
||||
matrix[1][0] = 0.0f;
|
||||
matrix[1][1] = scale[1];
|
||||
invert_m2_m2(matrix_inverse, matrix);
|
||||
|
||||
/* Add base_offset, post transform. */
|
||||
mul_v2_m2v2(pre_translate, matrix_inverse, base_offset);
|
||||
|
||||
/* Translate to box_array from bounds_rect. */
|
||||
pre_translate[0] += box_array[i].x - island->bounds_rect.xmin;
|
||||
pre_translate[1] += box_array[i].y - island->bounds_rect.ymin;
|
||||
island_uv_transform(island, matrix, pre_translate);
|
||||
}
|
||||
|
||||
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
||||
Object *obedit = objects[ob_index];
|
||||
DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, obedit->data);
|
||||
}
|
||||
|
||||
for (FaceIsland *island : island_vector) {
|
||||
MEM_freeN(island->faces);
|
||||
MEM_freeN(island);
|
||||
}
|
||||
|
||||
for (int i = 0; i < pack_island_vector.size(); i++) {
|
||||
blender::geometry::PackIsland *pack_island = pack_island_vector[i];
|
||||
pack_island_vector[i] = nullptr;
|
||||
delete pack_island;
|
||||
}
|
||||
|
||||
MEM_freeN(box_array);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Pack UV Islands Operator
|
||||
* \{ */
|
||||
|
@ -11,9 +11,6 @@
|
||||
* \ingroup geo
|
||||
*/
|
||||
|
||||
/** Workaround to forward-declare C type in C++ header. */
|
||||
extern "C" {
|
||||
|
||||
enum eUVPackIsland_MarginMethod {
|
||||
ED_UVPACK_MARGIN_SCALED = 0, /* Use scale of existing UVs to multiply margin. */
|
||||
ED_UVPACK_MARGIN_ADD, /* Just add the margin, ignoring any UV scale. */
|
||||
@ -43,7 +40,6 @@ struct UVPackIsland_Params {
|
||||
/** Additional translation for bottom left corner. */
|
||||
float udim_base_offset[2];
|
||||
};
|
||||
}
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user