Blender V5.0
transform_mode_resize.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <algorithm>
10#include <cstdlib>
11
13
14#include "BLI_math_matrix.h"
15#include "BLI_math_vector.h"
16#include "BLI_task.hh"
17
18#include "BKE_context.hh"
19
20#include "BKE_image.hh"
21#include "BKE_unit.hh"
22
23#include "BLT_translation.hh"
24
25#include "ED_screen.hh"
26
27#include "RNA_access.hh"
28
29#include "UI_interface.hh"
30
31#include "transform.hh"
33#include "transform_convert.hh"
34#include "transform_mode.hh"
35#include "transform_snap.hh"
36
37namespace blender::ed::transform {
38
39/* -------------------------------------------------------------------- */
42
43static float ResizeBetween(TransInfo *t, const float p1[3], const float p2[3])
44{
45 float d1[3], d2[3], len_d1;
46
47 sub_v3_v3v3(d1, p1, t->center_global);
48 sub_v3_v3v3(d2, p2, t->center_global);
49
50 if (t->con.applyRot != nullptr && (t->con.mode & CON_APPLY)) {
51 mul_m3_v3(t->con.pmtx, d1);
52 mul_m3_v3(t->con.pmtx, d2);
53 }
54
55 project_v3_v3v3(d1, d1, d2);
56
57 len_d1 = len_v3(d1);
58
59 /* Use 'invalid' dist when `center == p1` (after projecting),
60 * in this case scale will _never_ move the point in relation to the center,
61 * so it makes no sense to take it into account when scaling. see: #46503 */
62 return len_d1 != 0.0f ? len_v3(d2) / len_d1 : TRANSFORM_DIST_INVALID;
63}
64
65static void ApplySnapResize(TransInfo *t, float vec[3])
66{
67 float point[3];
68 getSnapPoint(t, point);
69
70 float dist = ResizeBetween(t, t->tsnap.snap_source, point);
71 if (dist != TRANSFORM_DIST_INVALID) {
72 copy_v3_fl(vec, dist);
73 }
74}
75
82static void constrain_scale_to_boundary(const float numerator,
83 const float denominator,
84 float *scale)
85{
86 /* It's possible the numerator or denominator can be very close to zero due to so-called
87 * "catastrophic cancellation". See #102923 for an example. We use epsilon tests here to
88 * distinguish between genuine negative coordinates versus coordinates that should be rounded off
89 * to zero. */
90 const float epsilon = 0.25f / 65536.0f; /* A quarter of a texel on a 65536 x 65536 texture. */
91 if (fabsf(denominator) < epsilon) {
92 /* The origin of the scale is very near the edge of the boundary. */
93 if (numerator < -epsilon) {
94 /* Negative scale will wrap around and put us outside the boundary. */
95 *scale = 0.0f; /* Hold at the boundary instead. */
96 }
97 return; /* Nothing else we can do without more info. */
98 }
99
100 const float correction = numerator / denominator;
101 if (correction < 0.0f || !isfinite(correction)) {
102 /* TODO: Correction is negative or invalid, but we lack context to fix `*scale`. */
103 return;
104 }
105
106 if (denominator < 0.0f) {
107 /* Scale origin is outside boundary, only make scale bigger. */
108 *scale = std::max(*scale, correction);
109 return;
110 }
111
112 /* Scale origin is inside boundary, the "regular" case, limit maximum scale. */
113 *scale = std::min(*scale, correction);
114}
115
116static bool clip_uv_transform_resize(TransInfo *t, float vec[2])
117{
118
119 /* Stores the coordinates of the closest UDIM tile.
120 * Also acts as an offset to the tile from the origin of UV space. */
121 float base_offset[2] = {0.0f, 0.0f};
122
123 /* If tiled image then constrain to correct/closest UDIM tile, else 0-1 UV space. */
124 const SpaceImage *sima = static_cast<const SpaceImage *>(t->area->spacedata.first);
126
127 /* Assume no change is required. */
128 float scale = 1.0f;
129
130 /* Are we scaling U and V together, or just one axis? */
131 const bool adjust_u = !(t->con.mode & CON_AXIS1);
132 const bool adjust_v = !(t->con.mode & CON_AXIS0);
133 const bool use_local_center = transdata_check_local_center(t, t->around);
135 for (TransData *td = tc->data; td < tc->data + tc->data_len; td++) {
136
137 /* Get scale origin. */
138 const float *scale_origin = use_local_center ? td->center : t->center_global;
139
140 /* Alias td->loc as min and max just in case we need to optimize later. */
141 const float *min = td->loc;
142 const float *max = td->loc;
143
144 if (adjust_u) {
145 /* Update U against the left border. */
147 scale_origin[0] - base_offset[0], scale_origin[0] - min[0], &scale);
148
149 /* Now the right border, negated, because `-1.0 / -1.0 = 1.0`. */
151 base_offset[0] + t->aspect[0] - scale_origin[0], max[0] - scale_origin[0], &scale);
152 }
153
154 /* Do the same for the V co-ordinate. */
155 if (adjust_v) {
157 scale_origin[1] - base_offset[1], scale_origin[1] - min[1], &scale);
158
160 base_offset[1] + t->aspect[1] - scale_origin[1], max[1] - scale_origin[1], &scale);
161 }
162 }
163 }
164 vec[0] *= scale;
165 vec[1] *= scale;
166 return scale != 1.0f;
167}
168
169static void applyResize(TransInfo *t)
170{
171 float mat[3][3];
172 int i;
173 char str[UI_MAX_DRAW_STR];
174
175 if (t->flag & T_INPUT_IS_VALUES_FINAL) {
177 }
178 else {
179 float ratio = t->values[0];
180
181 copy_v3_fl(t->values_final, ratio);
183
185
186 if (applyNumInput(&t->num, t->values_final)) {
188 }
189
191 }
192
193 size_to_mat3(mat, t->values_final);
194 if (t->con.mode & CON_APPLY) {
195 t->con.applySize(t, nullptr, nullptr, mat);
196
197 /* Only so we have re-usable value with redo. */
198 float pvec[3] = {0.0f, 0.0f, 0.0f};
199 int j = 0;
200 for (i = 0; i < 3; i++) {
201 if (!(t->con.mode & (CON_AXIS0 << i))) {
202 t->values_final[i] = 1.0f;
203 }
204 else {
205 pvec[j++] = t->values_final[i];
206 }
207 }
208 headerResize(t, pvec, str, sizeof(str));
209 }
210 else {
211 headerResize(t, t->values_final, str, sizeof(str));
212 }
213
214 copy_m3_m3(t->mat, mat); /* Used in gizmo. */
215
217 threading::parallel_for(IndexRange(tc->data_len), 1024, [&](const IndexRange range) {
218 for (const int i : range) {
219 TransData *td = &tc->data[i];
220 if (td->flag & TD_SKIP) {
221 continue;
222 }
223 ElementResize(t, tc, i, mat);
224 }
225 });
226 }
227
228 /* Evil hack - redo resize if clipping needed. */
229 if (t->flag & T_CLIP_UV && clip_uv_transform_resize(t, t->values_final)) {
230 size_to_mat3(mat, t->values_final);
231
232 if (t->con.mode & CON_APPLY) {
233 t->con.applySize(t, nullptr, nullptr, mat);
234 }
235
237 for (i = 0; i < tc->data_len; i++) {
238 ElementResize(t, tc, i, mat);
239 }
240
241 /* Not ideal, see #clipUVData code-comment. */
242 if (t->flag & T_PROP_EDIT) {
243 clipUVData(t);
244 }
245 }
246 }
247
248 recalc_data(t);
249
250 ED_area_status_text(t->area, str);
251}
252
253static void resize_transform_matrix_fn(TransInfo *t, float mat_xform[4][4])
254{
255 float mat4[4][4];
256 copy_m4_m3(mat4, t->mat);
258 mul_m4_m4m4(mat_xform, mat4, mat_xform);
259}
260
261static void initResize(TransInfo *t, wmOperator *op)
262{
263 float mouse_dir_constraint[3];
264 if (op) {
265 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "mouse_dir_constraint");
266 if (prop) {
267 RNA_property_float_get_array(op->ptr, prop, mouse_dir_constraint);
268 }
269 else {
270 /* Resize is expected to have this property. */
271 BLI_assert(!STREQ(op->idname, "TRANSFORM_OT_resize"));
272 }
273 }
274 else {
275 zero_v3(mouse_dir_constraint);
276 }
277
278 const bool only_location = transform_mode_affect_only_locations(t);
279 if (only_location) {
281 status.item(TIP_("Transform is set to only affect location"), ICON_ERROR);
282 }
283
284 if (is_zero_v3(mouse_dir_constraint)) {
286 }
287 else {
288 int mval_start[2], mval_end[2];
289 float mval_dir[3];
290 float viewmat[3][3];
291
292 copy_m3_m4(viewmat, t->viewmat);
293 mul_v3_m3v3(mval_dir, viewmat, mouse_dir_constraint);
294 normalize_v2(mval_dir);
295 if (is_zero_v2(mval_dir)) {
296 /* The screen space direction is orthogonal to the view.
297 * Fall back to constraining on the Y axis. */
298 mval_dir[0] = 0;
299 mval_dir[1] = 1;
300 }
301
302 mval_start[0] = t->center2d[0];
303 mval_start[1] = t->center2d[1];
304
305 float2 t_mval = t->mval - float2(t->center2d);
306 project_v2_v2v2(mval_dir, t_mval, mval_dir);
307
308 mval_end[0] = t->center2d[0] + mval_dir[0];
309 mval_end[1] = t->center2d[1] + mval_dir[1];
310
311 setCustomPoints(t, &t->mouse, mval_end, mval_start);
312
314 }
315
316 t->num.val_flag[0] |= NUM_NULL_ONE;
317 t->num.val_flag[1] |= NUM_NULL_ONE;
318 t->num.val_flag[2] |= NUM_NULL_ONE;
319 t->num.flag |= NUM_AFFECT_ALL;
320 if ((t->flag & T_EDIT) == 0) {
321#ifdef USE_NUM_NO_ZERO
322 t->num.val_flag[0] |= NUM_NO_ZERO;
323 t->num.val_flag[1] |= NUM_NO_ZERO;
324 t->num.val_flag[2] |= NUM_NO_ZERO;
325#endif
326 }
327
328 t->idx_max = 2;
329 t->num.idx_max = 2;
330 t->increment = float3(0.1f);
331 t->increment_precision = 0.1f;
332
333 copy_v3_fl(t->num.val_inc, t->increment[0]);
334 t->num.unit_sys = t->scene->unit.system;
335 t->num.unit_type[0] = B_UNIT_NONE;
336 t->num.unit_type[1] = B_UNIT_NONE;
337 t->num.unit_type[2] = B_UNIT_NONE;
338
340}
341
343
345 /*flags*/ T_NULL_ONE,
346 /*init_fn*/ initResize,
347 /*transform_fn*/ applyResize,
348 /*transform_matrix_fn*/ resize_transform_matrix_fn,
349 /*handle_event_fn*/ nullptr,
350 /*snap_distance_fn*/ ResizeBetween,
351 /*snap_apply_fn*/ ApplySnapResize,
352 /*draw_fn*/ nullptr,
353};
354
355} // namespace blender::ed::transform
int BKE_image_find_nearest_tile_with_offset(const Image *image, const float co[2], float r_uv_offset[2]) ATTR_NONNULL(2
@ B_UNIT_NONE
Definition BKE_unit.hh:136
#define BLI_assert(a)
Definition BLI_assert.h:46
void mul_m3_v3(const float M[3][3], float r[3])
void mul_m4_m4m4(float R[4][4], const float A[4][4], const float B[4][4])
void size_to_mat3(float R[3][3], const float size[3])
void copy_m3_m3(float m1[3][3], const float m2[3][3])
void copy_m3_m4(float m1[3][3], const float m2[4][4])
void copy_m4_m3(float m1[4][4], const float m2[3][3])
void transform_pivot_set_m4(float mat[4][4], const float pivot[3])
void mul_v3_m3v3(float r[3], const float M[3][3], const float a[3])
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE bool is_zero_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void copy_v3_v3(float r[3], const float a[3])
void project_v3_v3v3(float out[3], const float p[3], const float v_proj[3])
MINLINE bool is_zero_v3(const float v[3]) ATTR_WARN_UNUSED_RESULT
MINLINE float normalize_v2(float n[2])
MINLINE void copy_v3_fl(float r[3], float f)
MINLINE void zero_v3(float r[3])
MINLINE void add_v3_v3(float r[3], const float a[3])
void project_v2_v2v2(float out[2], const float p[2], const float v_proj[2])
MINLINE float len_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT
#define STREQ(a, b)
#define TIP_(msgid)
@ V3D_ORIENT_GLOBAL
@ NUM_AFFECT_ALL
bool applyNumInput(NumInput *n, float *vec)
Definition numinput.cc:190
@ NUM_NULL_ONE
@ NUM_NO_ZERO
void ED_area_status_text(ScrArea *area, const char *str)
Definition area.cc:851
#define UI_MAX_DRAW_STR
BMesh const char void * data
#define str(s)
VecBase< float, 2 > float2
VecBase< float, 3 > float3
static float ResizeBetween(TransInfo *t, const float p1[3], const float p2[3])
void initMouseInputMode(TransInfo *t, MouseInput *mi, MouseInputMode mode)
void recalc_data(TransInfo *t)
void clipUVData(TransInfo *t)
void getSnapPoint(const TransInfo *t, float vec[3])
static void constrain_scale_to_boundary(const float numerator, const float denominator, float *scale)
static void applyResize(TransInfo *t)
bool transform_snap_increment(const TransInfo *t, float *r_val)
void transform_snap_mixed_apply(TransInfo *t, float *vec)
static void resize_transform_matrix_fn(TransInfo *t, float mat_xform[4][4])
void setCustomPoints(TransInfo *t, MouseInput *mi, const int mval_start[2], const int mval_end[2])
static bool clip_uv_transform_resize(TransInfo *t, float vec[2])
void headerResize(TransInfo *t, const float vec[3], char *str, const int str_size)
void transform_mode_default_modal_orientation_set(TransInfo *t, int type)
static void ApplySnapResize(TransInfo *t, float vec[3])
bool transform_mode_affect_only_locations(const TransInfo *t)
void constraintNumInput(TransInfo *t, float vec[3])
bool transdata_check_local_center(const TransInfo *t, short around)
static void initResize(TransInfo *t, wmOperator *op)
void ElementResize(const TransInfo *t, const TransDataContainer *tc, int td_index, const float mat[3][3])
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
VecBase< float, 2 > float2
const int status
#define fabsf
void RNA_property_float_get_array(PointerRNA *ptr, PropertyRNA *prop, float *values)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
void * first
short idx_max
short val_flag[NUM_MAX_ELEMENTS]
float val_inc[NUM_MAX_ELEMENTS]
int unit_type[NUM_MAX_ELEMENTS]
short flag
struct UnitSettings unit
ListBase spacedata
struct Image * image
void(* applyRot)(const TransInfo *t, const TransDataContainer *tc, const TransData *td, float r_axis[3])
Definition transform.hh:596
void(* applySize)(const TransInfo *t, const TransDataContainer *tc, const TransData *td, float r_smat[3][3])
Definition transform.hh:591
struct PointerRNA * ptr
i
Definition text_draw.cc:230
#define TRANSFORM_DIST_INVALID
Definition transform.hh:35
#define FOREACH_TRANS_DATA_CONTAINER(t, th)
Definition transform.hh:42
conversion and adaptation of different datablocks to a common struct.
transform modes used by different operators.