Transform: Implement Snap to Grid mode #116109

Merged
Germano Cavalcante merged 4 commits from mano-wii/blender:snap_to_grid into main 2024-03-27 13:17:37 +01:00
15 changed files with 256 additions and 265 deletions

View File

@ -7538,9 +7538,6 @@ class VIEW3D_PT_snapping(Panel):
col.separator()
if 'INCREMENT' in tool_settings.snap_elements:
col.prop(tool_settings, "use_snap_grid_absolute")
if 'VOLUME' in tool_settings.snap_elements:
col.prop(tool_settings, "use_snap_peel_object")

View File

@ -130,38 +130,6 @@ static void v3d_cursor_poject_surface_normal(const float normal[3],
copy_v3_v3(r_mat[2], mat[i_best]);
}
/**
* Calculate 3D view incremental (grid) snapping.
*
* \note This could be moved to a public function.
*/
static bool v3d_cursor_snap_calc_incremental(
Scene *scene, View3D *v3d, ARegion *region, const float co_relative[3], float co[3])
{
const float grid_size = ED_view3d_grid_view_scale(scene, v3d, region, nullptr);
if (UNLIKELY(grid_size == 0.0f)) {
return false;
}
if (scene->toolsettings->snap_flag & SCE_SNAP_ABS_GRID) {
co_relative = nullptr;
}
if (co_relative != nullptr) {
sub_v3_v3(co, co_relative);
}
mul_v3_fl(co, 1.0f / grid_size);
co[0] = roundf(co[0]);
co[1] = roundf(co[1]);
co[2] = roundf(co[2]);
mul_v3_fl(co, grid_size);
if (co_relative != nullptr) {
add_v3_v3(co, co_relative);
}
return true;
}
/**
* Re-order \a mat so \a axis_align uses its own axis which is closest to \a v.
*/
@ -377,6 +345,11 @@ static void cursor_box_draw(const float dimensions[3], uchar color[4])
static void cursor_point_draw(
uint attr_pos, const float loc[3], const float size, eSnapMode snap_type, const uchar color[4])
{
if (snap_type == SCE_SNAP_TO_GRID) {
/* No drawing. */
return;
}
immUniformColor4ubv(color);
GPU_matrix_push();
@ -686,7 +659,7 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state,
snap_elements |= SCE_SNAP_TO_FACE;
}
if (snap_elements & SCE_SNAP_TO_GEOM) {
if (snap_elements & (SCE_SNAP_TO_GEOM | SCE_SNAP_TO_GRID)) {
float prev_co[3] = {0.0f};
if (state->prevpoint) {
copy_v3_v3(prev_co, state->prevpoint);
@ -810,10 +783,6 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state,
if (!do_plane_isect) {
ED_view3d_win_to_3d(v3d, region, co_depth, mval_fl, co);
}
if (snap_data->is_enabled && (snap_elements & SCE_SNAP_TO_INCREMENT)) {
v3d_cursor_snap_calc_incremental(scene, v3d, region, state->prevpoint, co);
}
}
else if (snap_elem & SCE_SNAP_TO_VERTEX) {
snap_elem_index[0] = index;

View File

@ -920,7 +920,7 @@ float ED_view3d_grid_view_scale(const Scene *scene,
const char **r_grid_unit)
{
float grid_scale;
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
const RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
if (!rv3d->is_persp && RV3D_VIEW_IS_AXIS(rv3d->view)) {
/* Decrease the distance between grid snap points depending on zoom. */
float dist = 12.0f / (region->sizex * rv3d->winmat[0][0]);

View File

@ -155,7 +155,9 @@ struct InteractivePlaceData {
/** When activated without a tool. */
bool wait_for_input;
eSnapMode snap_to;
/* WORKAROUND: We need to remove #SCE_SNAP_TO_GRID temporarily. */
short *snap_to_ptr;
eSnapMode snap_to_restore;
};
/** \} */
@ -225,7 +227,7 @@ static bool idp_snap_calc_incremental(
return false;
}
if (scene->toolsettings->snap_flag & SCE_SNAP_ABS_GRID) {
if (scene->toolsettings->snap_mode & SCE_SNAP_TO_GRID) {
co_relative = nullptr;
}
@ -761,10 +763,11 @@ static void view3d_interactive_add_begin(bContext *C, wmOperator *op, const wmEv
ipd->step_index = STEP_BASE;
ipd->snap_to = eSnapMode(tool_settings->snap_mode_tools);
if (ipd->snap_to == SCE_SNAP_TO_NONE) {
ipd->snap_to = eSnapMode(tool_settings->snap_mode);
ipd->snap_to_ptr = &tool_settings->snap_mode_tools;
if (eSnapMode(*ipd->snap_to_ptr) == SCE_SNAP_TO_NONE) {
ipd->snap_to_ptr = &tool_settings->snap_mode;
}
ipd->snap_to_restore = eSnapMode(*ipd->snap_to_ptr);
plane_from_point_normal_v3(ipd->step[0].plane, ipd->co_src, ipd->matrix_orient[plane_axis]);
@ -1035,6 +1038,10 @@ static int view3d_interactive_add_modal(bContext *C, wmOperator *op, const wmEve
if (ELEM(event->type, ipd->launch_event, LEFTMOUSE)) {
if (event->val == KM_RELEASE) {
ED_view3d_cursor_snap_state_prevpoint_set(ipd->snap_state, ipd->co_src);
if (ipd->snap_to_restore & SCE_SNAP_TO_GRID) {
/* Don't snap to grid in #STEP_DEPTH. */
*ipd->snap_to_ptr = ipd->snap_to_restore & ~SCE_SNAP_TO_GRID;
}
/* Set secondary plane. */
@ -1072,7 +1079,10 @@ static int view3d_interactive_add_modal(bContext *C, wmOperator *op, const wmEve
else if (ipd->step_index == STEP_DEPTH) {
if (ELEM(event->type, ipd->launch_event, LEFTMOUSE)) {
if (event->val == KM_PRESS) {
/* Restore snap mode. */
*ipd->snap_to_ptr = ipd->snap_to_restore;
/* Confirm. */
BoundBox bounds;
calc_bbox(ipd, &bounds);
@ -1205,7 +1215,7 @@ static int view3d_interactive_add_modal(bContext *C, wmOperator *op, const wmEve
/* pass */
}
if (ipd->use_snap && (ipd->snap_to & SCE_SNAP_TO_INCREMENT)) {
if (ipd->use_snap && (ipd->snap_to_restore & (SCE_SNAP_TO_GRID | SCE_SNAP_TO_INCREMENT))) {
if (idp_snap_calc_incremental(
ipd->scene, ipd->v3d, ipd->region, ipd->co_src, ipd->step[STEP_BASE].co_dst))
{
@ -1229,7 +1239,7 @@ static int view3d_interactive_add_modal(bContext *C, wmOperator *op, const wmEve
/* pass */
}
if (ipd->use_snap && (ipd->snap_to & SCE_SNAP_TO_INCREMENT)) {
if (ipd->use_snap && (ipd->snap_to_restore & (SCE_SNAP_TO_GRID | SCE_SNAP_TO_INCREMENT))) {
if (idp_snap_calc_incremental(
ipd->scene, ipd->v3d, ipd->region, ipd->co_src, ipd->step[STEP_DEPTH].co_dst))
{

View File

@ -1884,11 +1884,7 @@ static void initSnapSpatial(TransInfo *t, float r_snap[3], float *r_snap_precisi
*r_snap_precision = 0.1f;
if (t->spacetype == SPACE_VIEW3D) {
if (t->region->regiondata) {
View3D *v3d = static_cast<View3D *>(t->area->spacedata.first);
r_snap[0] = r_snap[1] = r_snap[2] = ED_view3d_grid_view_scale(
t->scene, v3d, t->region, nullptr);
}
/* Pass. Done in #ED_transform_snap_object_project_view3d_ex. */
}
else if (t->spacetype == SPACE_IMAGE) {
SpaceImage *sima = static_cast<SpaceImage *>(t->area->spacedata.first);

View File

@ -174,9 +174,8 @@ enum eTSnap {
SNAP_RESETTED = 0,
SNAP_SOURCE_FOUND = 1 << 0,
/* Special flag for snap to grid. */
SNAP_TARGET_GRID_FOUND = 1 << 1,
SNAP_TARGET_FOUND = 1 << 2,
SNAP_MULTI_POINTS = 1 << 3,
SNAP_TARGET_FOUND = 1 << 1,
SNAP_MULTI_POINTS = 1 << 2,
};
ENUM_OPERATORS(eTSnap, SNAP_MULTI_POINTS)
@ -316,7 +315,6 @@ struct TransSnap {
float snap_source[3];
/** To this point (in global-space). */
float snap_target[3];
float snap_target_grid[3];
float snapNormal[3];
char snapNodeBorder;
ListBase points;

View File

@ -404,9 +404,6 @@ static void applyAxisConstraintVec(const TransInfo *t,
is_snap_to_face = (t->tsnap.target_type & SCE_SNAP_TO_FACE) != 0;
is_snap_to_point = !is_snap_to_edge && !is_snap_to_face;
}
else if (t->tsnap.target_type & SCE_SNAP_TO_GRID) {
is_snap_to_point = true;
}
}
/* Fallback for when axes are aligned. */

View File

@ -205,9 +205,9 @@ void transform_mode_snap_source_init(TransInfo *t, wmOperator * /*op*/)
t->tsnap.mode &= ~(SCE_SNAP_TO_EDGE_PERPENDICULAR | SCE_SNAP_INDIVIDUAL_PROJECT |
SCE_SNAP_INDIVIDUAL_NEAREST);
if ((t->tsnap.mode & ~(SCE_SNAP_TO_INCREMENT | SCE_SNAP_TO_GRID)) == 0) {
if ((t->tsnap.mode & ~SCE_SNAP_TO_INCREMENT) == 0) {
/* Initialize snap modes for geometry. */
t->tsnap.mode &= ~(SCE_SNAP_TO_INCREMENT | SCE_SNAP_TO_GRID);
t->tsnap.mode &= ~SCE_SNAP_TO_INCREMENT;
t->tsnap.mode |= SCE_SNAP_TO_GEOM & ~SCE_SNAP_TO_EDGE_PERPENDICULAR;
if (!(customdata->snap_mode_confirm & SCE_SNAP_TO_EDGE_PERPENDICULAR)) {

View File

@ -343,99 +343,6 @@ static void headerTranslation(TransInfo *t, const float vec[3], char str[UI_MAX_
/** \name Transform (Translation) Snapping
* \{ */
static void translate_snap_target_grid_ensure(TransInfo *t)
{
/* Only need to calculate once. */
if ((t->tsnap.status & SNAP_TARGET_GRID_FOUND) == 0) {
if (t->data_type == &TransConvertType_Cursor3D) {
/* Use a fallback when transforming the cursor.
* In this case the center is _not_ derived from the cursor which is being transformed. */
copy_v3_v3(t->tsnap.snap_target_grid, TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->data->iloc);
}
else if (t->around == V3D_AROUND_CURSOR) {
/* Use a fallback for cursor selection,
* this isn't useful as a global center for absolute grid snapping
* since its not based on the position of the selection. */
tranform_snap_target_median_calc(t, t->tsnap.snap_target_grid);
}
else {
copy_v3_v3(t->tsnap.snap_target_grid, t->center_global);
}
t->tsnap.status |= SNAP_TARGET_GRID_FOUND;
}
}
static void translate_snap_grid_apply(TransInfo *t,
const int max_index,
const float grid_dist[3],
const float loc[3],
float r_out[3])
{
BLI_assert(max_index <= 2);
translate_snap_target_grid_ensure(t);
const float *center_global = t->tsnap.snap_target_grid;
const float *asp = t->aspect;
float in[3];
if (t->con.mode & CON_APPLY) {
/* We need to clear the previous Snap to Grid result,
* otherwise #t->con.applyVec will have no effect. */
t->tsnap.target_type = SCE_SNAP_TO_NONE;
t->con.applyVec(t, nullptr, nullptr, loc, in);
}
else {
copy_v3_v3(in, loc);
}
for (int i = 0; i <= max_index; i++) {
const float iter_fac = grid_dist[i] * asp[i];
r_out[i] = iter_fac * roundf((in[i] + center_global[i]) / iter_fac) - center_global[i];
}
if ((t->con.mode & CON_APPLY) &&
(t->spacemtx[0][0] != 1.0f || t->spacemtx[1][1] != 1.0f || t->spacemtx[2][2] != 1.0f))
{
/* The space matrix is not identity, we need to constrain the result again. */
t->con.applyVec(t, nullptr, nullptr, r_out, r_out);
}
}
static bool translate_snap_grid(TransInfo *t, float *val)
{
if (!transform_snap_is_active(t)) {
return false;
}
if (!(t->tsnap.mode & SCE_SNAP_TO_GRID) || validSnap(t)) {
/* Don't do grid snapping if there is a valid snap point. */
return false;
}
/* Don't do grid snapping if not in 3D viewport or UV editor. */
if (!ELEM(t->spacetype, SPACE_VIEW3D, SPACE_IMAGE)) {
return false;
}
if (t->mode != TFM_TRANSLATION) {
return false;
}
float grid_dist[3];
copy_v3_v3(grid_dist, t->snap_spatial);
if (t->modifiers & MOD_PRECISION) {
mul_v3_fl(grid_dist, t->snap_spatial_precision);
}
/* Early bailing out if no need to snap. */
if (is_zero_v3(grid_dist)) {
return false;
}
translate_snap_grid_apply(t, t->idx_max, grid_dist, val, val);
t->tsnap.target_type = SCE_SNAP_TO_GRID;
return true;
}
static void ApplySnapTranslation(TransInfo *t, float vec[3])
{
float point[3];
@ -615,7 +522,6 @@ static void applyTranslation(TransInfo *t)
}
transform_snap_mixed_apply(t, global_dir);
translate_snap_grid(t, global_dir);
if (t->con.mode & CON_APPLY) {
float in[3];

View File

@ -68,6 +68,13 @@ static void snap_source_center_fn(TransInfo *t);
static void snap_source_closest_fn(TransInfo *t);
static void snap_source_active_fn(TransInfo *t);
static eSnapMode snapObjectsTransform(TransInfo *t,
const float mval[2],
const float *vec,
float *dist_px,
float r_loc[3],
float r_no[3]);
/** \} */
/* -------------------------------------------------------------------- */
@ -552,7 +559,7 @@ static bool transform_snap_mixed_is_active(const TransInfo *t)
return (t->tsnap.mode &
(SCE_SNAP_TO_VERTEX | SCE_SNAP_TO_EDGE | SCE_SNAP_TO_FACE | SCE_SNAP_TO_VOLUME |
SCE_SNAP_TO_EDGE_MIDPOINT | SCE_SNAP_TO_EDGE_PERPENDICULAR)) != 0;
SCE_SNAP_TO_EDGE_MIDPOINT | SCE_SNAP_TO_EDGE_PERPENDICULAR | SCE_SNAP_TO_GRID)) != 0;
}
void transform_snap_mixed_apply(TransInfo *t, float *vec)
@ -561,7 +568,7 @@ void transform_snap_mixed_apply(TransInfo *t, float *vec)
return;
}
if (t->tsnap.mode & ~(SCE_SNAP_TO_INCREMENT | SCE_SNAP_TO_GRID)) {
if (t->tsnap.mode != SCE_SNAP_TO_INCREMENT) {
double current = BLI_time_now_seconds();
/* Time base quirky code to go around find-nearest slowness. */
@ -675,14 +682,7 @@ static eSnapMode snap_mode_from_spacetype(TransInfo *t)
}
if (t->spacetype == SPACE_IMAGE) {
eSnapMode snap_mode = eSnapMode(ts->snap_uv_mode);
if ((snap_mode & SCE_SNAP_TO_INCREMENT) && (ts->snap_uv_flag & SCE_SNAP_ABS_GRID) &&
(t->mode == TFM_TRANSLATION))
{
snap_mode &= ~SCE_SNAP_TO_INCREMENT;
snap_mode |= SCE_SNAP_TO_GRID;
}
return snap_mode;
return eSnapMode(ts->snap_uv_mode);
}
if (t->spacetype == SPACE_SEQ) {
@ -694,15 +694,7 @@ static eSnapMode snap_mode_from_spacetype(TransInfo *t)
return SCE_SNAP_TO_INCREMENT;
}
eSnapMode snap_mode = eSnapMode(ts->snap_mode);
if ((snap_mode & SCE_SNAP_TO_INCREMENT) && (ts->snap_flag & SCE_SNAP_ABS_GRID) &&
(t->mode == TFM_TRANSLATION))
{
/* Special case in which snap to increments is transformed to snap to grid. */
snap_mode &= ~SCE_SNAP_TO_INCREMENT;
snap_mode |= SCE_SNAP_TO_GRID;
}
return snap_mode;
return eSnapMode(ts->snap_mode);
}
if (ELEM(t->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_GRAPH)) {
@ -1146,7 +1138,58 @@ static void snap_multipoints_free(TransInfo *t)
/** \name Calc Snap
* \{ */
static void snap_target_view3d_fn(TransInfo *t, float * /*vec*/)
static void snap_grid_uv_apply(TransInfo *t,
const float grid_dist[2],
const float vec[2],
float r_out[2])
{
const float *center_global = t->center_global;
float in[2];
if (t->con.mode & CON_APPLY) {
/* We need to clear the previous Snap to Grid result,
* otherwise #t->con.applyVec will have no effect. */
t->tsnap.target_type = SCE_SNAP_TO_NONE;
t->con.applyVec(t, nullptr, nullptr, vec, in);
}
else {
copy_v2_v2(in, vec);
}
for (int i = 0; i < 2; i++) {
const float iter_fac = grid_dist[i];
r_out[i] = iter_fac * roundf((in[i] + center_global[i]) / iter_fac);
}
}
static bool snap_grid_uv(TransInfo *t, float vec[2], float r_val[2])
{
if (t->mode != TFM_TRANSLATION) {
return false;
}
float grid_dist[2];
mul_v2_v2v2(grid_dist, t->snap_spatial, t->aspect);
if (t->modifiers & MOD_PRECISION) {
mul_v2_fl(grid_dist, t->snap_spatial_precision);
}
/* Early bailing out if no need to snap */
if (is_zero_v2(grid_dist)) {
return false;
}
snap_grid_uv_apply(t, grid_dist, vec, r_val);
t->tsnap.target_type = SCE_SNAP_TO_GRID;
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Calc Snap
* \{ */
static void snap_target_view3d_fn(TransInfo *t, float *vec)
{
BLI_assert(t->spacetype == SPACE_VIEW3D);
float loc[3];
@ -1155,9 +1198,9 @@ static void snap_target_view3d_fn(TransInfo *t, float * /*vec*/)
eSnapMode snap_elem = SCE_SNAP_TO_NONE;
float dist_px = SNAP_MIN_DISTANCE; /* Use a user defined value here. */
if (t->tsnap.mode & SCE_SNAP_TO_GEOM) {
zero_v3(no); /* Objects won't set this. */
snap_elem = snapObjectsTransform(t, t->mval, &dist_px, loc, no);
if (t->tsnap.mode & (SCE_SNAP_TO_GEOM | SCE_SNAP_TO_GRID)) {
zero_v3(no); /* objects won't set this */
snap_elem = snapObjectsTransform(t, t->mval, vec, &dist_px, loc, no);
found = (snap_elem != SCE_SNAP_TO_NONE);
}
if ((found == false) && (t->tsnap.mode & SCE_SNAP_TO_VOLUME)) {
@ -1174,6 +1217,11 @@ static void snap_target_view3d_fn(TransInfo *t, float * /*vec*/)
copy_v3_v3(t->tsnap.snapNormal, no);
t->tsnap.status |= SNAP_TARGET_FOUND;
if (snap_elem == SCE_SNAP_TO_GRID && t->mode_info != &TransMode_translate) {
/* Change it to #SCE_SNAP_TO_POINT so we can see the symbol for other modes. */
snap_elem = SCE_SNAP_TO_POINT;
}
}
else {
t->tsnap.status &= ~SNAP_TARGET_FOUND;
@ -1182,10 +1230,10 @@ static void snap_target_view3d_fn(TransInfo *t, float * /*vec*/)
t->tsnap.target_type = snap_elem;
}
static void snap_target_uv_fn(TransInfo *t, float * /*vec*/)
static void snap_target_uv_fn(TransInfo *t, float *vec)
{
BLI_assert(t->spacetype == SPACE_IMAGE);
if (t->tsnap.mode & SCE_SNAP_TO_VERTEX) {
if (t->tsnap.mode & (SCE_SNAP_TO_VERTEX | SCE_SNAP_TO_GRID)) {
const Vector<Object *> objects =
BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
t->scene, t->view_layer, nullptr);
@ -1204,6 +1252,9 @@ static void snap_target_uv_fn(TransInfo *t, float * /*vec*/)
t->tsnap.status |= SNAP_TARGET_FOUND;
}
else if ((t->tsnap.mode & SCE_SNAP_TO_GRID) && snap_grid_uv(t, vec, t->tsnap.snap_target)) {
t->tsnap.status |= SNAP_TARGET_FOUND;
}
else {
t->tsnap.status &= ~SNAP_TARGET_FOUND;
}
@ -1358,7 +1409,17 @@ static void snap_source_median_fn(TransInfo *t)
static void snap_source_closest_fn(TransInfo *t)
{
/* Only valid if a snap point has been selected. */
if (t->tsnap.status & SNAP_TARGET_FOUND) {
if (!(t->tsnap.status & SNAP_TARGET_FOUND)) {
return;
}
if (t->tsnap.target_type == SCE_SNAP_TO_GRID) {
/* Previously Snap to Grid had its own snap source which was always the result of
* #snap_source_median_fn. Now this mode shares the same code, so to not change the behavior
* too much when using Closest, use the transform pivot as the snap source in this case. */
copy_v3_v3(t->tsnap.snap_source, t->center_global);
}
else {
float dist_closest = 0.0f;
TransData *closest = nullptr;
@ -1445,10 +1506,10 @@ static void snap_source_closest_fn(TransInfo *t)
}
TargetSnapOffset(t, closest);
t->tsnap.status |= SNAP_SOURCE_FOUND;
t->tsnap.source_type = SCE_SNAP_TO_NONE;
}
t->tsnap.status |= SNAP_SOURCE_FOUND;
t->tsnap.source_type = SCE_SNAP_TO_NONE;
}
/** \} */
@ -1457,8 +1518,12 @@ static void snap_source_closest_fn(TransInfo *t)
/** \name Snap Objects
* \{ */
eSnapMode snapObjectsTransform(
TransInfo *t, const float mval[2], float *dist_px, float r_loc[3], float r_no[3])
static eSnapMode snapObjectsTransform(TransInfo *t,
const float mval[2],
const float *vec,
float *dist_px,
float r_loc[3],
float r_no[3])
{
SnapObjectParams snap_object_params{};
snap_object_params.snap_target_select = t->tsnap.target_operation;
@ -1467,6 +1532,16 @@ eSnapMode snapObjectsTransform(
snap_object_params.use_backface_culling = (t->tsnap.flag & SCE_SNAP_BACKFACE_CULLING) != 0;
float *prev_co = (t->tsnap.status & SNAP_SOURCE_FOUND) ? t->tsnap.snap_source : t->center_global;
float *grid_co = nullptr, grid_co_stack[3];
if ((t->tsnap.mode & SCE_SNAP_TO_GRID) && (t->con.mode & CON_APPLY) &&
(t->mode == TFM_TRANSLATION))
{
/* Without this position adjustment, the snap may be far from the expected constraint point. */
grid_co = grid_co_stack;
t->tsnap.status &= ~SNAP_TARGET_FOUND;
t->con.applyVec(t, nullptr, nullptr, vec, grid_co);
add_v3_v3(grid_co, t->center_global);
}
return ED_transform_snap_object_project_view3d(t->tsnap.object_context,
t->depsgraph,
@ -1474,7 +1549,7 @@ eSnapMode snapObjectsTransform(
static_cast<const View3D *>(t->view),
t->tsnap.mode,
&snap_object_params,
nullptr,
grid_co,
mval,
prev_co,
dist_px,

View File

@ -22,12 +22,6 @@ bool peelObjectsTransform(TransInfo *t,
float r_no[3],
float *r_thickness);
eSnapMode snapObjectsTransform(TransInfo *t,
const float mval[2],
float *dist_px,
/* Return args. */
float r_loc[3],
float r_no[3]);
bool snapNodesTransform(TransInfo *t,
const blender::float2 &mval,
/* Return args. */

View File

@ -139,7 +139,7 @@ void SnapData::clip_planes_enable(SnapObjectContext *sctx,
{
float4x4 tobmat = math::transpose(this->obmat_);
if (!skip_occlusion_plane) {
const bool is_in_front = sctx->runtime.params.use_occlusion_test &&
const bool is_in_front = sctx->runtime.params.use_occlusion_test && ob_eval &&
(ob_eval->dtx & OB_DRAW_IN_FRONT) != 0;
if (!is_in_front && sctx->runtime.has_occlusion_plane) {
this->clip_planes.append(tobmat * sctx->runtime.occlusion_plane);
@ -958,6 +958,54 @@ static eSnapMode snapObjectsRay(SnapObjectContext *sctx)
return iter_snap_objects(sctx, snap_obj_fn);
}
static bool snap_grid(SnapObjectContext *sctx)
{
SnapData nearest2d(sctx);
nearest2d.clip_planes_enable(sctx, nullptr);
/* Ignore the maximum pixel distance when snapping to grid.
* This avoids undesirable jumps of the element being snapped. */
nearest2d.nearest_point.dist_sq = FLT_MAX;
float grid_dist = sctx->grid.size;
if (sctx->grid.use_init_co) {
float3 co = math::round((sctx->runtime.init_co) / grid_dist) * grid_dist;
if (nearest2d.snap_point(co)) {
nearest2d.register_result(sctx, nullptr, nullptr);
return true;
}
}
float ray_dist;
for (int i = 0; i < 4; i++) {
if (isect_ray_plane_v3(sctx->runtime.ray_start,
sctx->runtime.ray_dir,
sctx->grid.planes[i],
&ray_dist,
false) &&
IN_RANGE_INCL(ray_dist, 0.0f, sctx->ret.ray_depth_max))
{
float3 co = math::round((sctx->runtime.ray_start + sctx->runtime.ray_dir * ray_dist) /
grid_dist) *
grid_dist;
if (nearest2d.snap_point(co)) {
copy_v3_v3(nearest2d.nearest_point.no, sctx->grid.planes[i]);
if (!sctx->runtime.rv3d->is_persp && RV3D_VIEW_IS_AXIS(sctx->runtime.rv3d->view)) {
/* Project in #sctx->runtime.curr_co plane. */
add_v3_v3(nearest2d.nearest_point.co,
sctx->runtime.curr_co * float3(nearest2d.nearest_point.no));
}
nearest2d.register_result(sctx, nullptr, nullptr);
return true;
}
}
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
@ -1024,7 +1072,9 @@ static bool snap_object_context_runtime_init(SnapObjectContext *sctx,
ListBase *hit_list,
bool use_occlusion_test)
{
if (snap_to_flag & (SCE_SNAP_TO_EDGE_PERPENDICULAR | SCE_SNAP_INDIVIDUAL_NEAREST)) {
if (snap_to_flag &
(SCE_SNAP_TO_GRID | SCE_SNAP_TO_EDGE_PERPENDICULAR | SCE_SNAP_INDIVIDUAL_NEAREST))
{
if (prev_co) {
copy_v3_v3(sctx->runtime.curr_co, prev_co);
if (init_co) {
@ -1082,6 +1132,30 @@ static bool snap_object_context_runtime_init(SnapObjectContext *sctx,
}
sctx->runtime.rv3d = rv3d;
if (sctx->runtime.snap_to_flag & SCE_SNAP_TO_GRID) {
if (init_co) {
sctx->grid.use_init_co = true;
}
else if (!compare_m4m4(sctx->grid.persmat.ptr(), rv3d->persmat, FLT_EPSILON)) {
sctx->grid.persmat = float4x4(rv3d->persmat);
memset(sctx->grid.planes, 0, sizeof(sctx->grid.planes));
sctx->grid.planes[0][2] = 1.0f;
if (math::abs(sctx->runtime.ray_dir[0]) < math::abs(sctx->runtime.ray_dir[1])) {
sctx->grid.planes[1][1] = 1.0f;
sctx->grid.planes[2][0] = 1.0f;
}
else {
sctx->grid.planes[1][0] = 1.0f;
sctx->grid.planes[2][1] = 1.0f;
}
plane_from_point_normal_v3(sctx->grid.planes[3], sctx->runtime.curr_co, rv3d->viewinv[2]);
sctx->grid.size = ED_view3d_grid_view_scale(
sctx->scene, sctx->runtime.v3d, region, nullptr);
}
}
}
sctx->ret.ray_depth_max = sctx->ret.ray_depth_max_in_front = ray_depth;
@ -1252,7 +1326,12 @@ eSnapMode ED_transform_snap_object_project_view3d_ex(SnapObjectContext *sctx,
use_occlusion_plane = is_allways_occluded || !XRAY_ENABLED(v3d);
}
if (use_occlusion_plane || (snap_to_flag & SCE_SNAP_TO_FACE)) {
if (use_occlusion_plane || (snap_to_flag & (SCE_SNAP_TO_FACE | SCE_SNAP_TO_GRID))) {
/* Calculate the direction (`ray_dir`) and starting point (`ray_start`) of the ray from the
* viewport to a 3D point under the mouse cursor (`mval`), taking into account potential view
* clipping.
* This is required for raycast or snap to grid. */
const RegionView3D *rv3d = static_cast<const RegionView3D *>(region->regiondata);
float3 ray_end;
ED_view3d_win_to_ray_clipped_ex(depsgraph,
@ -1303,7 +1382,7 @@ eSnapMode ED_transform_snap_object_project_view3d_ex(SnapObjectContext *sctx,
snap_to_flag = sctx->runtime.snap_to_flag;
BLI_assert(snap_to_flag & (SCE_SNAP_TO_GEOM | SCE_SNAP_INDIVIDUAL_NEAREST));
BLI_assert(snap_to_flag & (SCE_SNAP_TO_GEOM | SCE_SNAP_TO_GRID | SCE_SNAP_INDIVIDUAL_NEAREST));
bool has_hit = false;
@ -1313,21 +1392,7 @@ eSnapMode ED_transform_snap_object_project_view3d_ex(SnapObjectContext *sctx,
has_hit = nearestWorldObjects(sctx);
if (has_hit) {
retval = SCE_SNAP_INDIVIDUAL_NEAREST;
copy_v3_v3(r_loc, sctx->ret.loc);
if (r_no) {
copy_v3_v3(r_no, sctx->ret.no);
}
if (r_ob) {
*r_ob = sctx->ret.ob;
}
if (r_obmat) {
copy_m4_m4(r_obmat, sctx->ret.obmat.ptr());
}
if (r_index) {
*r_index = sctx->ret.index;
}
retval |= SCE_SNAP_INDIVIDUAL_NEAREST;
}
}
@ -1340,21 +1405,7 @@ eSnapMode ED_transform_snap_object_project_view3d_ex(SnapObjectContext *sctx,
}
if (snap_to_flag & SCE_SNAP_TO_FACE) {
retval = SCE_SNAP_TO_FACE;
copy_v3_v3(r_loc, sctx->ret.loc);
if (r_no) {
copy_v3_v3(r_no, sctx->ret.no);
}
if (r_ob) {
*r_ob = sctx->ret.ob;
}
if (r_obmat) {
copy_m4_m4(r_obmat, sctx->ret.obmat.ptr());
}
if (r_index) {
*r_index = sctx->ret.index;
}
retval |= SCE_SNAP_TO_FACE;
}
}
}
@ -1393,26 +1444,32 @@ eSnapMode ED_transform_snap_object_project_view3d_ex(SnapObjectContext *sctx,
elem = snap_edge_points(sctx, square_f(*dist_px));
}
if (elem & snap_to_flag) {
retval = elem;
retval |= elem & snap_to_flag;
}
copy_v3_v3(r_loc, sctx->ret.loc);
if (r_no) {
copy_v3_v3(r_no, sctx->ret.no);
}
if (r_ob) {
*r_ob = sctx->ret.ob;
}
if (r_obmat) {
copy_m4_m4(r_obmat, sctx->ret.obmat.ptr());
}
if (r_index) {
*r_index = sctx->ret.index;
}
if ((retval == SCE_SNAP_TO_NONE) && (snap_to_flag & SCE_SNAP_TO_GRID)) {
if (snap_grid(sctx)) {
retval = SCE_SNAP_TO_GRID;
}
}
if (dist_px) {
*dist_px = math::sqrt(sctx->ret.dist_px_sq);
}
if (retval != SCE_SNAP_TO_NONE) {
copy_v3_v3(r_loc, sctx->ret.loc);
if (r_no) {
copy_v3_v3(r_no, sctx->ret.no);
}
if (r_ob) {
*r_ob = sctx->ret.ob;
}
if (r_obmat) {
copy_m4_m4(r_obmat, sctx->ret.obmat.ptr());
}
if (r_index) {
*r_index = sctx->ret.index;
}
if (dist_px) {
*dist_px = math::sqrt(sctx->ret.dist_px_sq);
}
}

View File

@ -35,6 +35,14 @@ struct SnapObjectContext {
} edit_mesh;
} callbacks;
struct {
/* Compare with #RegionView3D::persmat to update. */
blender::float4x4 persmat;
blender::float4 planes[4];
float size;
bool use_init_co;
} grid;
struct {
Depsgraph *depsgraph;
const RegionView3D *rv3d;

View File

@ -2376,7 +2376,7 @@ typedef enum eSnapFlag {
// SCE_SNAP_PROJECT = (1 << 3), /* DEPRECATED, see #SCE_SNAP_INDIVIDUAL_PROJECT. */
/** Was `SCE_SNAP_NO_SELF`, but self should be active. */
SCE_SNAP_NOT_TO_ACTIVE = (1 << 4),
SCE_SNAP_ABS_GRID = (1 << 5),
/* SCE_SNAP_ABS_GRID = (1 << 5), */ /* UNUSED */
/* Same value with different name to make it easier to understand in time based code. */
SCE_SNAP_ABS_TIME_STEP = (1 << 5),
SCE_SNAP_BACKFACE_CULLING = (1 << 6),

View File

@ -157,6 +157,7 @@ const EnumPropertyItem rna_enum_mesh_select_mode_uv_items[] = {
/* clang-format off */
#define RNA_SNAP_ELEMENTS_BASE \
{SCE_SNAP_TO_INCREMENT, "INCREMENT", ICON_SNAP_INCREMENT, "Increment", "Snap to increments"}, \
{SCE_SNAP_TO_GRID, "GRID", ICON_SNAP_GRID, "Grid", "Snap to grid"}, \
{SCE_SNAP_TO_VERTEX, "VERTEX", ICON_SNAP_VERTEX, "Vertex", "Snap to vertices"}, \
{SCE_SNAP_TO_EDGE, "EDGE", ICON_SNAP_EDGE, "Edge", "Snap to edges"}, \
{SCE_SNAP_TO_FACE, "FACE", ICON_SNAP_FACE, "Face", "Snap by projecting onto faces"}, \
@ -217,6 +218,7 @@ static const EnumPropertyItem snap_uv_element_items[] = {
ICON_SNAP_INCREMENT,
"Increment",
"Snap to increments of grid"},
{SCE_SNAP_TO_GRID, "GRID", ICON_SNAP_GRID, "Grid", "Snap to grid"},
{SCE_SNAP_TO_VERTEX, "VERTEX", ICON_SNAP_VERTEX, "Vertex", "Snap to vertices"},
{0, nullptr, 0, nullptr, nullptr},
};
@ -3513,15 +3515,6 @@ static void rna_def_tool_settings(BlenderRNA *brna)
prop, "Align Rotation to Target", "Align rotation with the snapping target");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
prop = RNA_def_property(srna, "use_snap_grid_absolute", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "snap_flag", SCE_SNAP_ABS_GRID);
RNA_def_property_flag(prop, PROP_DEG_SYNC_ONLY);
RNA_def_property_ui_text(
prop,
"Absolute Grid Snap",
"Absolute grid alignment while translating (based on the pivot center)");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
prop = RNA_def_property(srna, "snap_angle_increment_2d", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_float_sdna(prop, nullptr, "snap_angle_increment_2d");
RNA_def_property_ui_text(
@ -3643,15 +3636,6 @@ static void rna_def_tool_settings(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Snap UV Element", "Type of element to snap to");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
prop = RNA_def_property(srna, "use_snap_uv_grid_absolute", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "snap_uv_flag", SCE_SNAP_ABS_GRID);
RNA_def_property_flag(prop, PROP_DEG_SYNC_ONLY);
RNA_def_property_ui_text(
prop,
"Absolute Grid Snap",
"Absolute grid alignment while translating (based on the pivot center)");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr); /* header redraw */
/* TODO(@gfxcoder): Rename `snap_target` to `snap_source` to avoid previous ambiguity of "target"
* (now, "source" is geometry to be moved and "target" is geometry to which moved geometry is
* snapped). */