Blender V4.5
transform_mode_edge_slide.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 "BLI_math_geom.h"
10#include "BLI_math_matrix.h"
11#include "BLI_math_matrix.hh"
12#include "BLI_string.h"
13
14#include "BKE_editmesh.hh"
15#include "BKE_editmesh_bvh.hh"
16#include "BKE_unit.hh"
17
18#include "GPU_immediate.hh"
19#include "GPU_matrix.hh"
20#include "GPU_state.hh"
21
23
24#include "ED_mesh.hh"
25#include "ED_screen.hh"
26
27#include "RNA_access.hh"
28
29#include "UI_interface.hh"
30#include "UI_view2d.hh"
31
32#include "BLT_translation.hh"
33
34#include "transform.hh"
36#include "transform_convert.hh"
37#include "transform_mode.hh"
38#include "transform_snap.hh"
39
40namespace blender::ed::transform {
41
42/* -------------------------------------------------------------------- */
45
48
49 int mval_start[2], mval_end[2];
51
52 private:
53 float4x4 proj_mat;
54 float2 win_half;
55
56 public:
58 {
59 ARegion *region = t->region;
60 this->win_half = {region->winx / 2.0f, region->winy / 2.0f};
61
62 if (t->spacetype == SPACE_VIEW3D) {
63 RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
64 this->proj_mat = ED_view3d_ob_project_mat_get(rv3d, tc->obedit);
65
66 for (int i = 0; i < 4; i++) {
67 this->proj_mat[i][0] *= this->win_half[0];
68 this->proj_mat[i][1] *= this->win_half[1];
69 }
70 }
71 else {
72 const View2D *v2d = static_cast<View2D *>(t->view);
73 UI_view2d_view_to_region_m4(v2d, this->proj_mat.ptr());
74 this->proj_mat.location()[0] -= this->win_half[0];
75 this->proj_mat.location()[1] -= this->win_half[1];
76 }
77 }
78
79 void project(const TransDataEdgeSlideVert *svert, float2 &r_sco_a, float2 &r_sco_b) const
80 {
81 float3 iloc = svert->v_co_orig();
82 r_sco_a = math::project_point(this->proj_mat, iloc + svert->dir_side[0]).xy() + this->win_half;
83 r_sco_b = math::project_point(this->proj_mat, iloc + svert->dir_side[1]).xy() + this->win_half;
84 }
85};
86
98
106{
108 if (tc->custom.mode.data) {
109 return tc;
110 }
111 }
112 BLI_assert_msg(0, "Should never happen, at least one EdgeSlideData should be valid");
113 return nullptr;
114}
115
121
123{
125
126 setCustomPoints(t, &t->mouse, sld->mval_end, sld->mval_start);
127
128 /* #setCustomPoints isn't normally changing as the mouse moves,
129 * in this case apply mouse input immediately so we don't refresh
130 * with the value from the previous points. */
131 applyMouseInput(t, &t->mouse, t->mval, t->values);
132}
133
134/* Interpolates along a line made up of 2 segments (used for edge slide). */
136 float p[3], const float v1[3], const float v2[3], const float v3[3], float t)
137{
138 float t_mid, t_delta;
139
140 /* Could be pre-calculated. */
141 t_mid = line_point_factor_v3(v2, v1, v3);
142
143 t_delta = t - t_mid;
144 if (t_delta < 0.0f) {
145 if (UNLIKELY(fabsf(t_mid) < FLT_EPSILON)) {
146 copy_v3_v3(p, v2);
147 }
148 else {
149 interp_v3_v3v3(p, v1, v2, t / t_mid);
150 }
151 }
152 else {
153 t = t - t_mid;
154 t_mid = 1.0f - t_mid;
155
156 if (UNLIKELY(fabsf(t_mid) < FLT_EPSILON)) {
157 copy_v3_v3(p, v3);
158 }
159 else {
160 interp_v3_v3v3(p, v2, v3, t / t_mid);
161 }
162 }
163}
164
165static void edge_slide_data_init_mval(MouseInput *mi, EdgeSlideData *sld, float *mval_dir)
166{
167 /* Possible all of the edge loops are pointing directly at the view. */
168 if (UNLIKELY(len_squared_v2(mval_dir) < 0.1f)) {
169 mval_dir[0] = 0.0f;
170 mval_dir[1] = 100.0f;
171 }
172
173 float mval_start[2], mval_end[2];
174
175 /* Zero out Start. */
176 zero_v2(mval_start);
177
178 /* `mval_dir` holds a vector along edge loop. */
179 copy_v2_v2(mval_end, mval_dir);
180 mul_v2_fl(mval_end, 0.5f);
181
182 sld->mval_start[0] = mi->imval[0] + mval_start[0];
183 sld->mval_start[1] = mi->imval[1] + mval_start[1];
184
185 sld->mval_end[0] = mi->imval[0] + mval_end[0];
186 sld->mval_end[1] = mi->imval[1] + mval_end[1];
187}
188
191 const View3D *v3d,
192 const BMBVHTree *bmbvh,
194{
195 /* NOTE: */
196 BMIter iter_other;
197 BMEdge *e;
198
199 BMVert *v = static_cast<BMVert *>(sv->td->extra);
200 BM_ITER_ELEM (e, &iter_other, v, BM_EDGES_OF_VERT) {
202 continue;
203 }
204
205 if (BMBVH_EdgeVisible(bmbvh, e, t->depsgraph, t->region, v3d, tc->obedit)) {
206 return true;
207 }
208 }
209 return false;
210}
211
217 EdgeSlideData *sld,
218 const int loop_nr,
219 const float2 &mval,
220 const bool use_calc_direction)
221{
222 View3D *v3d = nullptr;
223
224 /* Use for visibility checks. */
225 bool use_occlude_geometry = false;
226 if (t->spacetype == SPACE_VIEW3D) {
227 v3d = static_cast<View3D *>(t->area ? t->area->spacedata.first : nullptr);
228 if (v3d) {
229 if (tc->obedit->type == OB_MESH) {
230 use_occlude_geometry = (tc->obedit->dt > OB_WIRE && !XRAY_ENABLED(v3d));
231 }
232 }
233 }
234
235 /* NOTE(@ideasman42): At the moment this is only needed for meshes.
236 * In principle we could use a generic ray-cast test.
237 *
238 * Prefer #BMBVHTree over generic snap: #SnapObjectContext
239 * or any method that considers all other objects in the scene.
240 *
241 * While generic snapping is technically "correct" there are multiple reasons not to use this.
242 *
243 * - Performance, where generic snapping would consider all other objects for every-vertex.
244 * This can cause lockups when #DupliObject have to be created multiple times for each vertex.
245 * - In practice it's acceptable (even preferable) to skip back-facing vertices
246 * based on each meshes own faces that doesn't take other scene objects into account,
247 * especially since this includes instances objects from particles or nodes.
248 * - The #BMBVH_EdgeVisible check skips faces that the edge is connected to,
249 * unlike generic ray-casts where an edge can (under some conditions) overlap it self.
250 *
251 * See: #125646 for details.
252 */
253 BMBVHTree *bmbvh = nullptr;
254 Array<float3> bmbvh_coord_storage;
255 if (use_occlude_geometry) {
256 Scene *scene_eval = DEG_get_evaluated(t->depsgraph, t->scene);
257 Object *obedit_eval = DEG_get_evaluated(t->depsgraph, tc->obedit);
259
261 t->depsgraph, em, scene_eval, obedit_eval, bmbvh_coord_storage);
262
265 vert_positions.is_empty() ? nullptr :
266 vert_positions.data(),
267 false);
268 }
269
270 /* Find mouse vectors, the global one, and one per loop in case we have
271 * multiple loops selected, in case they are oriented different. */
272 float2 mval_dir = float2(0);
273 float dist_best_sq = FLT_MAX;
274
275 /* Only for use_calc_direction. */
276 float2 *loop_dir = nullptr;
277 float *loop_maxdist = nullptr;
278
279 if (use_calc_direction) {
280 loop_dir = MEM_calloc_arrayN<float2>(loop_nr, "sv loop_dir");
281 loop_maxdist = MEM_malloc_arrayN<float>(loop_nr, "sv loop_maxdist");
282 copy_vn_fl(loop_maxdist, loop_nr, FLT_MAX);
283 }
284
285 for (int i : sld->sv.index_range()) {
286 TransDataEdgeSlideVert *sv = &sld->sv[i];
287 bool is_visible = !use_occlude_geometry || is_vert_slide_visible_bmesh(t, tc, v3d, bmbvh, sv);
288
289 /* This test is only relevant if object is not wire-drawn! See #32068. */
290 if (!is_visible && !use_calc_direction) {
291 continue;
292 }
293
294 /* Search cross edges for visible edge to the mouse cursor,
295 * then use the shared vertex to calculate screen vector. */
296 /* Screen-space coords. */
297 float2 sco_a, sco_b;
298 sld->project(sv, sco_a, sco_b);
299
300 /* Global direction. */
301 float dist_sq = dist_squared_to_line_segment_v2(mval, sco_b, sco_a);
302 if (is_visible) {
303 if (dist_sq < dist_best_sq && (len_squared_v2v2(sco_b, sco_a) > 0.1f)) {
304 dist_best_sq = dist_sq;
305 mval_dir = sco_b - sco_a;
306 sld->curr_sv_index = i;
307 }
308 }
309
310 if (use_calc_direction) {
311 /* Per loop direction. */
312 int l_nr = sv->loop_nr;
313 if (dist_sq < loop_maxdist[l_nr]) {
314 loop_maxdist[l_nr] = dist_sq;
315 loop_dir[l_nr] = sco_b - sco_a;
316 }
317 }
318 }
319
320 if (use_calc_direction) {
321 for (TransDataEdgeSlideVert &sv : sld->sv) {
322 /* Switch a/b if loop direction is different from global direction. */
323 int l_nr = sv.loop_nr;
324 if (math::dot(loop_dir[l_nr], mval_dir) < 0.0f) {
325 swap_v3_v3(sv.dir_side[0], sv.dir_side[1]);
326 }
327 }
328
329 MEM_freeN(loop_dir);
330 MEM_freeN(loop_maxdist);
331 }
332
333 edge_slide_data_init_mval(&t->mouse, sld, mval_dir);
334
335 if (bmbvh) {
336 BKE_bmbvh_free(bmbvh);
337 }
338}
339
342 const bool use_double_side)
343{
344 int group_len;
345 EdgeSlideData *sld = MEM_new<EdgeSlideData>("sld");
347 sld->sv = transform_mesh_uv_edge_slide_data_create(t, tc, &group_len);
348 }
349 else {
350 sld->sv = transform_mesh_edge_slide_data_create(tc, &group_len);
351 }
352
353 if (sld->sv.is_empty()) {
354 MEM_delete(sld);
355 return nullptr;
356 }
357
358 if (!use_double_side) {
359 /* Single Side Case.
360 * Used by #MESH_OT_offset_edge_loops_slide.
361 * It only slides to the side with the longest length. */
362 struct TMP {
363 float2 accum;
364 int count;
365 } zero{};
366
367 Array<TMP> array_len(group_len, zero);
368 for (TransDataEdgeSlideVert &sv : sld->sv) {
369 array_len[sv.loop_nr].accum += float2(math::length(sv.dir_side[0]),
370 math::length(sv.dir_side[1]));
371 array_len[sv.loop_nr].count++;
372 }
373
374 for (TMP &accum : array_len) {
375 accum.accum /= accum.count;
376 }
377
378 for (TransDataEdgeSlideVert &sv : sld->sv) {
379 if (array_len[sv.loop_nr].accum[1] > array_len[sv.loop_nr].accum[0]) {
380 sv.dir_side[0] = sv.dir_side[1];
381 }
382 sv.dir_side[1] = float3(0);
383 sv.edge_len = math::length(sv.dir_side[0]);
384 }
385 }
386
387 sld->curr_sv_index = 0;
388 sld->update_proj_mat(t, tc);
389
390 calcEdgeSlide_mval_range(t, tc, sld, group_len, t->mval, use_double_side);
391
392 return sld;
393}
394
395static void freeEdgeSlideVerts(TransInfo * /*t*/,
396 TransDataContainer * /*tc*/,
397 TransCustomData *custom_data)
398{
399 EdgeSlideData *sld = static_cast<EdgeSlideData *>(custom_data->data);
400
401 if (sld == nullptr) {
402 return;
403 }
404
405 MEM_delete(sld);
406
407 custom_data->data = nullptr;
408}
409
411{
412 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
413
414 if (slp) {
415 bool is_event_handled = t->redraw && (event->type != MOUSEMOVE);
416 slp->update_status_bar |= is_event_handled;
417 switch (event->type) {
418 case EVT_EKEY:
419 if (event->val == KM_PRESS) {
420 slp->use_even = !slp->use_even;
422 slp->update_status_bar = true;
423 return TREDRAW_HARD;
424 }
425 break;
426 case EVT_FKEY:
427 if (event->val == KM_PRESS) {
428 slp->flipped = !slp->flipped;
430 slp->update_status_bar = true;
431 return TREDRAW_HARD;
432 }
433 break;
434 case EVT_CKEY:
435 /* Use like a modifier key. */
436 if (event->val == KM_PRESS) {
437 t->flag ^= T_ALT_TRANSFORM;
439 slp->update_status_bar = true;
440 return TREDRAW_HARD;
441 }
442 break;
443 case MOUSEMOVE:
445 break;
446 default:
447 break;
448 }
449 }
450 return TREDRAW_NOTHING;
451}
452
454{
456 if (sld == nullptr) {
457 return;
458 }
459
460 const EdgeSlideParams *slp = static_cast<const EdgeSlideParams *>(t->custom.mode.data);
461 const bool is_clamp = !(t->flag & T_ALT_TRANSFORM);
462
463 const float line_size = UI_GetThemeValuef(TH_OUTLINE_WIDTH) + 0.5f;
464
466
468
469 if (t->spacetype == SPACE_VIEW3D) {
471 GPU_matrix_mul(TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->object_to_world().ptr());
472 }
473
475
477
478 TransDataEdgeSlideVert *curr_sv = &sld->sv[sld->curr_sv_index];
479 const float3 curr_sv_co_orig = curr_sv->v_co_orig();
480
481 if (slp->use_even == true) {
482 /* Even mode. */
483 float co_a[3], co_b[3], co_mark[3];
484 const float fac = (slp->perc + 1.0f) / 2.0f;
485 const float ctrl_size = UI_GetThemeValuef(TH_FACEDOT_SIZE) + 1.5f;
486 const float guide_size = ctrl_size - 0.5f;
487 const int alpha_shade = -30;
488
489 add_v3_v3v3(co_a, curr_sv_co_orig, curr_sv->dir_side[0]);
490 add_v3_v3v3(co_b, curr_sv_co_orig, curr_sv->dir_side[1]);
491
492 GPU_line_width(line_size);
495 if (!math::is_zero(curr_sv->dir_side[0])) {
496 immVertex3fv(pos, co_a);
497 immVertex3fv(pos, curr_sv_co_orig);
498 }
499 if (!math::is_zero(curr_sv->dir_side[1])) {
500 immVertex3fv(pos, co_b);
501 immVertex3fv(pos, curr_sv_co_orig);
502 }
503 immEnd();
505
507 {
508 float *co_test = nullptr;
509 if (slp->flipped) {
510 if (!math::is_zero(curr_sv->dir_side[1])) {
511 co_test = co_b;
512 }
513 }
514 else {
515 if (!math::is_zero(curr_sv->dir_side[0])) {
516 co_test = co_a;
517 }
518 }
519
520 if (co_test != nullptr) {
521 immUniformThemeColorShadeAlpha(TH_SELECT, -30, alpha_shade);
522 GPU_point_size(ctrl_size);
524 immVertex3fv(pos, co_test);
525 immEnd();
526 }
527 }
528
529 immUniformThemeColorShadeAlpha(TH_SELECT, 255, alpha_shade);
530 GPU_point_size(guide_size);
532 interp_line_v3_v3v3v3(co_mark, co_b, curr_sv_co_orig, co_a, fac);
533 immVertex3fv(pos, co_mark);
534 immEnd();
535 }
536 else if (is_clamp == false) {
537 const int side_index = slp->curr_side_unclamp;
538 const int alpha_shade = -160;
539
540 GPU_line_width(line_size);
542 immBegin(GPU_PRIM_LINES, sld->sv.size() * 2);
543
544 /* TODO(@ideasman42): Loop over all verts. */
545 for (TransDataEdgeSlideVert &sv : sld->sv) {
546 float a[3], b[3];
547
548 if (!is_zero_v3(sv.dir_side[side_index])) {
549 copy_v3_v3(a, sv.dir_side[side_index]);
550 }
551 else {
552 copy_v3_v3(a, sv.dir_side[!side_index]);
553 }
554
555 mul_v3_fl(a, 100.0f);
556 negate_v3_v3(b, a);
557
558 const float3 sv_co_orig = sv.v_co_orig();
559 add_v3_v3(a, sv_co_orig);
560 add_v3_v3(b, sv_co_orig);
561
562 immVertex3fv(pos, a);
564 }
565 immEnd();
566 }
567 else {
568 /* Common case. */
569 const int alpha_shade = -160;
570
571 float co_dir[3];
572 add_v3_v3v3(co_dir, curr_sv_co_orig, curr_sv->dir_side[slp->curr_side_unclamp]);
573
574 GPU_line_width(line_size);
577 immVertex3fv(pos, curr_sv_co_orig);
578 immVertex3fv(pos, co_dir);
579 immEnd();
580 }
581
583
584 if (t->spacetype == SPACE_VIEW3D) {
587 }
588
590}
591
592static void edge_slide_snap_apply(TransInfo *t, float *value)
593{
595 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
596 EdgeSlideData *sld_active = static_cast<EdgeSlideData *>(tc->custom.mode.data);
597 TransDataEdgeSlideVert *sv = &sld_active->sv[sld_active->curr_sv_index];
598 float3 co_orig, co_dest[2], dvec, snap_point;
599 co_orig = sv->v_co_orig();
600 co_dest[0] = co_orig + sv->dir_side[0];
601 co_dest[1] = co_orig + sv->dir_side[1];
602
603 if (tc->use_local_mat) {
604 mul_m4_v3(tc->mat, co_orig);
605 mul_m4_v3(tc->mat, co_dest[0]);
606 mul_m4_v3(tc->mat, co_dest[1]);
607 }
608
609 getSnapPoint(t, dvec);
610 sub_v3_v3(dvec, t->tsnap.snap_source);
611 add_v3_v3v3(snap_point, co_orig, dvec);
612
613 float perc = *value;
614 int side_index;
615 float t_mid;
616 if (slp->use_even == false) {
617 const bool is_clamp = !(t->flag & T_ALT_TRANSFORM);
618 if (is_clamp) {
619 side_index = perc < 0.0f;
620 }
621 else {
622 /* Use the side indicated in `EdgeSlideParams::curr_side_unclamp` as long as that side is not
623 * zero length. */
624 side_index = int(slp->curr_side_unclamp ==
626 }
627 }
628 else {
629 /* Could be pre-calculated. */
630 t_mid = line_point_factor_v3(float3{0.0f, 0.0f, 0.0f}, sv->dir_side[0], sv->dir_side[1]);
631
632 float t_snap = line_point_factor_v3(snap_point, co_dest[0], co_dest[1]);
633 side_index = t_snap >= t_mid;
634 }
635
637 float co_dir[3];
638 sub_v3_v3v3(co_dir, co_dest[side_index], co_orig);
639 normalize_v3(co_dir);
642 }
643 else {
645 }
646 add_v3_v3v3(snap_point, co_orig, dvec);
647 }
648
649 perc = line_point_factor_v3(snap_point, co_orig, co_dest[side_index]);
650 if (slp->use_even == false) {
651 if (side_index) {
652 perc *= -1;
653 }
654 }
655 else {
656 if (!side_index) {
657 perc = (1.0f - perc) * t_mid;
658 }
659 else {
660 perc = perc * (1.0f - t_mid) + t_mid;
661 }
662
663 if (slp->flipped) {
664 perc = 1.0f - perc;
665 }
666
667 perc = (2 * perc) - 1.0f;
668
669 if (!slp->flipped) {
670 perc *= -1;
671 }
672 }
673
674 *value = perc;
675}
676
678 const float fac,
679 const float curr_length_fac,
680 const int curr_side_unclamp,
681 const bool use_clamp,
682 const bool use_even,
683 const bool use_flip,
684 float r_co[3])
685{
686 copy_v3_v3(r_co, sv.v_co_orig());
687
688 if (use_even == false) {
689 if (use_clamp) {
690 const int side_index = (fac < 0.0f);
691 const float fac_final = fabsf(fac);
692 madd_v3_v3fl(r_co, sv.dir_side[side_index], fac_final);
693 }
694 else {
695 int side_index = curr_side_unclamp;
696 if (is_zero_v3(sv.dir_side[side_index])) {
697 side_index = int(!side_index);
698 }
699 const float fac_final = (side_index == (fac < 0.0f) ? fabsf(fac) : -fabsf(fac));
700 madd_v3_v3fl(r_co, sv.dir_side[side_index], fac_final);
701 }
702 }
703 else {
714 if (sv.edge_len > FLT_EPSILON) {
715 float co_a[3], co_b[3];
716 const float fac_final = min_ff(sv.edge_len, curr_length_fac) / sv.edge_len;
717
718 add_v3_v3v3(co_a, r_co, sv.dir_side[0]);
719 add_v3_v3v3(co_b, r_co, sv.dir_side[1]);
720
721 if (use_flip) {
722 interp_line_v3_v3v3v3(r_co, co_b, r_co, co_a, fac_final);
723 }
724 else {
725 interp_line_v3_v3v3v3(r_co, co_a, r_co, co_b, fac_final);
726 }
727 }
728 }
729}
730
731static void doEdgeSlide(TransInfo *t, float perc)
732{
733 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
734 EdgeSlideData *sld_active = edgeSlideFirstGet(t);
735
736 slp->perc = perc;
737
738 const bool use_clamp = !(t->flag & T_ALT_TRANSFORM);
739 const bool use_even = slp->use_even;
740 const bool use_flip = slp->flipped;
741
742 const int curr_side_unclamp = slp->curr_side_unclamp;
743 float curr_length_fac = 0.0f;
744 if (use_even) {
745 TransDataEdgeSlideVert *sv_active = &sld_active->sv[sld_active->curr_sv_index];
746 curr_length_fac = sv_active->edge_len * (((use_flip ? perc : -perc) + 1.0f) / 2.0f);
747 }
748 else if (use_clamp) {
749 slp->curr_side_unclamp = (perc < 0.0f);
750 }
751
753 EdgeSlideData *sld = static_cast<EdgeSlideData *>(tc->custom.mode.data);
754
755 if (sld == nullptr) {
756 continue;
757 }
758
759 for (TransDataEdgeSlideVert &sv : sld->sv) {
761 sv, perc, curr_length_fac, curr_side_unclamp, use_clamp, use_even, use_flip, sv.td->loc);
762 }
763 }
764}
765
767{
768 char str[UI_MAX_DRAW_STR];
769 size_t ofs = 0;
770 float final;
771 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
772 bool flipped = slp->flipped;
773 bool use_even = slp->use_even;
774 const bool is_clamp = !(t->flag & T_ALT_TRANSFORM);
775 const bool is_constrained = !(is_clamp == false || hasNumInput(&t->num));
776 const bool is_precision = t->modifiers & MOD_PRECISION;
777 const bool is_snap = t->modifiers & MOD_SNAP;
778 const bool is_snap_invert = t->modifiers & MOD_SNAP_INVERT;
779
780 final = t->values[0] + t->values_modal_offset[0];
781
783 if (!validSnap(t)) {
784 transform_snap_increment(t, &final);
785 }
786
787 /* Only do this so out of range values are not displayed. */
788 if (is_constrained) {
789 CLAMP(final, -1.0f, 1.0f);
790 }
791
792 applyNumInput(&t->num, &final);
793
794 t->values_final[0] = final;
795
796 /* Header string. */
797 ofs += BLI_strncpy_rlen(str + ofs, RPT_("Edge Slide: "), sizeof(str) - ofs);
798 if (hasNumInput(&t->num)) {
799 char c[NUM_STR_REP_LEN];
800 outputNumInput(&(t->num), c, t->scene->unit);
801 ofs += BLI_strncpy_rlen(str + ofs, &c[0], sizeof(str) - ofs);
802 }
803 else {
804 ofs += BLI_snprintf_rlen(str + ofs, sizeof(str) - ofs, "%.4f ", final);
805 }
806 /* Done with header string. */
807
808 /* Do stuff here. */
809 doEdgeSlide(t, final);
810
811 recalc_data(t);
812
814
815 wmOperator *op = slp->op;
816 if (!op) {
817 return;
818 }
819
820 if (slp->update_status_bar) {
821 slp->update_status_bar = false;
822
823 WorkspaceStatus status(t->context);
824 status.opmodal(IFACE_("Confirm"), op->type, TFM_MODAL_CONFIRM);
825 status.opmodal(IFACE_("Cancel"), op->type, TFM_MODAL_CANCEL);
826 status.opmodal(IFACE_("Snap"), op->type, TFM_MODAL_SNAP_TOGGLE, is_snap);
827 status.opmodal(IFACE_("Snap Invert"), op->type, TFM_MODAL_SNAP_INV_ON, is_snap_invert);
828 status.opmodal(IFACE_("Set Snap Base"), op->type, TFM_MODAL_EDIT_SNAP_SOURCE_ON);
829 status.opmodal(IFACE_("Move"), op->type, TFM_MODAL_TRANSLATE);
830 status.opmodal(IFACE_("Rotate"), op->type, TFM_MODAL_ROTATE);
831 status.opmodal(IFACE_("Resize"), op->type, TFM_MODAL_RESIZE);
832 status.opmodal(IFACE_("Precision Mode"), op->type, TFM_MODAL_PRECISION, is_precision);
833 status.item_bool(IFACE_("Clamp"), is_clamp, ICON_EVENT_C, ICON_EVENT_ALT);
834 status.item_bool(IFACE_("Even"), use_even, ICON_EVENT_E);
835 if (use_even) {
836 status.item_bool(IFACE_("Flipped"), flipped, ICON_EVENT_F);
837 }
838 }
839}
840
841static void edge_slide_transform_matrix_fn(TransInfo *t, float mat_xform[4][4])
842{
843 float delta[3], orig_co[3], final_co[3];
844
845 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
847 EdgeSlideData *sld_active = static_cast<EdgeSlideData *>(tc->custom.mode.data);
848 TransDataEdgeSlideVert &sv_active = sld_active->sv[sld_active->curr_sv_index];
849
850 copy_v3_v3(orig_co, sv_active.v_co_orig());
851
852 const float fac = t->values_final[0];
853 float curr_length_fac = 0.0f;
854 if (slp->use_even) {
855 curr_length_fac = sv_active.edge_len * (((slp->flipped ? fac : -fac) + 1.0f) / 2.0f);
856 }
857
858 edge_slide_apply_elem(sv_active,
859 fac,
860 curr_length_fac,
862 !(t->flag & T_ALT_TRANSFORM),
863 slp->use_even,
864 slp->flipped,
865 final_co);
866
867 if (tc->use_local_mat) {
868 mul_m4_v3(tc->mat, orig_co);
869 mul_m4_v3(tc->mat, final_co);
870 }
871
872 sub_v3_v3v3(delta, final_co, orig_co);
873 add_v3_v3(mat_xform[3], delta);
874}
875
877 wmOperator *op,
878 bool use_double_side,
879 bool use_even,
880 bool flipped,
881 bool use_clamp)
882{
883 EdgeSlideData *sld;
884 bool ok = false;
885
886 t->mode = TFM_EDGE_SLIDE;
887
888 {
890 slp->op = op;
891 slp->use_even = use_even;
892 slp->flipped = flipped;
893 /* Happens to be best for single-sided. */
894 if (use_double_side == false) {
895 slp->flipped = !flipped;
896 }
897 slp->perc = 0.0f;
898 slp->update_status_bar = true;
899
900 if (!use_clamp) {
901 t->flag |= T_ALT_TRANSFORM;
902 }
903
904 t->custom.mode.data = slp;
905 t->custom.mode.use_free = true;
906 }
907
909 sld = createEdgeSlideVerts(t, tc, use_double_side);
910 if (sld) {
911 tc->custom.mode.data = sld;
912 tc->custom.mode.free_cb = freeEdgeSlideVerts;
913 ok = true;
914 }
915 }
916
917 if (!ok) {
918 t->state = TRANS_CANCEL;
919 return;
920 }
921
922 /* Set custom point first if you want value to be initialized by init. */
925
926 t->idx_max = 0;
927 t->num.idx_max = 0;
928 t->snap[0] = 0.1f;
929 t->snap[1] = t->snap[0] * 0.1f;
930
931 copy_v3_fl(t->num.val_inc, t->snap[0]);
932 t->num.unit_sys = t->scene->unit.system;
933 t->num.unit_type[0] = B_UNIT_NONE;
934}
935
937{
938 bool use_double_side = true;
939 bool use_even = false;
940 bool flipped = false;
941 bool use_clamp = true;
942 if (op) {
943 PropertyRNA *prop;
944 /* The following properties could be unset when transitioning from this
945 * operator to another and back. For example pressing "G" to move, and
946 * then "G" again to go back to edge slide. */
947 prop = RNA_struct_find_property(op->ptr, "single_side");
948 use_double_side = (prop) ? !RNA_property_boolean_get(op->ptr, prop) : true;
949 prop = RNA_struct_find_property(op->ptr, "use_even");
950 use_even = (prop) ? RNA_property_boolean_get(op->ptr, prop) : false;
951 prop = RNA_struct_find_property(op->ptr, "flipped");
952 flipped = (prop) ? RNA_property_boolean_get(op->ptr, prop) : false;
953 prop = RNA_struct_find_property(op->ptr, "use_clamp");
954 use_clamp = (prop) ? RNA_property_boolean_get(op->ptr, prop) : true;
955 }
956 initEdgeSlide_ex(t, op, use_double_side, use_even, flipped, use_clamp);
957}
958
960
961/* -------------------------------------------------------------------- */
964
966{
968 EdgeSlideData *sld = static_cast<EdgeSlideData *>(tc->custom.mode.data);
969 if (sld) {
970 sld->update_proj_mat(t, tc);
971 TransDataEdgeSlideVert *curr_sv = &sld->sv[sld->curr_sv_index];
972
973 float2 sco_a, sco_b;
974 sld->project(curr_sv, sco_a, sco_b);
975 float2 mval_dir = sco_b - sco_a;
976 edge_slide_data_init_mval(&t->mouse, sld, mval_dir);
977 }
978 }
979
981 setCustomPoints(t, &t->mouse, sld->mval_end, sld->mval_start);
982}
983
985
987 /*flags*/ T_NO_CONSTRAINT,
988 /*init_fn*/ initEdgeSlide,
989 /*transform_fn*/ applyEdgeSlide,
990 /*transform_matrix_fn*/ edge_slide_transform_matrix_fn,
991 /*handle_event_fn*/ handleEventEdgeSlide,
992 /*snap_distance_fn*/ transform_snap_distance_len_squared_fn,
993 /*snap_apply_fn*/ edge_slide_snap_apply,
994 /*draw_fn*/ drawEdgeSlide,
995};
996
997} // namespace blender::ed::transform
blender::Span< blender::float3 > BKE_editmesh_vert_coords_when_deformed(Depsgraph *depsgraph, BMEditMesh *em, Scene *scene, Object *obedit, blender::Array< blender::float3 > &r_alloc)
BMEditMesh * BKE_editmesh_from_object(Object *ob)
Return the BMEditMesh for a given object.
Definition editmesh.cc:61
BMBVHTree * BKE_bmbvh_new_from_editmesh(struct BMEditMesh *em, int flag, const blender::float3 *cos_cage, bool cos_cage_free)
void BKE_bmbvh_free(BMBVHTree *tree)
@ BMBVH_RESPECT_HIDDEN
@ B_UNIT_NONE
Definition BKE_unit.hh:123
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
MINLINE float min_ff(float a, float b)
float line_point_factor_v3(const float p[3], const float l1[3], const float l2[3])
float dist_squared_to_line_segment_v2(const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:291
void mul_m4_v3(const float M[4][4], float r[3])
MINLINE float len_squared_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void madd_v3_v3fl(float r[3], const float a[3], float f)
MINLINE float len_squared_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v3_v3(float r[3], const float a[3])
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void mul_v2_fl(float r[2], float f)
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void negate_v3_v3(float r[3], const float a[3])
void copy_vn_fl(float *array_tar, int size, float val)
void interp_v3_v3v3(float r[3], const float a[3], const float b[3], float t)
MINLINE void add_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE bool is_zero_v3(const float v[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void zero_v2(float r[2])
MINLINE void swap_v3_v3(float a[3], float b[3])
MINLINE void copy_v3_fl(float r[3], float f)
MINLINE void add_v3_v3(float r[3], const float a[3])
MINLINE float normalize_v3(float n[3])
size_t BLI_snprintf_rlen(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
char char size_t BLI_strncpy_rlen(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
unsigned int uint
#define CLAMP(a, b, c)
#define UNLIKELY(x)
#define RPT_(msgid)
#define IFACE_(msgid)
T * DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
@ OB_WIRE
@ OB_MESH
@ SCE_SNAP_TO_EDGE
@ SCE_SNAP_TO_FACE
@ SPACE_VIEW3D
bool BMBVH_EdgeVisible(const BMBVHTree *tree, const BMEdge *e, const Depsgraph *depsgraph, const ARegion *region, const View3D *v3d, const Object *obedit)
#define NUM_STR_REP_LEN
bool applyNumInput(NumInput *n, float *vec)
Definition numinput.cc:189
void outputNumInput(NumInput *n, char *str, const UnitSettings &unit_settings)
Definition numinput.cc:87
bool hasNumInput(const NumInput *n)
Definition numinput.cc:170
void ED_area_status_text(ScrArea *area, const char *str)
Definition area.cc:872
#define XRAY_ENABLED(v3d)
blender::float4x4 ED_view3d_ob_project_mat_get(const RegionView3D *rv3d, const Object *ob)
void immEnd()
void immUnbindProgram()
void immUniformThemeColorShadeAlpha(int color_id, int color_offset, int alpha_offset)
void immBindBuiltinProgram(eGPUBuiltinShader shader_id)
void immBeginAtMost(GPUPrimType, uint max_vertex_len)
GPUVertFormat * immVertexFormat()
void immVertex3fv(uint attr_id, const float data[3])
void immBegin(GPUPrimType, uint vertex_len)
void GPU_matrix_push()
#define GPU_matrix_mul(x)
void GPU_matrix_pop()
@ GPU_PRIM_LINES
@ GPU_PRIM_POINTS
@ GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA
@ GPU_SHADER_3D_UNIFORM_COLOR
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(eGPUBlend blend)
Definition gpu_state.cc:42
void GPU_line_width(float width)
Definition gpu_state.cc:166
void GPU_point_size(float size)
Definition gpu_state.cc:172
@ GPU_DEPTH_LESS_EQUAL
Definition GPU_state.hh:114
@ GPU_DEPTH_NONE
Definition GPU_state.hh:111
void GPU_depth_test(eGPUDepthTest test)
Definition gpu_state.cc:68
@ GPU_FETCH_FLOAT
uint GPU_vertformat_attr_add(GPUVertFormat *, blender::StringRef name, GPUVertCompType, uint comp_len, GPUVertFetchMode)
@ GPU_COMP_F32
#define UI_MAX_DRAW_STR
@ TH_FACEDOT_SIZE
@ TH_EDGE_SELECT
@ TH_OUTLINE_WIDTH
@ TH_SELECT
float UI_GetThemeValuef(int colorid)
void UI_view2d_view_to_region_m4(const View2D *v2d, float matrix[4][4]) ATTR_NONNULL()
Definition view2d.cc:1807
@ KM_PRESS
Definition WM_types.hh:308
@ BM_ELEM_HIDDEN
@ BM_ELEM_SELECT
#define BM_elem_flag_test(ele, hflag)
#define BM_ITER_ELEM(ele, iter, data, itype)
@ BM_EDGES_OF_VERT
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
void item_bool(std::string text, bool inverted, int icon1, int icon2=0)
Definition area.cc:995
void opmodal(std::string text, const wmOperatorType *ot, int propvalue, bool inverted=false)
Definition area.cc:1005
constexpr const T * data() const
Definition BLI_span.hh:215
constexpr bool is_empty() const
Definition BLI_span.hh:260
#define fabsf(x)
#define str(s)
uint pos
int count
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
void initMouseInputMode(TransInfo *t, MouseInput *mi, MouseInputMode mode)
void recalc_data(TransInfo *t)
static void edge_slide_snap_apply(TransInfo *t, float *value)
static EdgeSlideData * edgeSlideFirstGet(TransInfo *t)
void getSnapPoint(const TransInfo *t, float vec[3])
Array< TransDataEdgeSlideVert > transform_mesh_edge_slide_data_create(const TransDataContainer *tc, int *r_group_len)
static void interp_line_v3_v3v3v3(float p[3], const float v1[3], const float v2[3], const float v3[3], float t)
static void initEdgeSlide_ex(TransInfo *t, wmOperator *op, bool use_double_side, bool use_even, bool flipped, bool use_clamp)
bool transform_snap_increment(const TransInfo *t, float *r_val)
void transform_snap_mixed_apply(TransInfo *t, float *vec)
static void applyEdgeSlide(TransInfo *t)
static void edge_slide_apply_elem(const TransDataEdgeSlideVert &sv, const float fac, const float curr_length_fac, const int curr_side_unclamp, const bool use_clamp, const bool use_even, const bool use_flip, float r_co[3])
bool validSnap(const TransInfo *t)
static void calcEdgeSlide_mval_range(TransInfo *t, TransDataContainer *tc, EdgeSlideData *sld, const int loop_nr, const float2 &mval, const bool use_calc_direction)
static void doEdgeSlide(TransInfo *t, float perc)
void transform_constraint_snap_axis_to_face(const TransInfo *t, const float axis[3], float r_out[3])
void transform_constraint_snap_axis_to_edge(const TransInfo *t, const float axis[3], float r_out[3])
Array< TransDataEdgeSlideVert > transform_mesh_uv_edge_slide_data_create(const TransInfo *t, TransDataContainer *tc, int *r_group_len)
static void freeEdgeSlideVerts(TransInfo *, TransDataContainer *, TransCustomData *custom_data)
static void calcEdgeSlideCustomPoints(TransInfo *t)
float transform_snap_distance_len_squared_fn(TransInfo *, const float p1[3], const float p2[3])
void setCustomPoints(TransInfo *t, MouseInput *mi, const int mval_start[2], const int mval_end[2])
static void initEdgeSlide(TransInfo *t, wmOperator *op)
static EdgeSlideData * createEdgeSlideVerts(TransInfo *t, TransDataContainer *tc, const bool use_double_side)
void applyMouseInput(TransInfo *t, MouseInput *mi, const float2 &mval, float output[3])
static void drawEdgeSlide(TransInfo *t)
static eRedrawFlag handleEventEdgeSlide(TransInfo *t, const wmEvent *event)
static void edge_slide_data_init_mval(MouseInput *mi, EdgeSlideData *sld, float *mval_dir)
static void edge_slide_transform_matrix_fn(TransInfo *t, float mat_xform[4][4])
static TransDataContainer * edge_slide_container_first_ok(TransInfo *t)
TransConvertTypeInfo TransConvertType_MeshUV
void transform_mode_edge_slide_reproject_input(TransInfo *t)
static bool is_vert_slide_visible_bmesh(TransInfo *t, TransDataContainer *tc, const View3D *v3d, const BMBVHTree *bmbvh, TransDataEdgeSlideVert *sv)
T length(const VecBase< T, Size > &a)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
bool is_zero(const T &a)
VectorT project_point(const MatT &mat, const VectorT &point)
MatBase< float, 4, 4 > float4x4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop)
#define FLT_MAX
Definition stdcycles.h:14
void * regiondata
void * first
short idx_max
float val_inc[NUM_MAX_ELEMENTS]
int unit_type[NUM_MAX_ELEMENTS]
struct UnitSettings unit
ListBase spacedata
const c_style_mat & ptr() const
VecBase< T, 2 > xy() const
void project(const TransDataEdgeSlideVert *svert, float2 &r_sco_a, float2 &r_sco_b) const
void update_proj_mat(TransInfo *t, const TransDataContainer *tc)
TransConvertTypeInfo * data_type
Definition transform.hh:805
TransCustomDataContainer custom
Definition transform.hh:968
wmEventType type
Definition WM_types.hh:754
short val
Definition WM_types.hh:756
struct wmOperatorType * type
struct PointerRNA * ptr
i
Definition text_draw.cc:230
#define TRANS_DATA_CONTAINER_FIRST_OK(t)
Definition transform.hh:37
#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.
@ EVT_EKEY
@ EVT_FKEY
@ EVT_CKEY
@ MOUSEMOVE