Blender V4.5
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.hh"
15
16#include "BKE_context.hh"
17#include "BKE_report.hh"
18#include "BKE_unit.hh"
19
20#include "BLT_translation.hh"
21
22#include "ED_screen.hh"
23
24#include "UI_interface.hh"
25
26#include "transform.hh"
27#include "transform_convert.hh"
28#include "transform_snap.hh"
29
30#include "transform_mode.hh"
31
32namespace blender::ed::transform {
33
34/* -------------------------------------------------------------------- */
37
45 float mat[3][3];
46};
47
48static void rmat_cache_init(RotateMatrixCache *rmc, const float angle, const float axis[3])
49{
51 rmc->do_update_matrix = 0;
52}
53
55{
56 rmc->do_update_matrix = 2;
57}
58
59static void rmat_cache_update(RotateMatrixCache *rmc, const float axis[3], const float angle)
60{
61 if (rmc->do_update_matrix > 0) {
63 rmc->do_update_matrix--;
64 }
65}
66
68
69/* -------------------------------------------------------------------- */
72
73static void transdata_elem_rotate(const TransInfo *t,
74 const TransDataContainer *tc,
75 TransData *td,
76 const float axis[3],
77 const float angle,
78 const float angle_step,
79 const bool is_large_rotation,
81{
82 float axis_buffer[3];
83 const float *axis_final = axis;
84
85 float angle_final = angle;
86 if (t->con.applyRot) {
87 copy_v3_v3(axis_buffer, axis);
88 axis_final = axis_buffer;
89 t->con.applyRot(t, tc, td, axis_buffer, nullptr);
90 angle_final = angle * td->factor;
91 /* Even though final angle might be identical to orig value,
92 * we have to update the rotation matrix in that case... */
94 }
95 else if (t->flag & T_PROP_EDIT) {
96 angle_final = angle * td->factor;
97 }
98
99 /* Rotation is very likely to be above 180 degrees we need to do rotation by steps.
100 * Note that this is only needed when doing 'absolute' rotation
101 * (i.e. from initial rotation again, typically when using numinput).
102 * regular incremental rotation (from mouse/widget/...) will be called often enough,
103 * hence steps are small enough to be properly handled without that complicated trick.
104 * Note that we can only do that kind of stepped rotation if we have initial rotation values
105 * (and access to some actual rotation value storage).
106 * Otherwise, just assume it's useless (e.g. in case of mesh/UV/etc. editing).
107 * Also need to be in Euler rotation mode, the others never allow more than one turn anyway.
108 */
109 if (is_large_rotation && td->ext != nullptr && td->ext->rotOrder == ROT_MODE_EUL) {
110 copy_v3_v3(td->ext->rot, td->ext->irot);
111 for (float angle_progress = angle_step; fabsf(angle_progress) < fabsf(angle_final);
112 angle_progress += angle_step)
113 {
114 axis_angle_normalized_to_mat3(rmc->mat, axis_final, angle_progress);
115 ElementRotation(t, tc, td, rmc->mat, t->around);
116 }
117 rmat_cache_reset(rmc);
118 }
119 else if (angle_final != angle) {
120 rmat_cache_reset(rmc);
121 }
122
123 rmat_cache_update(rmc, axis_final, angle_final);
124
125 ElementRotation(t, tc, td, rmc->mat, t->around);
126}
127
129
130/* -------------------------------------------------------------------- */
133
134static float RotationBetween(TransInfo *t, const float p1[3], const float p2[3])
135{
136 float angle, start[3], end[3];
137
138 sub_v3_v3v3(start, p1, t->center_global);
139 sub_v3_v3v3(end, p2, t->center_global);
140
141 /* Angle around a constraint axis (error prone, will need debug). */
142 if (t->con.applyRot != nullptr && (t->con.mode & CON_APPLY)) {
143 float axis[3];
144
145 t->con.applyRot(t, nullptr, nullptr, axis, nullptr);
146
147 angle = -angle_signed_on_axis_v3v3_v3(start, end, axis);
148 }
149 else {
150 float mtx[3][3];
151
152 copy_m3_m4(mtx, t->viewmat);
153
154 mul_m3_v3(mtx, end);
155 mul_m3_v3(mtx, start);
156
157 angle = atan2f(start[1], start[0]) - atan2f(end[1], end[0]);
158 }
159
160 if (angle > float(M_PI)) {
161 angle = angle - 2 * float(M_PI);
162 }
163 else if (angle < -float(M_PI)) {
164 angle = 2.0f * float(M_PI) + angle;
165 }
166
167 return angle;
168}
169
170static void ApplySnapRotation(TransInfo *t, float *value)
171{
172 float point[3];
173 getSnapPoint(t, point);
174
175 float dist = RotationBetween(t, t->tsnap.snap_source, point);
176 *value = dist;
177}
178
179static float large_rotation_limit(float angle)
180{
181 /* Limit rotation to 1001 turns max
182 * (otherwise iterative handling of 'large' rotations would become too slow). */
183 const float angle_max = float(M_PI * 2000.0);
184 if (fabsf(angle) > angle_max) {
185 const float angle_sign = angle < 0.0f ? -1.0f : 1.0f;
186 angle = angle_sign * (fmodf(fabsf(angle), float(M_PI * 2.0)) + angle_max);
187 }
188 return angle;
189}
190
192 float angle,
193 const float axis[3],
194 const bool is_large_rotation)
195{
196 const float angle_sign = angle < 0.0f ? -1.0f : 1.0f;
197 /* We cannot use something too close to 180 degrees, or 'continuous' rotation may fail
198 * due to computing error. */
199 const float angle_step = angle_sign * float(0.9 * M_PI);
200
201 if (is_large_rotation) {
202 /* Just in case, calling code should have already done that in practice
203 * (for UI feedback reasons). */
205 }
206
208 threading::parallel_for(IndexRange(tc->data_len), 1024, [&](const IndexRange range) {
209 RotateMatrixCache rmc = {0};
210 rmat_cache_init(&rmc, angle, axis);
211 for (const int i : range) {
212 TransData *td = &tc->data[i];
213 if (td->flag & TD_SKIP) {
214 continue;
215 }
216 transdata_elem_rotate(t, tc, td, axis, angle, angle_step, is_large_rotation, &rmc);
217 }
218 });
219 }
220}
221
222static bool uv_rotation_in_clip_bounds_test(const TransInfo *t, const float angle)
223{
224 const float cos_angle = cosf(angle);
225 const float sin_angle = sinf(angle);
226 const float *center = t->center_global;
228 TransData *td = tc->data;
229 for (int i = 0; i < tc->data_len; i++, td++) {
230 if (td->flag & TD_SKIP) {
231 continue;
232 }
233 if (td->factor < 1.0f) {
234 continue; /* Proportional edit, will get picked up in next phase. */
235 }
236
237 float uv[2];
238 sub_v2_v2v2(uv, td->iloc, center);
239 float pr[2];
240 pr[0] = cos_angle * uv[0] + sin_angle * uv[1];
241 pr[1] = -sin_angle * uv[0] + cos_angle * uv[1];
242 add_v2_v2(pr, center);
243 /* TODO: UDIM support. */
244 if (pr[0] < 0.0f || 1.0f < pr[0]) {
245 return false;
246 }
247 if (pr[1] < 0.0f || 1.0f < pr[1]) {
248 return false;
249 }
250 }
251 }
252 return true;
253}
254
255static bool clip_uv_transform_rotate(const TransInfo *t, float *vec, float *vec_inside_bounds)
256{
257 float angle = vec[0];
259 vec_inside_bounds[0] = angle; /* Store for next iteration. */
260 return false; /* Nothing to do. */
261 }
262 float angle_inside_bounds = vec_inside_bounds[0];
263 if (!uv_rotation_in_clip_bounds_test(t, angle_inside_bounds)) {
264 return false; /* No known way to fix, may as well rotate anyway. */
265 }
266 const int max_i = 32; /* Limit iteration, mainly for debugging. */
267 for (int i = 0; i < max_i; i++) {
268 /* Binary search. */
269 const float angle_mid = (angle_inside_bounds + angle) / 2.0f;
270 if (ELEM(angle_mid, angle_inside_bounds, angle)) {
271 break; /* Float precision reached. */
272 }
273 if (uv_rotation_in_clip_bounds_test(t, angle_mid)) {
274 angle_inside_bounds = angle_mid;
275 }
276 else {
277 angle = angle_mid;
278 }
279 }
280
281 vec_inside_bounds[0] = angle_inside_bounds; /* Store for next iteration. */
282 vec[0] = angle_inside_bounds; /* Update rotation angle. */
283 return true;
284}
285
287{
288 float axis_final[3];
289 float final = t->values[0] + t->values_modal_offset[0];
290
291 if ((t->con.mode & CON_APPLY) && t->con.applyRot) {
292 t->con.applyRot(t, nullptr, nullptr, axis_final, &final);
293 }
294 else {
295 negate_v3_v3(axis_final, t->spacemtx[t->orient_axis]);
296 }
297
298 if (applyNumInput(&t->num, &final)) {
299 /* We have to limit the amount of turns to a reasonable number here,
300 * to avoid things getting *very* slow, see how applyRotationValue() handles those... */
301 final = large_rotation_limit(final);
302 }
303 else {
305 if (!(transform_snap_is_active(t) && validSnap(t))) {
306 transform_snap_increment(t, &final);
307 }
308 }
309
310 t->values_final[0] = final;
311
312 const bool is_large_rotation = hasNumInput(&t->num);
313 applyRotationValue(t, final, axis_final, is_large_rotation);
314
315 if (t->flag & T_CLIP_UV) {
317 applyRotationValue(t, t->values_final[0], axis_final, is_large_rotation);
318 }
319
320 /* Not ideal, see #clipUVData code-comment. */
321 if (t->flag & T_PROP_EDIT) {
322 clipUVData(t);
323 }
324 }
325
326 recalc_data(t);
327
328 char str[UI_MAX_DRAW_STR];
329 headerRotation(t, str, sizeof(str), t->values_final[0]);
331}
332
333static void applyRotationMatrix(TransInfo *t, float mat_xform[4][4])
334{
335 float axis_final[3];
336 const float angle_final = t->values_final[0];
337 if ((t->con.mode & CON_APPLY) && t->con.applyRot) {
338 t->con.applyRot(t, nullptr, nullptr, axis_final, nullptr);
339 }
340 else {
341 negate_v3_v3(axis_final, t->spacemtx[t->orient_axis]);
342 }
343
344 float mat3[3][3];
345 float mat4[4][4];
346 axis_angle_normalized_to_mat3(mat3, axis_final, angle_final);
347 copy_m4_m3(mat4, mat3);
349 mul_m4_m4m4(mat_xform, mat4, mat_xform);
350}
351
352static void initRotation(TransInfo *t, wmOperator * /*op*/)
353{
354 if (t->spacetype == SPACE_ACTION) {
355 BKE_report(t->reports, RPT_ERROR, "Rotation is not supported in the Dope Sheet Editor");
356 t->state = TRANS_CANCEL;
357 }
358
359 t->mode = TFM_ROTATION;
360
362 WorkspaceStatus status(t->context);
363 status.item(TIP_("Transform is set to only affect location"), ICON_ERROR);
365 }
366 else {
368 }
369
370 t->idx_max = 0;
371 t->num.idx_max = 0;
373
374 copy_v3_fl(t->num.val_inc, t->snap[1]);
375 t->num.unit_sys = t->scene->unit.system;
378
379 if (t->flag & T_2D_EDIT) {
380 t->flag |= T_NO_CONSTRAINT;
381 }
382
384}
385
387
389 /*flags*/ 0,
390 /*init_fn*/ initRotation,
391 /*transform_fn*/ applyRotation,
392 /*transform_matrix_fn*/ applyRotationMatrix,
393 /*handle_event_fn*/ nullptr,
394 /*snap_distance_fn*/ RotationBetween,
395 /*snap_apply_fn*/ ApplySnapRotation,
396 /*draw_fn*/ nullptr,
397};
398
399} // namespace blender::ed::transform
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
@ B_UNIT_ROTATION
Definition BKE_unit.hh:128
#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)
#define ELEM(...)
#define TIP_(msgid)
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:189
bool hasNumInput(const NumInput *n)
Definition numinput.cc:170
void ED_area_status_text(ScrArea *area, const char *str)
Definition area.cc:872
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
#define UI_MAX_DRAW_STR
void item(std::string text, int icon1, int icon2=0)
Definition area.cc:979
#define sinf(x)
#define cosf(x)
#define atan2f(x, y)
#define fmodf(x, y)
#define fabsf(x)
#define str(s)
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])
void headerRotation(TransInfo *t, char *str, const int str_size, float final)
bool transform_snap_increment(const TransInfo *t, float *r_val)
void transform_snap_mixed_apply(TransInfo *t, float *vec)
static float RotationBetween(TransInfo *t, const float p1[3], const float p2[3])
bool validSnap(const TransInfo *t)
void ElementRotation(const TransInfo *t, const TransDataContainer *tc, TransData *td, const float mat[3][3], const short around)
static void ApplySnapRotation(TransInfo *t, float *value)
static void applyRotationValue(TransInfo *t, float angle, const float axis[3], const bool is_large_rotation)
static void applyRotation(TransInfo *t)
static bool clip_uv_transform_rotate(const TransInfo *t, float *vec, float *vec_inside_bounds)
static void rmat_cache_reset(RotateMatrixCache *rmc)
void transform_mode_default_modal_orientation_set(TransInfo *t, int type)
static float large_rotation_limit(float angle)
static void initRotation(TransInfo *t, wmOperator *)
bool transform_mode_affect_only_locations(const TransInfo *t)
bool transform_snap_is_active(const TransInfo *t)
void initSnapAngleIncrements(TransInfo *t)
static void applyRotationMatrix(TransInfo *t, float mat_xform[4][4])
static bool uv_rotation_in_clip_bounds_test(const TransInfo *t, const float angle)
static void rmat_cache_update(RotateMatrixCache *rmc, const float axis[3], const float angle)
static void rmat_cache_init(RotateMatrixCache *rmc, const float angle, const float axis[3])
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)
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
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:590
i
Definition text_draw.cc:230
#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.