Blender V4.3
transform_mode_rotate.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 <cstdlib>
10
11#include "BLI_math_matrix.h"
12#include "BLI_math_rotation.h"
13#include "BLI_math_vector.h"
14#include "BLI_task.h"
15
16#include "BKE_report.hh"
17#include "BKE_unit.hh"
18
19#include "ED_screen.hh"
20
21#include "UI_interface.hh"
22
23#include "transform.hh"
24#include "transform_convert.hh"
25#include "transform_snap.hh"
26
27#include "transform_mode.hh"
28
29/* -------------------------------------------------------------------- */
32
40 float mat[3][3];
41};
42
43static void rmat_cache_init(RotateMatrixCache *rmc, const float angle, const float axis[3])
44{
46 rmc->do_update_matrix = 0;
47}
48
50{
51 rmc->do_update_matrix = 2;
52}
53
54static void rmat_cache_update(RotateMatrixCache *rmc, const float axis[3], const float angle)
55{
56 if (rmc->do_update_matrix > 0) {
58 rmc->do_update_matrix--;
59 }
60}
61
63
64/* -------------------------------------------------------------------- */
67
79
83
84static void transdata_elem_rotate(const TransInfo *t,
85 const TransDataContainer *tc,
86 TransData *td,
87 const float axis[3],
88 const float angle,
89 const float angle_step,
90 const bool is_large_rotation,
92{
93 float axis_buffer[3];
94 const float *axis_final = axis;
95
96 float angle_final = angle;
97 if (t->con.applyRot) {
98 copy_v3_v3(axis_buffer, axis);
99 axis_final = axis_buffer;
100 t->con.applyRot(t, tc, td, axis_buffer, nullptr);
101 angle_final = angle * td->factor;
102 /* Even though final angle might be identical to orig value,
103 * we have to update the rotation matrix in that case... */
104 rmat_cache_reset(rmc);
105 }
106 else if (t->flag & T_PROP_EDIT) {
107 angle_final = angle * td->factor;
108 }
109
110 /* Rotation is very likely to be above 180 degrees we need to do rotation by steps.
111 * Note that this is only needed when doing 'absolute' rotation
112 * (i.e. from initial rotation again, typically when using numinput).
113 * regular incremental rotation (from mouse/widget/...) will be called often enough,
114 * hence steps are small enough to be properly handled without that complicated trick.
115 * Note that we can only do that kind of stepped rotation if we have initial rotation values
116 * (and access to some actual rotation value storage).
117 * Otherwise, just assume it's useless (e.g. in case of mesh/UV/etc. editing).
118 * Also need to be in Euler rotation mode, the others never allow more than one turn anyway.
119 */
120 if (is_large_rotation && td->ext != nullptr && td->ext->rotOrder == ROT_MODE_EUL) {
121 copy_v3_v3(td->ext->rot, td->ext->irot);
122 for (float angle_progress = angle_step; fabsf(angle_progress) < fabsf(angle_final);
123 angle_progress += angle_step)
124 {
125 axis_angle_normalized_to_mat3(rmc->mat, axis_final, angle_progress);
126 ElementRotation(t, tc, td, rmc->mat, t->around);
127 }
128 rmat_cache_reset(rmc);
129 }
130 else if (angle_final != angle) {
131 rmat_cache_reset(rmc);
132 }
133
134 rmat_cache_update(rmc, axis_final, angle_final);
135
136 ElementRotation(t, tc, td, rmc->mat, t->around);
137}
138
139static void transdata_elem_rotate_fn(void *__restrict iter_data_v,
140 const int iter,
141 const TaskParallelTLS *__restrict tls)
142{
143 TransDataArgs_Rotate *data = static_cast<TransDataArgs_Rotate *>(iter_data_v);
144 TransDataArgs_RotateTLS *tls_data = static_cast<TransDataArgs_RotateTLS *>(tls->userdata_chunk);
145
146 TransData *td = &data->tc->data[iter];
147 if (td->flag & TD_SKIP) {
148 return;
149 }
151 data->tc,
152 td,
153 data->axis,
154 data->angle,
155 data->angle_step,
156 data->is_large_rotation,
157 &tls_data->rmc);
158}
159
161
162/* -------------------------------------------------------------------- */
165
166static float RotationBetween(TransInfo *t, const float p1[3], const float p2[3])
167{
168 float angle, start[3], end[3];
169
170 sub_v3_v3v3(start, p1, t->center_global);
171 sub_v3_v3v3(end, p2, t->center_global);
172
173 /* Angle around a constraint axis (error prone, will need debug). */
174 if (t->con.applyRot != nullptr && (t->con.mode & CON_APPLY)) {
175 float axis[3];
176
177 t->con.applyRot(t, nullptr, nullptr, axis, nullptr);
178
179 angle = -angle_signed_on_axis_v3v3_v3(start, end, axis);
180 }
181 else {
182 float mtx[3][3];
183
184 copy_m3_m4(mtx, t->viewmat);
185
186 mul_m3_v3(mtx, end);
187 mul_m3_v3(mtx, start);
188
189 angle = atan2f(start[1], start[0]) - atan2f(end[1], end[0]);
190 }
191
192 if (angle > float(M_PI)) {
193 angle = angle - 2 * float(M_PI);
194 }
195 else if (angle < -float(M_PI)) {
196 angle = 2.0f * float(M_PI) + angle;
197 }
198
199 return angle;
200}
201
202static void ApplySnapRotation(TransInfo *t, float *value)
203{
204 float point[3];
206
207 float dist = RotationBetween(t, t->tsnap.snap_source, point);
208 *value = dist;
209}
210
211static float large_rotation_limit(float angle)
212{
213 /* Limit rotation to 1001 turns max
214 * (otherwise iterative handling of 'large' rotations would become too slow). */
215 const float angle_max = float(M_PI * 2000.0);
216 if (fabsf(angle) > angle_max) {
217 const float angle_sign = angle < 0.0f ? -1.0f : 1.0f;
218 angle = angle_sign * (fmodf(fabsf(angle), float(M_PI * 2.0)) + angle_max);
219 }
220 return angle;
221}
222
224 float angle,
225 const float axis[3],
226 const bool is_large_rotation)
227{
228 const float angle_sign = angle < 0.0f ? -1.0f : 1.0f;
229 /* We cannot use something too close to 180 degrees, or 'continuous' rotation may fail
230 * due to computing error. */
231 const float angle_step = angle_sign * float(0.9 * M_PI);
232
233 if (is_large_rotation) {
234 /* Just in case, calling code should have already done that in practice
235 * (for UI feedback reasons). */
237 }
238
239 RotateMatrixCache rmc = {0};
240 rmat_cache_init(&rmc, angle, axis);
241
243 if (tc->data_len < TRANSDATA_THREAD_LIMIT) {
244 TransData *td = tc->data;
245 for (int i = 0; i < tc->data_len; i++, td++) {
246 if (td->flag & TD_SKIP) {
247 continue;
248 }
249 transdata_elem_rotate(t, tc, td, axis, angle, angle_step, is_large_rotation, &rmc);
250 }
251 }
252 else {
254 data.t = t;
255 data.tc = tc;
256 copy_v3_v3(data.axis, axis);
257 data.angle = angle;
258 data.angle_step = angle_step;
259 data.is_large_rotation = is_large_rotation;
260 TransDataArgs_RotateTLS tls_data{};
261 tls_data.rmc = rmc;
262
263 TaskParallelSettings settings;
265 settings.userdata_chunk = &tls_data;
266 settings.userdata_chunk_size = sizeof(tls_data);
267 BLI_task_parallel_range(0, tc->data_len, &data, transdata_elem_rotate_fn, &settings);
268 }
269 }
270}
271
272static bool uv_rotation_in_clip_bounds_test(const TransInfo *t, const float angle)
273{
274 const float cos_angle = cosf(angle);
275 const float sin_angle = sinf(angle);
276 const float *center = t->center_global;
278 TransData *td = tc->data;
279 for (int i = 0; i < tc->data_len; i++, td++) {
280 if (td->flag & TD_SKIP) {
281 continue;
282 }
283 if (td->factor < 1.0f) {
284 continue; /* Proportional edit, will get picked up in next phase. */
285 }
286
287 float uv[2];
288 sub_v2_v2v2(uv, td->iloc, center);
289 float pr[2];
290 pr[0] = cos_angle * uv[0] + sin_angle * uv[1];
291 pr[1] = -sin_angle * uv[0] + cos_angle * uv[1];
292 add_v2_v2(pr, center);
293 /* TODO: UDIM support. */
294 if (pr[0] < 0.0f || 1.0f < pr[0]) {
295 return false;
296 }
297 if (pr[1] < 0.0f || 1.0f < pr[1]) {
298 return false;
299 }
300 }
301 }
302 return true;
303}
304
305static bool clip_uv_transform_rotate(const TransInfo *t, float *vec, float *vec_inside_bounds)
306{
307 float angle = vec[0];
309 vec_inside_bounds[0] = angle; /* Store for next iteration. */
310 return false; /* Nothing to do. */
311 }
312 float angle_inside_bounds = vec_inside_bounds[0];
313 if (!uv_rotation_in_clip_bounds_test(t, angle_inside_bounds)) {
314 return false; /* No known way to fix, may as well rotate anyway. */
315 }
316 const int max_i = 32; /* Limit iteration, mainly for debugging. */
317 for (int i = 0; i < max_i; i++) {
318 /* Binary search. */
319 const float angle_mid = (angle_inside_bounds + angle) / 2.0f;
320 if (ELEM(angle_mid, angle_inside_bounds, angle)) {
321 break; /* Float precision reached. */
322 }
323 if (uv_rotation_in_clip_bounds_test(t, angle_mid)) {
324 angle_inside_bounds = angle_mid;
325 }
326 else {
327 angle = angle_mid;
328 }
329 }
330
331 vec_inside_bounds[0] = angle_inside_bounds; /* Store for next iteration. */
332 vec[0] = angle_inside_bounds; /* Update rotation angle. */
333 return true;
334}
335
337{
338 float axis_final[3];
339 float final = t->values[0] + t->values_modal_offset[0];
340
341 if ((t->con.mode & CON_APPLY) && t->con.applyRot) {
342 t->con.applyRot(t, nullptr, nullptr, axis_final, &final);
343 }
344 else {
345 negate_v3_v3(axis_final, t->spacemtx[t->orient_axis]);
346 }
347
348 if (applyNumInput(&t->num, &final)) {
349 /* We have to limit the amount of turns to a reasonable number here,
350 * to avoid things getting *very* slow, see how applyRotationValue() handles those... */
351 final = large_rotation_limit(final);
352 }
353 else {
355 if (!(transform_snap_is_active(t) && validSnap(t))) {
356 transform_snap_increment(t, &final);
357 }
358 }
359
360 t->values_final[0] = final;
361
362 const bool is_large_rotation = hasNumInput(&t->num);
363 applyRotationValue(t, final, axis_final, is_large_rotation);
364
365 if (t->flag & T_CLIP_UV) {
367 applyRotationValue(t, t->values_final[0], axis_final, is_large_rotation);
368 }
369
370 /* Not ideal, see #clipUVData code-comment. */
371 if (t->flag & T_PROP_EDIT) {
372 clipUVData(t);
373 }
374 }
375
376 recalc_data(t);
377
378 char str[UI_MAX_DRAW_STR];
379 headerRotation(t, str, sizeof(str), t->values_final[0]);
381}
382
383static void applyRotationMatrix(TransInfo *t, float mat_xform[4][4])
384{
385 float axis_final[3];
386 const float angle_final = t->values_final[0];
387 if ((t->con.mode & CON_APPLY) && t->con.applyRot) {
388 t->con.applyRot(t, nullptr, nullptr, axis_final, nullptr);
389 }
390 else {
391 negate_v3_v3(axis_final, t->spacemtx[t->orient_axis]);
392 }
393
394 float mat3[3][3];
395 float mat4[4][4];
396 axis_angle_normalized_to_mat3(mat3, axis_final, angle_final);
397 copy_m4_m3(mat4, mat3);
399 mul_m4_m4m4(mat_xform, mat4, mat_xform);
400}
401
402static void initRotation(TransInfo *t, wmOperator * /*op*/)
403{
404 if (t->spacetype == SPACE_ACTION) {
405 BKE_report(t->reports, RPT_ERROR, "Rotation is not supported in the Dope Sheet Editor");
406 t->state = TRANS_CANCEL;
407 }
408
409 t->mode = TFM_ROTATION;
410
412
413 t->idx_max = 0;
414 t->num.idx_max = 0;
416
417 copy_v3_fl(t->num.val_inc, t->snap[1]);
418 t->num.unit_sys = t->scene->unit.system;
421
422 if (t->flag & T_2D_EDIT) {
423 t->flag |= T_NO_CONSTRAINT;
424 }
425
427}
428
430
432 /*flags*/ 0,
433 /*init_fn*/ initRotation,
434 /*transform_fn*/ applyRotation,
435 /*transform_matrix_fn*/ applyRotationMatrix,
436 /*handle_event_fn*/ nullptr,
437 /*snap_distance_fn*/ RotationBetween,
438 /*snap_apply_fn*/ ApplySnapRotation,
439 /*draw_fn*/ nullptr,
440};
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:125
@ B_UNIT_ROTATION
Definition BKE_unit.hh:111
#define M_PI
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 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 axis_angle_normalized_to_mat3(float R[3][3], const float axis[3], float angle)
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void negate_v3_v3(float r[3], const float a[3])
MINLINE void add_v2_v2(float r[2], const float a[2])
float angle_signed_on_axis_v3v3_v3(const float v1[3], const float v2[3], const float axis[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v2_v2v2(float r[2], const float a[2], const float b[2])
MINLINE void copy_v3_fl(float r[3], float f)
void BLI_task_parallel_range(int start, int stop, void *userdata, TaskParallelRangeFunc func, const TaskParallelSettings *settings)
Definition task_range.cc:99
BLI_INLINE void BLI_parallel_range_settings_defaults(TaskParallelSettings *settings)
Definition BLI_task.h:230
#define ELEM(...)
int max_i(int a, int b)
Definition Basic.c:11
@ ROT_MODE_EUL
@ USER_UNIT_ROT_RADIANS
@ SPACE_ACTION
@ V3D_ORIENT_VIEW
bool applyNumInput(NumInput *n, float *vec)
Definition numinput.cc:190
bool hasNumInput(const NumInput *n)
Definition numinput.cc:171
void ED_area_status_text(ScrArea *area, const char *str)
Definition area.cc:803
@ TFM_ROTATION
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:125
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a point
#define UI_MAX_DRAW_STR
#define sinf(x)
#define cosf(x)
#define atan2f(x, y)
#define fmodf(x, y)
#define fabsf(x)
draw_view in_light_buf[] float
#define str(s)
short idx_max
float val_inc[NUM_MAX_ELEMENTS]
int unit_type[NUM_MAX_ELEMENTS]
bool unit_use_radians
struct UnitSettings unit
size_t userdata_chunk_size
Definition BLI_task.h:173
void(* applyRot)(const TransInfo *t, const TransDataContainer *tc, const TransData *td, float r_axis[3], float *r_angle)
Definition transform.hh:371
eTConstraint mode
Definition transform.hh:351
const TransDataContainer * tc
TransDataExtension * ext
eTfmMode mode
Definition transform.hh:517
short around
Definition transform.hh:580
char spacetype
Definition transform.hh:582
int orient_axis
Definition transform.hh:640
float snap[2]
Definition transform.hh:561
ReportList * reports
Definition transform.hh:661
float values[4]
Definition transform.hh:624
TransSnap tsnap
Definition transform.hh:537
short idx_max
Definition transform.hh:559
float values_modal_offset[4]
Definition transform.hh:627
eTState state
Definition transform.hh:527
NumInput num
Definition transform.hh:540
Scene * scene
Definition transform.hh:654
eTFlag flag
Definition transform.hh:523
MouseInput mouse
Definition transform.hh:543
float viewmat[4][4]
Definition transform.hh:573
float values_final[4]
Definition transform.hh:632
TransCon con
Definition transform.hh:534
float center_global[3]
Definition transform.hh:555
float spacemtx[3][3]
Definition transform.hh:592
float values_inside_constraints[4]
Definition transform.hh:635
ScrArea * area
Definition transform.hh:651
float snap_source[3]
Definition transform.hh:325
@ INPUT_ANGLE
Definition transform.hh:746
@ CON_APPLY
Definition transform.hh:193
@ T_PROP_EDIT
Definition transform.hh:98
@ T_2D_EDIT
Definition transform.hh:104
@ T_NO_CONSTRAINT
Definition transform.hh:95
@ T_CLIP_UV
Definition transform.hh:105
@ TRANS_CANCEL
Definition transform.hh:210
void initMouseInputMode(TransInfo *t, MouseInput *mi, MouseInputMode mode)
#define FOREACH_TRANS_DATA_CONTAINER(t, th)
Definition transform.hh:854
void clipUVData(TransInfo *t)
void recalc_data(TransInfo *t)
conversion and adaptation of different datablocks to a common struct.
@ TD_SKIP
#define TRANSDATA_THREAD_LIMIT
void transform_mode_default_modal_orientation_set(TransInfo *t, int type)
void ElementRotation(const TransInfo *t, const TransDataContainer *tc, TransData *td, const float mat[3][3], const short around)
void headerRotation(TransInfo *t, char *str, const int str_size, float final)
transform modes used by different operators.
TransModeInfo TransMode_rotate
static void initRotation(TransInfo *t, wmOperator *)
static void transdata_elem_rotate_fn(void *__restrict iter_data_v, const int iter, const TaskParallelTLS *__restrict tls)
static void rmat_cache_update(RotateMatrixCache *rmc, const float axis[3], const float angle)
static void applyRotationValue(TransInfo *t, float angle, const float axis[3], const bool is_large_rotation)
static void rmat_cache_reset(RotateMatrixCache *rmc)
static void applyRotation(TransInfo *t)
static float RotationBetween(TransInfo *t, const float p1[3], const float p2[3])
static float large_rotation_limit(float angle)
static void ApplySnapRotation(TransInfo *t, float *value)
static void rmat_cache_init(RotateMatrixCache *rmc, const float angle, const float axis[3])
static bool uv_rotation_in_clip_bounds_test(const TransInfo *t, const float angle)
static void transdata_elem_rotate(const TransInfo *t, const TransDataContainer *tc, TransData *td, const float axis[3], const float angle, const float angle_step, const bool is_large_rotation, RotateMatrixCache *rmc)
static bool clip_uv_transform_rotate(const TransInfo *t, float *vec, float *vec_inside_bounds)
static void applyRotationMatrix(TransInfo *t, float mat_xform[4][4])
void transform_snap_mixed_apply(TransInfo *t, float *vec)
bool validSnap(const TransInfo *t)
bool transform_snap_increment(const TransInfo *t, float *r_val)
bool transform_snap_is_active(const TransInfo *t)
void initSnapAngleIncrements(TransInfo *t)
void getSnapPoint(const TransInfo *t, float vec[3])