Blender V5.0
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_utf8.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 immVertexFormat(), "pos", blender::gpu::VertAttrType::SFLOAT_32_32_32);
476
478
479 TransDataEdgeSlideVert *curr_sv = &sld->sv[sld->curr_sv_index];
480 const float3 curr_sv_co_orig = curr_sv->v_co_orig();
481
482 if (slp->use_even == true) {
483 /* Even mode. */
484 float co_a[3], co_b[3], co_mark[3];
485 const float fac = (slp->perc + 1.0f) / 2.0f;
486 const float ctrl_size = UI_GetThemeValuef(TH_FACEDOT_SIZE) + 1.5f;
487 const float guide_size = ctrl_size - 0.5f;
488 const int alpha_shade = -30;
489
490 add_v3_v3v3(co_a, curr_sv_co_orig, curr_sv->dir_side[0]);
491 add_v3_v3v3(co_b, curr_sv_co_orig, curr_sv->dir_side[1]);
492
493 GPU_line_width(line_size);
496 if (!math::is_zero(curr_sv->dir_side[0])) {
497 immVertex3fv(pos, co_a);
498 immVertex3fv(pos, curr_sv_co_orig);
499 }
500 if (!math::is_zero(curr_sv->dir_side[1])) {
501 immVertex3fv(pos, co_b);
502 immVertex3fv(pos, curr_sv_co_orig);
503 }
504 immEnd();
506
508 {
509 float *co_test = nullptr;
510 if (slp->flipped) {
511 if (!math::is_zero(curr_sv->dir_side[1])) {
512 co_test = co_b;
513 }
514 }
515 else {
516 if (!math::is_zero(curr_sv->dir_side[0])) {
517 co_test = co_a;
518 }
519 }
520
521 if (co_test != nullptr) {
522 immUniformThemeColorShadeAlpha(TH_SELECT, -30, alpha_shade);
523 GPU_point_size(ctrl_size);
525 immVertex3fv(pos, co_test);
526 immEnd();
527 }
528 }
529
530 immUniformThemeColorShadeAlpha(TH_SELECT, 255, alpha_shade);
531 GPU_point_size(guide_size);
533 interp_line_v3_v3v3v3(co_mark, co_b, curr_sv_co_orig, co_a, fac);
534 immVertex3fv(pos, co_mark);
535 immEnd();
536 }
537 else if (is_clamp == false) {
538 const int side_index = slp->curr_side_unclamp;
539 const int alpha_shade = -160;
540
541 GPU_line_width(line_size);
543 immBegin(GPU_PRIM_LINES, sld->sv.size() * 2);
544
545 /* TODO(@ideasman42): Loop over all verts. */
546 for (TransDataEdgeSlideVert &sv : sld->sv) {
547 float a[3], b[3];
548
549 if (!is_zero_v3(sv.dir_side[side_index])) {
550 copy_v3_v3(a, sv.dir_side[side_index]);
551 }
552 else {
553 copy_v3_v3(a, sv.dir_side[!side_index]);
554 }
555
556 mul_v3_fl(a, 100.0f);
557 negate_v3_v3(b, a);
558
559 const float3 sv_co_orig = sv.v_co_orig();
560 add_v3_v3(a, sv_co_orig);
561 add_v3_v3(b, sv_co_orig);
562
563 immVertex3fv(pos, a);
565 }
566 immEnd();
567 }
568 else {
569 /* Common case. */
570 const int alpha_shade = -160;
571
572 float co_dir[3];
573 add_v3_v3v3(co_dir, curr_sv_co_orig, curr_sv->dir_side[slp->curr_side_unclamp]);
574
575 GPU_line_width(line_size);
578 immVertex3fv(pos, curr_sv_co_orig);
579 immVertex3fv(pos, co_dir);
580 immEnd();
581 }
582
584
585 if (t->spacetype == SPACE_VIEW3D) {
588 }
589
591}
592
593static void edge_slide_snap_apply(TransInfo *t, float *value)
594{
596 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
597 EdgeSlideData *sld_active = static_cast<EdgeSlideData *>(tc->custom.mode.data);
598 TransDataEdgeSlideVert *sv = &sld_active->sv[sld_active->curr_sv_index];
599 float3 co_orig, co_dest[2], dvec, snap_point;
600 co_orig = sv->v_co_orig();
601 co_dest[0] = co_orig + sv->dir_side[0];
602 co_dest[1] = co_orig + sv->dir_side[1];
603
604 if (tc->use_local_mat) {
605 mul_m4_v3(tc->mat, co_orig);
606 mul_m4_v3(tc->mat, co_dest[0]);
607 mul_m4_v3(tc->mat, co_dest[1]);
608 }
609
610 getSnapPoint(t, dvec);
611 sub_v3_v3(dvec, t->tsnap.snap_source);
612 add_v3_v3v3(snap_point, co_orig, dvec);
613
614 float perc = *value;
615 int side_index;
616 float t_mid;
617 if (slp->use_even == false) {
618 const bool is_clamp = !(t->flag & T_ALT_TRANSFORM);
619 if (is_clamp) {
620 side_index = perc < 0.0f;
621 }
622 else {
623 /* Use the side indicated in `EdgeSlideParams::curr_side_unclamp` as long as that side is not
624 * zero length. */
625 side_index = int(slp->curr_side_unclamp ==
627 }
628 }
629 else {
630 /* Could be pre-calculated. */
631 t_mid = line_point_factor_v3(float3{0.0f, 0.0f, 0.0f}, sv->dir_side[0], sv->dir_side[1]);
632
633 float t_snap = line_point_factor_v3(snap_point, co_dest[0], co_dest[1]);
634 side_index = t_snap >= t_mid;
635 }
636
638 float co_dir[3];
639 sub_v3_v3v3(co_dir, co_dest[side_index], co_orig);
640 normalize_v3(co_dir);
643 }
644 else {
646 }
647 add_v3_v3v3(snap_point, co_orig, dvec);
648 }
649
650 perc = line_point_factor_v3(snap_point, co_orig, co_dest[side_index]);
651 if (slp->use_even == false) {
652 if (side_index) {
653 perc *= -1;
654 }
655 }
656 else {
657 if (!side_index) {
658 perc = (1.0f - perc) * t_mid;
659 }
660 else {
661 perc = perc * (1.0f - t_mid) + t_mid;
662 }
663
664 if (slp->flipped) {
665 perc = 1.0f - perc;
666 }
667
668 perc = (2 * perc) - 1.0f;
669
670 if (!slp->flipped) {
671 perc *= -1;
672 }
673 }
674
675 *value = perc;
676}
677
679 const float fac,
680 const float curr_length_fac,
681 const int curr_side_unclamp,
682 const bool use_clamp,
683 const bool use_even,
684 const bool use_flip,
685 float r_co[3])
686{
687 copy_v3_v3(r_co, sv.v_co_orig());
688
689 if (use_even == false) {
690 if (use_clamp) {
691 const int side_index = (fac < 0.0f);
692 const float fac_final = fabsf(fac);
693 madd_v3_v3fl(r_co, sv.dir_side[side_index], fac_final);
694 }
695 else {
696 int side_index = curr_side_unclamp;
697 if (is_zero_v3(sv.dir_side[side_index])) {
698 side_index = int(!side_index);
699 }
700 const float fac_final = (side_index == (fac < 0.0f) ? fabsf(fac) : -fabsf(fac));
701 madd_v3_v3fl(r_co, sv.dir_side[side_index], fac_final);
702 }
703 }
704 else {
715 if (sv.edge_len > FLT_EPSILON) {
716 float co_a[3], co_b[3];
717 const float fac_final = min_ff(sv.edge_len, curr_length_fac) / sv.edge_len;
718
719 add_v3_v3v3(co_a, r_co, sv.dir_side[0]);
720 add_v3_v3v3(co_b, r_co, sv.dir_side[1]);
721
722 if (use_flip) {
723 interp_line_v3_v3v3v3(r_co, co_b, r_co, co_a, fac_final);
724 }
725 else {
726 interp_line_v3_v3v3v3(r_co, co_a, r_co, co_b, fac_final);
727 }
728 }
729 }
730}
731
732static void doEdgeSlide(TransInfo *t, float perc)
733{
734 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
735 EdgeSlideData *sld_active = edgeSlideFirstGet(t);
736
737 slp->perc = perc;
738
739 const bool use_clamp = !(t->flag & T_ALT_TRANSFORM);
740 const bool use_even = slp->use_even;
741 const bool use_flip = slp->flipped;
742
743 const int curr_side_unclamp = slp->curr_side_unclamp;
744 float curr_length_fac = 0.0f;
745 if (use_even) {
746 TransDataEdgeSlideVert *sv_active = &sld_active->sv[sld_active->curr_sv_index];
747 curr_length_fac = sv_active->edge_len * (((use_flip ? perc : -perc) + 1.0f) / 2.0f);
748 }
749 else if (use_clamp) {
750 slp->curr_side_unclamp = (perc < 0.0f);
751 }
752
754 EdgeSlideData *sld = static_cast<EdgeSlideData *>(tc->custom.mode.data);
755
756 if (sld == nullptr) {
757 continue;
758 }
759
760 for (TransDataEdgeSlideVert &sv : sld->sv) {
762 sv, perc, curr_length_fac, curr_side_unclamp, use_clamp, use_even, use_flip, sv.td->loc);
763 }
764 }
765}
766
768{
769 char str[UI_MAX_DRAW_STR];
770 size_t ofs = 0;
771 float final;
772 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
773 bool flipped = slp->flipped;
774 bool use_even = slp->use_even;
775 const bool is_clamp = !(t->flag & T_ALT_TRANSFORM);
776 const bool is_constrained = !(is_clamp == false || hasNumInput(&t->num));
777 const bool is_precision = t->modifiers & MOD_PRECISION;
778 const bool is_snap = t->modifiers & MOD_SNAP;
779 const bool is_snap_invert = t->modifiers & MOD_SNAP_INVERT;
780
781 final = t->values[0] + t->values_modal_offset[0];
782
784 if (!validSnap(t)) {
785 transform_snap_increment(t, &final);
786 }
787
788 /* Only do this so out of range values are not displayed. */
789 if (is_constrained) {
790 CLAMP(final, -1.0f, 1.0f);
791 }
792
793 applyNumInput(&t->num, &final);
794
795 t->values_final[0] = final;
796
797 /* Header string. */
798 ofs += BLI_strncpy_utf8_rlen(str + ofs, RPT_("Edge Slide: "), sizeof(str) - ofs);
799 if (hasNumInput(&t->num)) {
800 char c[NUM_STR_REP_LEN];
801 outputNumInput(&(t->num), c, t->scene->unit);
802 ofs += BLI_strncpy_utf8_rlen(str + ofs, &c[0], sizeof(str) - ofs);
803 }
804 else {
805 ofs += BLI_snprintf_utf8_rlen(str + ofs, sizeof(str) - ofs, "%.4f ", final);
806 }
807 /* Done with header string. */
808
809 /* Do stuff here. */
810 doEdgeSlide(t, final);
811
812 recalc_data(t);
813
815
816 wmOperator *op = slp->op;
817 if (!op) {
818 return;
819 }
820
821 if (slp->update_status_bar) {
822 slp->update_status_bar = false;
823
825 status.opmodal(IFACE_("Confirm"), op->type, TFM_MODAL_CONFIRM);
826 status.opmodal(IFACE_("Cancel"), op->type, TFM_MODAL_CANCEL);
827 status.opmodal(IFACE_("Snap"), op->type, TFM_MODAL_SNAP_TOGGLE, is_snap);
828 status.opmodal(IFACE_("Snap Invert"), op->type, TFM_MODAL_SNAP_INV_ON, is_snap_invert);
829 status.opmodal(IFACE_("Set Snap Base"), op->type, TFM_MODAL_EDIT_SNAP_SOURCE_ON);
830 status.opmodal(IFACE_("Move"), op->type, TFM_MODAL_TRANSLATE);
831 status.opmodal(IFACE_("Rotate"), op->type, TFM_MODAL_ROTATE);
832 status.opmodal(IFACE_("Resize"), op->type, TFM_MODAL_RESIZE);
833 status.opmodal(IFACE_("Precision Mode"), op->type, TFM_MODAL_PRECISION, is_precision);
834 status.item_bool(IFACE_("Clamp"), is_clamp, ICON_EVENT_C, ICON_EVENT_ALT);
835 status.item_bool(IFACE_("Even"), use_even, ICON_EVENT_E);
836 if (use_even) {
837 status.item_bool(IFACE_("Flipped"), flipped, ICON_EVENT_F);
838 }
839 }
840}
841
842static void edge_slide_transform_matrix_fn(TransInfo *t, float mat_xform[4][4])
843{
844 float delta[3], orig_co[3], final_co[3];
845
846 EdgeSlideParams *slp = static_cast<EdgeSlideParams *>(t->custom.mode.data);
848 EdgeSlideData *sld_active = static_cast<EdgeSlideData *>(tc->custom.mode.data);
849 TransDataEdgeSlideVert &sv_active = sld_active->sv[sld_active->curr_sv_index];
850
851 copy_v3_v3(orig_co, sv_active.v_co_orig());
852
853 const float fac = t->values_final[0];
854 float curr_length_fac = 0.0f;
855 if (slp->use_even) {
856 curr_length_fac = sv_active.edge_len * (((slp->flipped ? fac : -fac) + 1.0f) / 2.0f);
857 }
858
859 edge_slide_apply_elem(sv_active,
860 fac,
861 curr_length_fac,
863 !(t->flag & T_ALT_TRANSFORM),
864 slp->use_even,
865 slp->flipped,
866 final_co);
867
868 if (tc->use_local_mat) {
869 mul_m4_v3(tc->mat, orig_co);
870 mul_m4_v3(tc->mat, final_co);
871 }
872
873 sub_v3_v3v3(delta, final_co, orig_co);
874 add_v3_v3(mat_xform[3], delta);
875}
876
878 wmOperator *op,
879 bool use_double_side,
880 bool use_even,
881 bool flipped,
882 bool use_clamp)
883{
884 EdgeSlideData *sld;
885 bool ok = false;
886
887 t->mode = TFM_EDGE_SLIDE;
888
889 {
891 slp->op = op;
892 slp->use_even = use_even;
893 slp->flipped = flipped;
894 /* Happens to be best for single-sided. */
895 if (use_double_side == false) {
896 slp->flipped = !flipped;
897 }
898 slp->perc = 0.0f;
899 slp->update_status_bar = true;
900
901 if (!use_clamp) {
902 t->flag |= T_ALT_TRANSFORM;
903 }
904
905 t->custom.mode.data = slp;
906 t->custom.mode.use_free = true;
907 }
908
910 sld = createEdgeSlideVerts(t, tc, use_double_side);
911 if (sld) {
912 tc->custom.mode.data = sld;
913 tc->custom.mode.free_cb = freeEdgeSlideVerts;
914 ok = true;
915 }
916 }
917
918 if (!ok) {
919 t->state = TRANS_CANCEL;
920 return;
921 }
922
923 /* Set custom point first if you want value to be initialized by init. */
926
927 t->idx_max = 0;
928 t->num.idx_max = 0;
929 t->increment[0] = 0.1f;
930 t->increment_precision = 0.1f;
931
932 copy_v3_fl(t->num.val_inc, t->increment[0]);
933 t->num.unit_sys = t->scene->unit.system;
934 t->num.unit_type[0] = B_UNIT_NONE;
935}
936
938{
939 bool use_double_side = true;
940 bool use_even = false;
941 bool flipped = false;
942 bool use_clamp = true;
943 if (op) {
944 PropertyRNA *prop;
945 /* The following properties could be unset when transitioning from this
946 * operator to another and back. For example pressing "G" to move, and
947 * then "G" again to go back to edge slide. */
948 prop = RNA_struct_find_property(op->ptr, "single_side");
949 use_double_side = (prop) ? !RNA_property_boolean_get(op->ptr, prop) : true;
950 prop = RNA_struct_find_property(op->ptr, "use_even");
951 use_even = (prop) ? RNA_property_boolean_get(op->ptr, prop) : false;
952 prop = RNA_struct_find_property(op->ptr, "flipped");
953 flipped = (prop) ? RNA_property_boolean_get(op->ptr, prop) : false;
954 prop = RNA_struct_find_property(op->ptr, "use_clamp");
955 use_clamp = (prop) ? RNA_property_boolean_get(op->ptr, prop) : true;
956 }
957 initEdgeSlide_ex(t, op, use_double_side, use_even, flipped, use_clamp);
958}
959
961
962/* -------------------------------------------------------------------- */
965
967{
969 EdgeSlideData *sld = static_cast<EdgeSlideData *>(tc->custom.mode.data);
970 if (sld) {
971 sld->update_proj_mat(t, tc);
972 TransDataEdgeSlideVert *curr_sv = &sld->sv[sld->curr_sv_index];
973
974 float2 sco_a, sco_b;
975 sld->project(curr_sv, sco_a, sco_b);
976 float2 mval_dir = sco_b - sco_a;
977 edge_slide_data_init_mval(&t->mouse, sld, mval_dir);
978 }
979 }
980
982 setCustomPoints(t, &t->mouse, sld->mval_end, sld->mval_start);
983}
984
986
988 /*flags*/ T_NO_CONSTRAINT,
989 /*init_fn*/ initEdgeSlide,
990 /*transform_fn*/ applyEdgeSlide,
991 /*transform_matrix_fn*/ edge_slide_transform_matrix_fn,
992 /*handle_event_fn*/ handleEventEdgeSlide,
993 /*snap_distance_fn*/ transform_snap_distance_len_squared_fn,
994 /*snap_apply_fn*/ edge_slide_snap_apply,
995 /*draw_fn*/ drawEdgeSlide,
996};
997
998} // 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:136
#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])
char size_t BLI_strncpy_utf8_rlen(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
size_t BLI_snprintf_utf8_rlen(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) 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:190
void outputNumInput(NumInput *n, char *str, const UnitSettings &unit_settings)
Definition numinput.cc:88
bool hasNumInput(const NumInput *n)
Definition numinput.cc:171
void ED_area_status_text(ScrArea *area, const char *str)
Definition area.cc:851
#define XRAY_ENABLED(v3d)
blender::float4x4 ED_view3d_ob_project_mat_get(const RegionView3D *rv3d, const Object *ob)
void immEnd()
void immUnbindProgram()
void immBindBuiltinProgram(GPUBuiltinShader shader_id)
void immUniformThemeColorShadeAlpha(int color_id, int color_offset, int alpha_offset)
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_DEPTH_LESS_EQUAL
Definition GPU_state.hh:114
@ GPU_DEPTH_NONE
Definition GPU_state.hh:111
void GPU_line_width(float width)
Definition gpu_state.cc:166
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(GPUBlend blend)
Definition gpu_state.cc:42
void GPU_depth_test(GPUDepthTest test)
Definition gpu_state.cc:68
void GPU_point_size(float size)
Definition gpu_state.cc:172
uint GPU_vertformat_attr_add(GPUVertFormat *format, blender::StringRef name, blender::gpu::VertAttrType type)
#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:1808
@ KM_PRESS
Definition WM_types.hh:311
@ 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
constexpr const T * data() const
Definition BLI_span.hh:215
constexpr bool is_empty() const
Definition BLI_span.hh:260
#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
const int status
#define fabsf
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:810
TransCustomDataContainer custom
Definition transform.hh:974
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
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