Fix #78396: Pack UVs to original bounding box
Adds the ability to pack UVs back into the original bounding box. Choose UV Editor > Menu > UV > Pack Islands Then change "Pack To" to "Original bounding box"
This commit is contained in:
parent
531f99ffbd
commit
957ac41237
@ -361,12 +361,6 @@ int bm_mesh_calc_uv_islands(const Scene *scene,
|
||||
const float aspect_y,
|
||||
BMUVOffsets offsets);
|
||||
|
||||
struct UVMapUDIM_Params {
|
||||
const struct Image *image;
|
||||
/** Copied from #SpaceImage.tile_grid_shape */
|
||||
int grid_shape[2];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if UV coordinates lie on a valid tile in UDIM grid or tiled image.
|
||||
*/
|
||||
|
@ -1287,15 +1287,17 @@ static bool island_has_pins(const Scene *scene,
|
||||
* \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 udim_source_closest: UDIM source SpaceImage.
|
||||
* \param original_selection: Pack to original selection.
|
||||
* \param params: Parameters and options to pass to the packing engine.
|
||||
*/
|
||||
static void uvedit_pack_islands_multi(const Scene *scene,
|
||||
Object **objects,
|
||||
const int objects_len,
|
||||
BMesh **bmesh_override,
|
||||
const UVMapUDIM_Params *closest_udim,
|
||||
const blender::geometry::UVPackIsland_Params *params)
|
||||
const SpaceImage *udim_source_closest,
|
||||
const bool original_selection,
|
||||
blender::geometry::UVPackIsland_Params *params)
|
||||
{
|
||||
blender::Vector<FaceIsland *> island_vector;
|
||||
|
||||
@ -1357,12 +1359,10 @@ static void uvedit_pack_islands_multi(const Scene *scene,
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -1375,9 +1375,15 @@ static void uvedit_pack_islands_multi(const Scene *scene,
|
||||
|
||||
/* 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;
|
||||
mid_v2_v2v2(selection_center, selection_min_co, selection_max_co);
|
||||
|
||||
if (original_selection) {
|
||||
/* Protect against degenerate source AABB. */
|
||||
if ((selection_max_co[0] - selection_min_co[0]) * (selection_max_co[1] - selection_min_co[1]) >
|
||||
1e-40f) {
|
||||
params->target_aspect_y = (selection_max_co[0] - selection_min_co[0]) /
|
||||
(selection_max_co[1] - selection_min_co[1]);
|
||||
}
|
||||
}
|
||||
|
||||
MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
|
||||
@ -1415,14 +1421,15 @@ static void uvedit_pack_islands_multi(const Scene *scene,
|
||||
}
|
||||
BLI_heap_free(heap, nullptr);
|
||||
BLI_memarena_free(arena);
|
||||
|
||||
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;
|
||||
if (udim_source_closest) {
|
||||
const Image *image = udim_source_closest->image;
|
||||
const int *udim_grid = udim_source_closest->tile_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) {
|
||||
@ -1470,6 +1477,20 @@ static void uvedit_pack_islands_multi(const Scene *scene,
|
||||
/* Perform the transformation. */
|
||||
island_uv_transform(island, matrix, pre_translate);
|
||||
|
||||
if (original_selection) {
|
||||
const float rescale_x = (selection_max_co[0] - selection_min_co[0]) /
|
||||
params->target_aspect_y;
|
||||
const float rescale_y = (selection_max_co[1] - selection_min_co[1]);
|
||||
const float rescale = std::min(rescale_x, rescale_y);
|
||||
matrix[0][0] = rescale;
|
||||
matrix[0][1] = 0.0f;
|
||||
matrix[1][0] = 0.0f;
|
||||
matrix[1][1] = rescale;
|
||||
pre_translate[0] = selection_min_co[0] / rescale;
|
||||
pre_translate[1] = selection_min_co[1] / rescale;
|
||||
island_uv_transform(island, matrix, pre_translate);
|
||||
}
|
||||
|
||||
/* Cleanup memory. */
|
||||
pack_island_vector[i] = nullptr;
|
||||
delete pack_island;
|
||||
@ -1494,7 +1515,8 @@ static void uvedit_pack_islands_multi(const Scene *scene,
|
||||
/* Packing targets. */
|
||||
enum {
|
||||
PACK_UDIM_SRC_CLOSEST = 0,
|
||||
PACK_UDIM_SRC_ACTIVE = 1,
|
||||
PACK_UDIM_SRC_ACTIVE,
|
||||
PACK_ORIGINAL_AABB,
|
||||
};
|
||||
|
||||
static int pack_islands_exec(bContext *C, wmOperator *op)
|
||||
@ -1539,21 +1561,17 @@ static int pack_islands_exec(bContext *C, wmOperator *op)
|
||||
pack_island_params.shape_method = eUVPackIsland_ShapeMethod(
|
||||
RNA_enum_get(op->ptr, "shape_method"));
|
||||
|
||||
UVMapUDIM_Params closest_udim_buf;
|
||||
UVMapUDIM_Params *closest_udim = nullptr;
|
||||
if (udim_source == PACK_UDIM_SRC_ACTIVE) {
|
||||
pack_island_params.setUDIMOffsetFromSpaceImage(sima);
|
||||
}
|
||||
else if (sima) {
|
||||
BLI_assert(udim_source == PACK_UDIM_SRC_CLOSEST);
|
||||
closest_udim = &closest_udim_buf;
|
||||
closest_udim->image = sima->image;
|
||||
closest_udim->grid_shape[0] = sima->tile_grid_shape[0];
|
||||
closest_udim->grid_shape[1] = sima->tile_grid_shape[1];
|
||||
}
|
||||
|
||||
uvedit_pack_islands_multi(
|
||||
scene, objects, objects_len, nullptr, closest_udim, &pack_island_params);
|
||||
uvedit_pack_islands_multi(scene,
|
||||
objects,
|
||||
objects_len,
|
||||
nullptr,
|
||||
(udim_source == PACK_UDIM_SRC_CLOSEST) ? sima : nullptr,
|
||||
(udim_source == PACK_ORIGINAL_AABB),
|
||||
&pack_island_params);
|
||||
|
||||
MEM_freeN(objects);
|
||||
return OPERATOR_FINISHED;
|
||||
@ -1591,6 +1609,11 @@ void UV_OT_pack_islands(wmOperatorType *ot)
|
||||
0,
|
||||
"Active UDIM",
|
||||
"Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"},
|
||||
{PACK_ORIGINAL_AABB,
|
||||
"ORIGINAL_AABB",
|
||||
0,
|
||||
"Original bounding box",
|
||||
"Pack to starting bounding box of islands"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
/* identifiers */
|
||||
@ -2336,7 +2359,8 @@ void ED_uvedit_live_unwrap(const Scene *scene, Object **objects, int objects_len
|
||||
pack_island_params.margin_method = ED_UVPACK_MARGIN_SCALED;
|
||||
pack_island_params.margin = scene->toolsettings->uvcalc_margin;
|
||||
|
||||
uvedit_pack_islands_multi(scene, objects, objects_len, nullptr, nullptr, &pack_island_params);
|
||||
uvedit_pack_islands_multi(
|
||||
scene, objects, objects_len, nullptr, nullptr, false, &pack_island_params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2478,7 +2502,8 @@ static int unwrap_exec(bContext *C, wmOperator *op)
|
||||
RNA_enum_get(op->ptr, "margin_method"));
|
||||
pack_island_params.margin = RNA_float_get(op->ptr, "margin");
|
||||
|
||||
uvedit_pack_islands_multi(scene, objects, objects_len, nullptr, nullptr, &pack_island_params);
|
||||
uvedit_pack_islands_multi(
|
||||
scene, objects, objects_len, nullptr, nullptr, false, &pack_island_params);
|
||||
|
||||
MEM_freeN(objects);
|
||||
|
||||
@ -2859,7 +2884,7 @@ static int smart_project_exec(bContext *C, wmOperator *op)
|
||||
params.margin = RNA_float_get(op->ptr, "island_margin");
|
||||
|
||||
uvedit_pack_islands_multi(
|
||||
scene, objects_changed, object_changed_len, nullptr, nullptr, ¶ms);
|
||||
scene, objects_changed, object_changed_len, nullptr, nullptr, false, ¶ms);
|
||||
|
||||
/* #uvedit_pack_islands_multi only supports `per_face_aspect = false`. */
|
||||
const bool per_face_aspect = false;
|
||||
@ -3847,7 +3872,7 @@ void ED_uvedit_add_simple_uvs(Main *bmain, const Scene *scene, Object *ob)
|
||||
params.margin_method = ED_UVPACK_MARGIN_SCALED;
|
||||
params.margin = 0.001f;
|
||||
|
||||
uvedit_pack_islands_multi(scene, &ob, 1, &bm, nullptr, ¶ms);
|
||||
uvedit_pack_islands_multi(scene, &ob, 1, &bm, nullptr, false, ¶ms);
|
||||
|
||||
/* Write back from BMesh to Mesh. */
|
||||
BMeshToMeshParams bm_to_me_params{};
|
||||
|
@ -66,6 +66,8 @@ class UVPackIsland_Params {
|
||||
eUVPackIsland_MarginMethod margin_method;
|
||||
/** Additional translation for bottom left corner. */
|
||||
float udim_base_offset[2];
|
||||
/** Target aspect ratio. */
|
||||
float target_aspect_y;
|
||||
/** Which shape to use when packing. */
|
||||
eUVPackIsland_ShapeMethod shape_method;
|
||||
};
|
||||
|
@ -181,6 +181,7 @@ UVPackIsland_Params::UVPackIsland_Params()
|
||||
margin_method = ED_UVPACK_MARGIN_SCALED;
|
||||
udim_base_offset[0] = 0.0f;
|
||||
udim_base_offset[1] = 0.0f;
|
||||
target_aspect_y = 1.0f;
|
||||
shape_method = ED_UVPACK_SHAPE_AABB;
|
||||
}
|
||||
|
||||
@ -205,13 +206,14 @@ class UVAABBIsland {
|
||||
* the input must be pre-sorted, which costs an additional `O(nlogn)` time complexity.
|
||||
*/
|
||||
static void pack_islands_alpaca_turbo(const Span<UVAABBIsland *> islands,
|
||||
const float target_aspect_y,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
{
|
||||
/* Exclude an initial AABB near the origin. */
|
||||
float next_u1 = *r_max_u;
|
||||
float next_v1 = *r_max_v;
|
||||
bool zigzag = next_u1 < next_v1; /* Horizontal or Vertical strip? */
|
||||
bool zigzag = next_u1 / target_aspect_y < next_v1; /* Horizontal or Vertical strip? */
|
||||
|
||||
float u0 = zigzag ? next_u1 : 0.0f;
|
||||
float v0 = zigzag ? 0.0f : next_v1;
|
||||
@ -230,7 +232,7 @@ static void pack_islands_alpaca_turbo(const Span<UVAABBIsland *> islands,
|
||||
}
|
||||
if (restart) {
|
||||
/* We're at the end of a strip. Restart from U axis or V axis. */
|
||||
zigzag = next_u1 < next_v1;
|
||||
zigzag = next_u1 / target_aspect_y < next_v1;
|
||||
u0 = zigzag ? next_u1 : 0.0f;
|
||||
v0 = zigzag ? 0.0f : next_v1;
|
||||
}
|
||||
@ -262,6 +264,7 @@ static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
|
||||
const Span<PackIsland *> islands,
|
||||
const float scale,
|
||||
const float margin,
|
||||
const float target_aspect_y,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
{
|
||||
@ -273,21 +276,24 @@ static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
|
||||
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;
|
||||
box->w = (island->half_diagonal_.x * 2 * scale + 2 * margin) / target_aspect_y;
|
||||
box->h = island->half_diagonal_.y * 2 * scale + 2 * margin;
|
||||
}
|
||||
|
||||
const bool sort_boxes = false; /* Use existing ordering from `aabbs`. */
|
||||
|
||||
/* \note Writes to `*r_max_u` and `*r_max_v`. */
|
||||
BLI_box_pack_2d(box_array, int(islands.size()), sort_boxes, r_max_u, r_max_v);
|
||||
BLI_box_pack_2d(box_array, int(aabbs.size()), sort_boxes, r_max_u, r_max_v);
|
||||
|
||||
*r_max_u *= target_aspect_y;
|
||||
|
||||
/* Write back box_pack UVs. */
|
||||
for (const int64_t i : aabbs.index_range()) {
|
||||
PackIsland *island = islands[aabbs[i]->index];
|
||||
BoxPack *box = box_array + i;
|
||||
island->angle = 0.0f; /* #BLI_box_pack_2d never rotates. */
|
||||
island->pre_translate.x = (box->x + box->w * 0.5f) / scale - island->pivot_.x;
|
||||
island->pre_translate.x = (box->x + box->w * 0.5f) * target_aspect_y / scale -
|
||||
island->pivot_.x;
|
||||
island->pre_translate.y = (box->y + box->h * 0.5f) / scale - island->pivot_.y;
|
||||
}
|
||||
|
||||
@ -464,17 +470,26 @@ float Occupancy::trace_island(PackIsland *island,
|
||||
return -1.0f; /* Available. */
|
||||
}
|
||||
|
||||
static float2 find_best_fit_for_island(
|
||||
PackIsland *island, int scan_line, Occupancy &occupancy, const float scale, const float margin)
|
||||
static float2 find_best_fit_for_island(PackIsland *island,
|
||||
int scan_line,
|
||||
Occupancy &occupancy,
|
||||
const float scale,
|
||||
const float margin,
|
||||
const float target_aspect_y)
|
||||
{
|
||||
const float bitmap_scale = 1.0f / occupancy.bitmap_scale_reciprocal;
|
||||
const float half_x_scaled = island->half_diagonal_.x * scale;
|
||||
const float half_y_scaled = island->half_diagonal_.y * scale;
|
||||
|
||||
const float sqrt_target_aspect_y = sqrtf(target_aspect_y);
|
||||
int scan_line_x = int(scan_line * sqrt_target_aspect_y);
|
||||
int scan_line_y = int(scan_line / sqrt_target_aspect_y);
|
||||
|
||||
/* Scan using an "Alpaca"-style search, first horizontally using "less-than". */
|
||||
int t = int(ceilf((2 * half_x_scaled + margin) * occupancy.bitmap_scale_reciprocal));
|
||||
while (t < scan_line) {
|
||||
const float2 probe(t * bitmap_scale - half_x_scaled, scan_line * bitmap_scale - half_y_scaled);
|
||||
while (t < scan_line_x) {
|
||||
const float2 probe(t * bitmap_scale - half_x_scaled,
|
||||
scan_line_y * bitmap_scale - half_y_scaled);
|
||||
const float extent = occupancy.trace_island(island, scale, margin, probe, false);
|
||||
if (extent < 0.0f) {
|
||||
return probe; /* Success. */
|
||||
@ -484,8 +499,9 @@ static float2 find_best_fit_for_island(
|
||||
|
||||
/* Then scan vertically using "less-than-or-equal" */
|
||||
t = int(ceilf((2 * half_y_scaled + margin) * occupancy.bitmap_scale_reciprocal));
|
||||
while (t <= scan_line) {
|
||||
const float2 probe(scan_line * bitmap_scale - half_x_scaled, t * bitmap_scale - half_y_scaled);
|
||||
while (t <= scan_line_y) {
|
||||
const float2 probe(scan_line_x * bitmap_scale - half_x_scaled,
|
||||
t * bitmap_scale - half_y_scaled);
|
||||
const float extent = occupancy.trace_island(island, scale, margin, probe, false);
|
||||
if (extent < 0.0f) {
|
||||
return probe; /* Success. */
|
||||
@ -528,6 +544,7 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
|
||||
const Span<PackIsland *> islands,
|
||||
const float scale,
|
||||
const float margin,
|
||||
const float target_aspect_y,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
{
|
||||
@ -546,7 +563,8 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
|
||||
|
||||
while (i < island_indices.size()) {
|
||||
PackIsland *island = islands[island_indices[i]->index];
|
||||
const float2 best = find_best_fit_for_island(island, scan_line, occupancy, scale, margin);
|
||||
const float2 best = find_best_fit_for_island(
|
||||
island, scan_line, occupancy, scale, margin, target_aspect_y);
|
||||
|
||||
if (best.x <= -1.0f) {
|
||||
/* Unable to find a fit on this scan_line. */
|
||||
@ -562,7 +580,8 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
|
||||
* choice is 2. */
|
||||
scan_line += 2;
|
||||
}
|
||||
if (scan_line < occupancy.bitmap_radix) {
|
||||
if (scan_line <
|
||||
occupancy.bitmap_radix * sqrtf(std::min(target_aspect_y, 1.0f / target_aspect_y))) {
|
||||
continue; /* Try again on next scan_line. */
|
||||
}
|
||||
|
||||
@ -654,13 +673,14 @@ static void update_hole_rotate(float2 &hole,
|
||||
* Tracking the "Hole" has a slight performance cost, while improving packing efficiency.
|
||||
*/
|
||||
static void pack_islands_alpaca_rotate(const Span<UVAABBIsland *> islands,
|
||||
const float target_aspect_y,
|
||||
float *r_max_u,
|
||||
float *r_max_v)
|
||||
{
|
||||
/* Exclude an initial AABB near the origin. */
|
||||
float next_u1 = *r_max_u;
|
||||
float next_v1 = *r_max_v;
|
||||
bool zigzag = next_u1 < next_v1; /* Horizontal or Vertical strip? */
|
||||
bool zigzag = next_u1 / target_aspect_y < next_v1; /* Horizontal or Vertical strip? */
|
||||
|
||||
/* Track an AABB "hole" which may be filled at any time. */
|
||||
float2 hole(0.0f);
|
||||
@ -716,7 +736,7 @@ static void pack_islands_alpaca_rotate(const Span<UVAABBIsland *> islands,
|
||||
if (restart) {
|
||||
update_hole_rotate(hole, hole_diagonal, hole_rotate, u0, v0, next_u1, next_v1);
|
||||
/* We're at the end of a strip. Restart from U axis or V axis. */
|
||||
zigzag = next_u1 < next_v1;
|
||||
zigzag = next_u1 / target_aspect_y < next_v1;
|
||||
u0 = zigzag ? next_u1 : 0.0f;
|
||||
v0 = zigzag ? 0.0f : next_v1;
|
||||
}
|
||||
@ -842,7 +862,6 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
|
||||
alpaca_cutoff = alpaca_cutoff_fast;
|
||||
}
|
||||
}
|
||||
|
||||
const int64_t max_box_pack = std::min(alpaca_cutoff, islands.size());
|
||||
|
||||
/* Call box_pack_2d (slow for large N.) */
|
||||
@ -851,23 +870,35 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
|
||||
switch (params.shape_method) {
|
||||
case ED_UVPACK_SHAPE_CONVEX:
|
||||
case ED_UVPACK_SHAPE_CONCAVE:
|
||||
pack_island_xatlas(
|
||||
aabbs.as_span().take_front(max_box_pack), islands, scale, margin, &max_u, &max_v);
|
||||
pack_island_xatlas(aabbs.as_span().take_front(max_box_pack),
|
||||
islands,
|
||||
scale,
|
||||
margin,
|
||||
params.target_aspect_y,
|
||||
&max_u,
|
||||
&max_v);
|
||||
break;
|
||||
default:
|
||||
pack_island_box_pack_2d(
|
||||
aabbs.as_span().take_front(max_box_pack), islands, scale, margin, &max_u, &max_v);
|
||||
pack_island_box_pack_2d(aabbs.as_span().take_front(max_box_pack),
|
||||
islands,
|
||||
scale,
|
||||
margin,
|
||||
params.target_aspect_y,
|
||||
&max_u,
|
||||
&max_v);
|
||||
break;
|
||||
}
|
||||
|
||||
/* At this stage, `max_u` and `max_v` contain the box_pack UVs. */
|
||||
/* At this stage, `max_u` and `max_v` contain the box_pack/xatlas UVs. */
|
||||
|
||||
/* Call Alpaca. */
|
||||
if (params.rotate && !contains_non_square_islands) {
|
||||
pack_islands_alpaca_rotate(aabbs.as_mutable_span().drop_front(max_box_pack), &max_u, &max_v);
|
||||
pack_islands_alpaca_rotate(
|
||||
aabbs.as_mutable_span().drop_front(max_box_pack), params.target_aspect_y, &max_u, &max_v);
|
||||
}
|
||||
else {
|
||||
pack_islands_alpaca_turbo(aabbs.as_mutable_span().drop_front(max_box_pack), &max_u, &max_v);
|
||||
pack_islands_alpaca_turbo(
|
||||
aabbs.as_mutable_span().drop_front(max_box_pack), params.target_aspect_y, &max_u, &max_v);
|
||||
}
|
||||
|
||||
/* Write back Alpaca UVs. */
|
||||
@ -888,7 +919,7 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
|
||||
delete aabb;
|
||||
}
|
||||
|
||||
return std::max(max_u, max_v);
|
||||
return std::max(max_u / params.target_aspect_y, max_v);
|
||||
}
|
||||
|
||||
/** Find the optimal scale to pack islands into the unit square.
|
||||
|
@ -444,10 +444,11 @@ static void uv_parametrizer_scale_x(ParamHandle *phandle, const float scale_x)
|
||||
return; /* Identity transform. */
|
||||
}
|
||||
|
||||
/* Scale every chart. */
|
||||
for (int i = 0; i < phandle->ncharts; i++) {
|
||||
PChart *chart = phandle->charts[i];
|
||||
for (PVert *v = chart->verts; v; v = v->nextlink) {
|
||||
v->uv[0] *= scale_x;
|
||||
v->uv[0] *= scale_x; /* Only scale x axis. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user