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
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/* -------------------------------------------------------------------- */
40 float mat[3][3];
41};
42
43static void rmat_cache_init(RotateMatrixCache *rmc, const float angle, const float axis[3])
44{
45 axis_angle_normalized_to_mat3(rmc->mat, axis, angle);
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) {
57 axis_angle_normalized_to_mat3(rmc->mat, axis, angle);
58 rmc->do_update_matrix--;
59 }
60}
61
64/* -------------------------------------------------------------------- */
72 const TransInfo *t;
74 float axis[3];
75 float angle;
78};
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 }
150 transdata_elem_rotate(data->t,
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
162/* -------------------------------------------------------------------- */
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];
205 getSnapPoint(t, point);
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). */
236 angle = large_rotation_limit(angle);
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];
308 if (uv_rotation_in_clip_bounds_test(t, angle)) {
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
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
#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
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.
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)
TransModeInfo TransMode_rotate
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])