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