/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** \file * \ingroup spview3d * * Operator to interactively place data. * * Currently only adds meshes, but could add other kinds of data * including library assets & non-mesh types. */ #include "MEM_guardedalloc.h" #include "DNA_collection_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_vfont_types.h" #include "BLI_math.h" #include "BLI_utildefines.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_main.h" #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" #include "WM_api.h" #include "WM_toolsystem.h" #include "WM_types.h" #include "ED_gizmo_library.h" #include "ED_gizmo_utils.h" #include "ED_screen.h" #include "ED_space_api.h" #include "ED_transform.h" #include "ED_transform_snap_object_context.h" #include "ED_view3d.h" #include "UI_resources.h" #include "GPU_batch.h" #include "GPU_immediate.h" #include "GPU_matrix.h" #include "GPU_state.h" #include "view3d_intern.h" static const char *view3d_gzgt_placement_id = "VIEW3D_GGT_placement"; static void preview_plane_cursor_setup(wmGizmoGroup *gzgroup); static void preview_plane_cursor_visible_set(wmGizmoGroup *gzgroup, bool do_draw); /** * Dot products below this will be considered view aligned. * In this case we can't usefully project the mouse cursor onto the plane, * so use a fall-back plane instead. */ static const float eps_view_align = 1e-2f; /* -------------------------------------------------------------------- */ /** \name Local Types * \{ */ enum ePlace_PrimType { PLACE_PRIMITIVE_TYPE_CUBE = 1, PLACE_PRIMITIVE_TYPE_CYLINDER = 2, PLACE_PRIMITIVE_TYPE_CONE = 3, PLACE_PRIMITIVE_TYPE_SPHERE_UV = 4, PLACE_PRIMITIVE_TYPE_SPHERE_ICO = 5, }; enum ePlace_Origin { PLACE_ORIGIN_BASE = 1, PLACE_ORIGIN_CENTER = 2, }; enum ePlace_Aspect { PLACE_ASPECT_FREE = 1, PLACE_ASPECT_FIXED = 2, }; enum ePlace_Depth { PLACE_DEPTH_SURFACE = 1, PLACE_DEPTH_CURSOR_PLANE = 2, PLACE_DEPTH_CURSOR_VIEW = 3, }; enum ePlace_Orient { PLACE_ORIENT_SURFACE = 1, PLACE_ORIENT_DEFAULT = 2, }; enum ePlace_SnapTo { PLACE_SNAP_TO_GEOMETRY = 1, PLACE_SNAP_TO_DEFAULT = 2, }; struct InteractivePlaceData { /* Window manager variables (set these even when waiting for input). */ Scene *scene; ScrArea *area; View3D *v3d; ARegion *region; /** Draw object preview region draw callback. */ void *draw_handle_view; float co_src[3]; /** Primary & secondary steps. */ struct { /** * When centered, drag out the shape from the center. * Toggling the setting flips the value from it's initial state. */ bool is_centered, is_centered_init; /** * When fixed, constrain the X/Y aspect for the initial #STEP_BASE drag. * For #STEP_DEPTH match the maximum X/Y dimension. * Toggling the setting flips the value from it's initial state. */ bool is_fixed_aspect, is_fixed_aspect_init; float plane[4]; float co_dst[3]; /** * We can't project the mouse cursor onto `plane`, * in this case #view3d_win_to_3d_on_plane_maybe_fallback is used. * * - For #STEP_BASE we're drawing from the side, where the X/Y axis can't be projected. * - For #STEP_DEPTH we're drawing from the top (2D), where the depth can't be projected. */ bool is_degenerate_view_align; /** * When view aligned, use a diagonal offset (cavalier projection) * to give user feedback about the depth being set. * * Currently this is only used for orthogonal views since perspective views * nearly always show some depth, even when view aligned. * * - Drag to the bottom-left to move away from the view. * - Drag to the top-right to move towards the view. */ float degenerate_diagonal[3]; /** * Corrected for display, so what's shown on-screen doesn't loop to be reversed * in relation to cursor-motion. */ float degenerate_diagonal_display[3]; /** * Index into `matrix_orient` which is degenerate. */ int degenerate_axis; } step[2]; /** When we can't project onto the real plane, use this in it's place. */ float view_plane[4]; float matrix_orient[3][3]; int orient_axis; bool use_snap, is_snap_found, is_snap_invert; float snap_co[3]; /** Can index into #InteractivePlaceData.step. */ enum { STEP_BASE = 0, STEP_DEPTH = 1, } step_index; enum ePlace_PrimType primitive_type; /** Activated from the tool-system. */ bool use_tool; /** Event used to start the operator. */ short launch_event; /** When activated without a tool. */ bool wait_for_input; /** Optional snap gizmo, needed for snapping. */ wmGizmo *snap_gizmo; enum ePlace_SnapTo snap_to; }; /** \} */ /* -------------------------------------------------------------------- */ /** \name Internal Utilities * \{ */ /** * Convenience wrapper to avoid duplicating arguments. */ static bool view3d_win_to_3d_on_plane_maybe_fallback(const ARegion *region, const float plane[4], const float mval[2], const float *plane_fallback, float r_out[3]) { RegionView3D *rv3d = region->regiondata; bool do_clip = rv3d->is_persp; if (plane_fallback != NULL) { return ED_view3d_win_to_3d_on_plane_with_fallback( region, plane, mval, do_clip, plane_fallback, r_out); } return ED_view3d_win_to_3d_on_plane(region, plane, mval, do_clip, r_out); } /** * Return the index of \a dirs with the largest dot product compared to \a dir_test. */ static int dot_v3_array_find_max_index(const float dirs[][3], const int dirs_len, const float dir_test[3], bool is_signed) { int index_found = -1; float dot_best = -1.0f; for (int i = 0; i < dirs_len; i++) { float dot_test = dot_v3v3(dirs[i], dir_test); if (is_signed == false) { dot_test = fabsf(dot_test); } if ((index_found == -1) || (dot_test > dot_best)) { dot_best = dot_test; index_found = i; } } return index_found; } /** * Re-order \a mat so \a axis_align uses its own axis which is closest to \a v. */ static bool mat3_align_axis_to_v3(float mat[3][3], const int axis_align, const float v[3]) { float dot_best = -1.0f; int axis_found = axis_align; for (int i = 0; i < 3; i++) { const float dot_test = fabsf(dot_v3v3(mat[i], v)); if (dot_test > dot_best) { dot_best = dot_test; axis_found = i; } } if (axis_align != axis_found) { float tmat[3][3]; copy_m3_m3(tmat, mat); const int offset = mod_i(axis_found - axis_align, 3); for (int i = 0; i < 3; i++) { copy_v3_v3(mat[i], tmat[(i + offset) % 3]); } return true; } return false; } /* On-screen snap distance. */ #define MVAL_MAX_PX_DIST 12.0f static bool idp_snap_point_from_gizmo_ex(wmGizmo *gz, const char *prop_id, float r_location[3]) { if (gz->state & WM_GIZMO_STATE_HIGHLIGHT) { PropertyRNA *prop_location = RNA_struct_find_property(gz->ptr, prop_id); RNA_property_float_get_array(gz->ptr, prop_location, r_location); return true; } return false; } static bool idp_snap_point_from_gizmo(wmGizmo *gz, float r_location[3]) { return idp_snap_point_from_gizmo_ex(gz, "location", r_location); } static bool idp_snap_normal_from_gizmo(wmGizmo *gz, float r_normal[3]) { return idp_snap_point_from_gizmo_ex(gz, "normal", r_normal); } /** * Calculate a 3x3 orientation matrix from the surface under the cursor. */ static bool idp_poject_surface_normal(SnapObjectContext *snap_context, struct Depsgraph *depsgraph, const float mval_fl[2], const float mat_fallback[3][3], const float normal_fallback[3], float r_mat[3][3]) { bool success = false; float normal[3] = {0.0f}; float co_dummy[3]; /* We could use the index to get the orientation from the face. */ Object *ob_snap; float obmat[4][4]; if (ED_transform_snap_object_project_view3d_ex(snap_context, depsgraph, SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, .edit_mode_type = SNAP_GEOM_EDIT, }, mval_fl, NULL, NULL, co_dummy, normal, NULL, &ob_snap, obmat)) { /* pass */ } else if (normal_fallback != NULL) { copy_m4_m3(obmat, mat_fallback); copy_v3_v3(normal, normal_fallback); } if (!is_zero_v3(normal)) { float mat[3][3]; copy_m3_m4(mat, obmat); normalize_m3(mat); float dot_best = fabsf(dot_v3v3(mat[0], normal)); int i_best = 0; for (int i = 1; i < 3; i++) { float dot_test = fabsf(dot_v3v3(mat[i], normal)); if (dot_test > dot_best) { i_best = i; dot_best = dot_test; } } if (dot_v3v3(mat[i_best], normal) < 0.0f) { negate_v3(mat[(i_best + 1) % 3]); negate_v3(mat[(i_best + 2) % 3]); } copy_v3_v3(mat[i_best], normal); orthogonalize_m3(mat, i_best); normalize_m3(mat); copy_v3_v3(r_mat[0], mat[(i_best + 1) % 3]); copy_v3_v3(r_mat[1], mat[(i_best + 2) % 3]); copy_v3_v3(r_mat[2], mat[i_best]); success = true; } return success; } static wmGizmoGroup *idp_gizmogroup_from_region(ARegion *region) { wmGizmoMap *gzmap = region->gizmo_map; return gzmap ? WM_gizmomap_group_find(gzmap, view3d_gzgt_placement_id) : NULL; } /** * Calculate 3D view incremental (grid) snapping. * * \note This could be moved to a public function. */ static bool idp_snap_calc_incremental( Scene *scene, View3D *v3d, ARegion *region, const float co_relative[3], float co[3]) { if ((scene->toolsettings->snap_mode & SCE_SNAP_MODE_INCREMENT) == 0) { return false; } const float grid_size = ED_view3d_grid_view_scale(scene, v3d, region, NULL); if (UNLIKELY(grid_size == 0.0f)) { return false; } if (scene->toolsettings->snap_flag & SCE_SNAP_ABS_GRID) { co_relative = NULL; } if (co_relative != NULL) { 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 != NULL) { add_v3_v3(co, co_relative); } return true; } static void idp_snap_gizmo_update_snap_elements(Scene *scene, enum ePlace_SnapTo snap_to, wmGizmo *gizmo) { const int snap_mode = (snap_to == PLACE_SNAP_TO_GEOMETRY) ? (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE | /* SCE_SNAP_MODE_VOLUME | SCE_SNAP_MODE_GRID | SCE_SNAP_MODE_INCREMENT | */ SCE_SNAP_MODE_EDGE_PERPENDICULAR | SCE_SNAP_MODE_EDGE_MIDPOINT) : scene->toolsettings->snap_mode; RNA_enum_set(gizmo->ptr, "snap_elements_force", snap_mode); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Primitive Drawing (Cube, Cone, Cylinder...) * \{ */ static void draw_line_loop(const float coords[][3], int coords_len, const float color[4]) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); GPUVertBuf *vert = GPU_vertbuf_create_with_format(format); GPU_vertbuf_data_alloc(vert, coords_len); for (int i = 0; i < coords_len; i++) { GPU_vertbuf_attr_set(vert, pos, i, coords[i]); } GPU_blend(GPU_BLEND_ALPHA); GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_LINE_LOOP, vert, NULL, GPU_BATCH_OWNS_VBO); GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); GPU_batch_uniform_4fv(batch, "color", color); float viewport[4]; GPU_viewport_size_get_f(viewport); GPU_batch_uniform_2fv(batch, "viewportSize", &viewport[2]); GPU_batch_uniform_1f(batch, "lineWidth", U.pixelsize); GPU_batch_draw(batch); GPU_batch_discard(batch); GPU_blend(GPU_BLEND_NONE); } static void draw_line_pairs(const float coords_a[][3], float coords_b[][3], int coords_len, const float color[4]) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); GPUVertBuf *vert = GPU_vertbuf_create_with_format(format); GPU_vertbuf_data_alloc(vert, coords_len * 2); for (int i = 0; i < coords_len; i++) { GPU_vertbuf_attr_set(vert, pos, i * 2, coords_a[i]); GPU_vertbuf_attr_set(vert, pos, (i * 2) + 1, coords_b[i]); } GPU_blend(GPU_BLEND_ALPHA); GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_LINES, vert, NULL, GPU_BATCH_OWNS_VBO); GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); GPU_batch_uniform_4fv(batch, "color", color); float viewport[4]; GPU_viewport_size_get_f(viewport); GPU_batch_uniform_2fv(batch, "viewportSize", &viewport[2]); GPU_batch_uniform_1f(batch, "lineWidth", U.pixelsize); GPU_batch_draw(batch); GPU_batch_discard(batch); GPU_blend(GPU_BLEND_NONE); } static void draw_line_bounds(const BoundBox *bounds, const float color[4]) { GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); const int edges[12][2] = { /* First side. */ {0, 1}, {1, 2}, {2, 3}, {3, 0}, /* Second side. */ {4, 5}, {5, 6}, {6, 7}, {7, 4}, /* Edges between. */ {0, 4}, {1, 5}, {2, 6}, {3, 7}, }; GPUVertBuf *vert = GPU_vertbuf_create_with_format(format); GPU_vertbuf_data_alloc(vert, ARRAY_SIZE(edges) * 2); for (int i = 0, j = 0; i < ARRAY_SIZE(edges); i++) { GPU_vertbuf_attr_set(vert, pos, j++, bounds->vec[edges[i][0]]); GPU_vertbuf_attr_set(vert, pos, j++, bounds->vec[edges[i][1]]); } GPU_blend(GPU_BLEND_ALPHA); GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_LINES, vert, NULL, GPU_BATCH_OWNS_VBO); GPU_batch_program_set_builtin(batch, GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); GPU_batch_uniform_4fv(batch, "color", color); float viewport[4]; GPU_viewport_size_get_f(viewport); GPU_batch_uniform_2fv(batch, "viewportSize", &viewport[2]); GPU_batch_uniform_1f(batch, "lineWidth", U.pixelsize); GPU_batch_draw(batch); GPU_batch_discard(batch); GPU_blend(GPU_BLEND_NONE); } static bool calc_bbox(struct InteractivePlaceData *ipd, BoundBox *bounds) { memset(bounds, 0x0, sizeof(*bounds)); if (compare_v3v3(ipd->co_src, ipd->step[0].co_dst, FLT_EPSILON)) { return false; } float matrix_orient_inv[3][3]; invert_m3_m3(matrix_orient_inv, ipd->matrix_orient); const int x_axis = (ipd->orient_axis + 1) % 3; const int y_axis = (ipd->orient_axis + 2) % 3; float quad_base[4][3]; float quad_secondary[4][3]; copy_v3_v3(quad_base[0], ipd->co_src); copy_v3_v3(quad_base[2], ipd->step[0].co_dst); /* Only set when we have a fixed aspect. */ float fixed_aspect_dimension; /* *** Primary *** */ { float delta_local[3]; float delta_a[3]; float delta_b[3]; sub_v3_v3v3(delta_local, ipd->step[0].co_dst, ipd->co_src); mul_m3_v3(matrix_orient_inv, delta_local); copy_v3_v3(delta_a, delta_local); copy_v3_v3(delta_b, delta_local); delta_a[ipd->orient_axis] = 0.0f; delta_b[ipd->orient_axis] = 0.0f; delta_a[x_axis] = 0.0f; delta_b[y_axis] = 0.0f; /* Assign here in case secondary. */ fixed_aspect_dimension = max_ff(fabsf(delta_a[y_axis]), fabsf(delta_b[x_axis])); if (ipd->step[0].is_fixed_aspect) { delta_a[y_axis] = copysignf(fixed_aspect_dimension, delta_a[y_axis]); delta_b[x_axis] = copysignf(fixed_aspect_dimension, delta_b[x_axis]); } mul_m3_v3(ipd->matrix_orient, delta_a); mul_m3_v3(ipd->matrix_orient, delta_b); if (ipd->step[0].is_fixed_aspect) { /* Recalculate the destination point. */ copy_v3_v3(quad_base[2], ipd->co_src); add_v3_v3(quad_base[2], delta_a); add_v3_v3(quad_base[2], delta_b); } add_v3_v3v3(quad_base[1], ipd->co_src, delta_a); add_v3_v3v3(quad_base[3], ipd->co_src, delta_b); } if (ipd->step[0].is_centered) { /* Use a copy in case aspect was applied to the quad. */ float base_co_dst[3]; copy_v3_v3(base_co_dst, quad_base[2]); for (int i = 0; i < ARRAY_SIZE(quad_base); i++) { sub_v3_v3(quad_base[i], base_co_dst); mul_v3_fl(quad_base[i], 2.0f); add_v3_v3(quad_base[i], base_co_dst); } fixed_aspect_dimension *= 2.0f; } /* *** Secondary *** */ float delta_local[3]; if (ipd->step_index == STEP_DEPTH) { sub_v3_v3v3(delta_local, ipd->step[1].co_dst, ipd->step[0].co_dst); } else { zero_v3(delta_local); } if (ipd->step[1].is_fixed_aspect) { if (!is_zero_v3(delta_local)) { normalize_v3_length(delta_local, fixed_aspect_dimension); } } if (ipd->step[1].is_centered) { float temp_delta[3]; if (ipd->step[1].is_fixed_aspect) { mul_v3_v3fl(temp_delta, delta_local, 0.5f); } else { copy_v3_v3(temp_delta, delta_local); mul_v3_fl(delta_local, 2.0f); } for (int i = 0; i < ARRAY_SIZE(quad_base); i++) { sub_v3_v3(quad_base[i], temp_delta); } } if ((ipd->step_index == STEP_DEPTH) && (compare_v3v3(ipd->step[0].co_dst, ipd->step[1].co_dst, FLT_EPSILON) == false)) { for (int i = 0; i < ARRAY_SIZE(quad_base); i++) { add_v3_v3v3(quad_secondary[i], quad_base[i], delta_local); } } else { copy_v3_v3(quad_secondary[0], quad_base[0]); copy_v3_v3(quad_secondary[1], quad_base[1]); copy_v3_v3(quad_secondary[2], quad_base[2]); copy_v3_v3(quad_secondary[3], quad_base[3]); } for (int i = 0; i < 4; i++) { copy_v3_v3(bounds->vec[i], quad_base[i]); copy_v3_v3(bounds->vec[i + 4], quad_secondary[i]); } return true; } static void draw_circle_in_quad(const float v1[3], const float v2[3], const float v3[3], const float v4[3], const int resolution, const float color[4]) { /* This isn't so efficient. */ const float quad[4][2] = { {-1, -1}, {+1, -1}, {+1, +1}, {-1, +1}, }; float(*coords)[3] = MEM_mallocN(sizeof(float[3]) * (resolution + 1), __func__); for (int i = 0; i <= resolution; i++) { float theta = ((2.0f * M_PI) * ((float)i / (float)resolution)) + 0.01f; float x = cosf(theta); float y = sinf(theta); const float pt[2] = {x, y}; float w[4]; barycentric_weights_v2_quad(UNPACK4(quad), pt, w); float *co = coords[i]; zero_v3(co); madd_v3_v3fl(co, v1, w[0]); madd_v3_v3fl(co, v2, w[1]); madd_v3_v3fl(co, v3, w[2]); madd_v3_v3fl(co, v4, w[3]); } draw_line_loop(coords, resolution + 1, color); MEM_freeN(coords); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Drawing Callbacks * \{ */ static void draw_primitive_view_impl(const struct bContext *C, struct InteractivePlaceData *ipd, const float color[4], int flatten_axis) { UNUSED_VARS(C); BoundBox bounds; calc_bbox(ipd, &bounds); /* Use cavalier projection, since it maps the scale usefully to the cursor. */ if (flatten_axis == STEP_BASE) { /* Calculate the plane that would be defined by the side of the cube vertices * if the plane had any volume. */ float no[3]; cross_v3_v3v3( no, ipd->matrix_orient[ipd->orient_axis], ipd->matrix_orient[(ipd->orient_axis + 1) % 3]); RegionView3D *rv3d = ipd->region->regiondata; copy_v3_v3(no, rv3d->viewinv[2]); normalize_v3(no); float base_plane[4]; plane_from_point_normal_v3(base_plane, bounds.vec[0], no); /* Offset all vertices even though we only need to offset the half of them. * This is harmless as `dist` will be zero for the `base_plane` aligned side of the cube. */ for (int i = 0; i < ARRAY_SIZE(bounds.vec); i++) { const float dist = dist_signed_to_plane_v3(bounds.vec[i], base_plane); madd_v3_v3fl(bounds.vec[i], base_plane, -dist); madd_v3_v3fl(bounds.vec[i], ipd->step[STEP_BASE].degenerate_diagonal_display, dist); } } if (flatten_axis == STEP_DEPTH) { const float *base_plane = ipd->step[0].plane; for (int i = 0; i < 4; i++) { const float dist = dist_signed_to_plane_v3(bounds.vec[i + 4], base_plane); madd_v3_v3fl(bounds.vec[i + 4], base_plane, -dist); madd_v3_v3fl(bounds.vec[i + 4], ipd->step[STEP_DEPTH].degenerate_diagonal_display, dist); } } draw_line_bounds(&bounds, color); if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CUBE) { /* pass */ } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CYLINDER) { draw_circle_in_quad(UNPACK4(bounds.vec), 32, color); draw_circle_in_quad(UNPACK4((&bounds.vec[4])), 32, color); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CONE) { draw_circle_in_quad(UNPACK4(bounds.vec), 32, color); float center[3]; mid_v3_v3v3v3v3(center, UNPACK4((&bounds.vec[4]))); float coords_a[4][3]; float coords_b[4][3]; for (int i = 0; i < 4; i++) { copy_v3_v3(coords_a[i], center); mid_v3_v3v3(coords_b[i], bounds.vec[i], bounds.vec[(i + 1) % 4]); } draw_line_pairs(coords_a, coords_b, 4, color); } else if (ELEM(ipd->primitive_type, PLACE_PRIMITIVE_TYPE_SPHERE_UV, PLACE_PRIMITIVE_TYPE_SPHERE_ICO)) { /* See bound-box diagram for reference. */ /* Primary Side. */ float v01[3], v12[3], v23[3], v30[3]; mid_v3_v3v3(v01, bounds.vec[0], bounds.vec[1]); mid_v3_v3v3(v12, bounds.vec[1], bounds.vec[2]); mid_v3_v3v3(v23, bounds.vec[2], bounds.vec[3]); mid_v3_v3v3(v30, bounds.vec[3], bounds.vec[0]); /* Secondary Side. */ float v45[3], v56[3], v67[3], v74[3]; mid_v3_v3v3(v45, bounds.vec[4], bounds.vec[5]); mid_v3_v3v3(v56, bounds.vec[5], bounds.vec[6]); mid_v3_v3v3(v67, bounds.vec[6], bounds.vec[7]); mid_v3_v3v3(v74, bounds.vec[7], bounds.vec[4]); /* Edges between. */ float v04[3], v15[3], v26[3], v37[3]; mid_v3_v3v3(v04, bounds.vec[0], bounds.vec[4]); mid_v3_v3v3(v15, bounds.vec[1], bounds.vec[5]); mid_v3_v3v3(v26, bounds.vec[2], bounds.vec[6]); mid_v3_v3v3(v37, bounds.vec[3], bounds.vec[7]); draw_circle_in_quad(v01, v45, v67, v23, 32, color); draw_circle_in_quad(v30, v12, v56, v74, 32, color); draw_circle_in_quad(v04, v15, v26, v37, 32, color); } } static void draw_primitive_view(const struct bContext *C, ARegion *UNUSED(region), void *arg) { struct InteractivePlaceData *ipd = arg; float color[4]; UI_GetThemeColor3fv(TH_GIZMO_PRIMARY, color); const bool use_depth = !XRAY_ENABLED(ipd->v3d); const eGPUDepthTest depth_test_enabled = GPU_depth_test_get(); if (use_depth) { GPU_depth_test(GPU_DEPTH_NONE); color[3] = 0.15f; draw_primitive_view_impl(C, ipd, color, -1); } /* Show a flattened projection if the current step is aligned to the view. */ if (ipd->step[ipd->step_index].is_degenerate_view_align) { const RegionView3D *rv3d = ipd->region->regiondata; if (!rv3d->is_persp) { draw_primitive_view_impl(C, ipd, color, ipd->step_index); } } if (use_depth) { GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } color[3] = 1.0f; draw_primitive_view_impl(C, ipd, color, -1); if (use_depth) { if (depth_test_enabled == false) { GPU_depth_test(GPU_DEPTH_NONE); } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Calculate The Initial Placement Plane * * Use by both the operator and placement cursor. * \{ */ static void view3d_interactive_add_calc_plane(bContext *C, Scene *scene, View3D *v3d, ARegion *region, const float mval_fl[2], wmGizmo *snap_gizmo, const enum ePlace_SnapTo snap_to, const enum ePlace_Depth plane_depth, const enum ePlace_Orient plane_orient, const int plane_axis, const bool plane_axis_auto, float r_co_src[3], float r_matrix_orient[3][3]) { const RegionView3D *rv3d = region->regiondata; ED_transform_calc_orientation_from_type(C, r_matrix_orient); /* Non-orthogonal matrices cause the preview and final result not to match. * * While making orthogonal doesn't always work well (especially with gimbal orientation for e.g.) * it's a corner case, without better alternatives as objects don't support shear. */ orthogonalize_m3(r_matrix_orient, plane_axis); SnapObjectContext *snap_context = NULL; bool snap_context_free = false; /* Set the orientation. */ if ((plane_orient == PLACE_ORIENT_SURFACE) || (plane_depth == PLACE_DEPTH_SURFACE)) { snap_context = (snap_gizmo ? ED_gizmotypes_snap_3d_context_ensure(scene, region, v3d, snap_gizmo) : NULL); if (snap_context == NULL) { snap_context = ED_transform_snap_object_context_create_view3d(scene, 0, region, v3d); snap_context_free = true; } } if (plane_orient == PLACE_ORIENT_SURFACE) { bool found_surface_or_normal = false; float matrix_orient_surface[3][3]; /* Use the snap normal as a fallback in case the cursor isn't over a surface * but snapping is enabled. */ float normal_fallback[3]; bool use_normal_fallback = snap_gizmo ? idp_snap_normal_from_gizmo(snap_gizmo, normal_fallback) : false; if ((snap_context != NULL) && idp_poject_surface_normal(snap_context, CTX_data_ensure_evaluated_depsgraph(C), mval_fl, use_normal_fallback ? r_matrix_orient : NULL, use_normal_fallback ? normal_fallback : NULL, matrix_orient_surface)) { copy_m3_m3(r_matrix_orient, matrix_orient_surface); found_surface_or_normal = true; } if (!found_surface_or_normal && plane_axis_auto) { /* Drawing into empty space, draw onto the plane most aligned to the view direction. */ mat3_align_axis_to_v3(r_matrix_orient, plane_axis, rv3d->viewinv[2]); } } const bool is_snap_found = snap_gizmo ? idp_snap_point_from_gizmo(snap_gizmo, r_co_src) : false; if (is_snap_found) { /* pass */ } else { bool use_depth_fallback = true; if (plane_depth == PLACE_DEPTH_CURSOR_VIEW) { /* View plane. */ ED_view3d_win_to_3d(v3d, region, scene->cursor.location, mval_fl, r_co_src); use_depth_fallback = false; } else if (plane_depth == PLACE_DEPTH_SURFACE) { if ((snap_context != NULL) && ED_transform_snap_object_project_view3d(snap_context, CTX_data_ensure_evaluated_depsgraph(C), SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, .edit_mode_type = SNAP_GEOM_EDIT, }, mval_fl, NULL, NULL, r_co_src, NULL)) { use_depth_fallback = false; } } /* Use as fallback to surface. */ if (use_depth_fallback || (plane_depth == PLACE_DEPTH_CURSOR_PLANE)) { /* Cursor plane. */ float plane[4]; const float *plane_normal = r_matrix_orient[plane_axis]; const float view_axis_dot = fabsf(dot_v3v3(rv3d->viewinv[2], r_matrix_orient[plane_axis])); if (view_axis_dot < eps_view_align) { /* In this case, just project onto the view plane as it's important the location * is _always_ under the mouse cursor, even if it turns out that won't lie on * the original 'plane' that's been calculated for us. */ plane_normal = rv3d->viewinv[2]; } plane_from_point_normal_v3(plane, scene->cursor.location, plane_normal); if (view3d_win_to_3d_on_plane_maybe_fallback(region, plane, mval_fl, NULL, r_co_src)) { use_depth_fallback = false; } /* Even if the calculation works, it's possible the point found is behind the view, * or very far away (past the far clipping). * In either case creating objects won't be useful. */ if (rv3d->is_persp) { float dir[3]; sub_v3_v3v3(dir, rv3d->viewinv[3], r_co_src); const float dot = dot_v3v3(dir, rv3d->viewinv[2]); if (dot < v3d->clip_start || dot > v3d->clip_end) { use_depth_fallback = true; } } } if (use_depth_fallback) { float co_depth[3]; /* Fallback to view center. */ negate_v3_v3(co_depth, rv3d->ofs); ED_view3d_win_to_3d(v3d, region, co_depth, mval_fl, r_co_src); } } if (!is_snap_found && ((snap_gizmo != NULL) && ED_gizmotypes_snap_3d_is_enabled(snap_gizmo))) { if (snap_to == PLACE_SNAP_TO_DEFAULT) { idp_snap_calc_incremental(scene, v3d, region, NULL, r_co_src); } } if (snap_context_free) { ED_transform_snap_object_context_destroy(snap_context); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Add Object Modal Operator * \{ */ static void view3d_interactive_add_begin(bContext *C, wmOperator *op, const wmEvent *event) { const int plane_axis = RNA_enum_get(op->ptr, "plane_axis"); const bool plane_axis_auto = RNA_boolean_get(op->ptr, "plane_axis_auto"); const enum ePlace_SnapTo snap_to = RNA_enum_get(op->ptr, "snap_target"); const enum ePlace_Depth plane_depth = RNA_enum_get(op->ptr, "plane_depth"); const enum ePlace_Origin plane_origin[2] = { RNA_enum_get(op->ptr, "plane_origin_base"), RNA_enum_get(op->ptr, "plane_origin_depth"), }; const enum ePlace_Aspect plane_aspect[2] = { RNA_enum_get(op->ptr, "plane_aspect_base"), RNA_enum_get(op->ptr, "plane_aspect_depth"), }; const enum ePlace_Orient plane_orient = RNA_enum_get(op->ptr, "plane_orientation"); const float mval_fl[2] = {UNPACK2(event->mval)}; struct InteractivePlaceData *ipd = op->customdata; /* Assign snap gizmo which is may be used as part of the tool. */ { wmGizmoGroup *gzgroup = idp_gizmogroup_from_region(ipd->region); if (gzgroup != NULL) { if (gzgroup->gizmos.first) { ipd->snap_gizmo = gzgroup->gizmos.first; } /* Can be NULL when gizmos are disabled. */ if (gzgroup->customdata != NULL) { preview_plane_cursor_visible_set(gzgroup, false); } } } /* For tweak events the snap target may have changed since dragging, * update the snap target at the cursor location where tweak began. * * NOTE: we could investigating solving this in a more generic way, * so each operator doesn't have to account for it. */ if (ISTWEAK(event->type)) { if (ipd->snap_gizmo != NULL) { ED_gizmotypes_snap_3d_update(ipd->snap_gizmo, CTX_data_ensure_evaluated_depsgraph(C), ipd->region, ipd->v3d, G_MAIN->wm.first, mval_fl); } } ipd->launch_event = WM_userdef_event_type_from_keymap_type(event->type); view3d_interactive_add_calc_plane(C, ipd->scene, ipd->v3d, ipd->region, mval_fl, ipd->snap_gizmo, snap_to, plane_depth, plane_orient, plane_axis, plane_axis_auto, ipd->co_src, ipd->matrix_orient); ipd->orient_axis = plane_axis; for (int i = 0; i < 2; i++) { ipd->step[i].is_centered_init = (plane_origin[i] == PLACE_ORIGIN_CENTER); ipd->step[i].is_centered = ipd->step[i].is_centered_init; ipd->step[i].is_fixed_aspect_init = (plane_aspect[i] == PLACE_ASPECT_FIXED); ipd->step[i].is_fixed_aspect = ipd->step[i].is_fixed_aspect_init; } ipd->step_index = STEP_BASE; ipd->snap_to = snap_to; plane_from_point_normal_v3(ipd->step[0].plane, ipd->co_src, ipd->matrix_orient[plane_axis]); copy_v3_v3(ipd->step[0].co_dst, ipd->co_src); { RegionView3D *rv3d = ipd->region->regiondata; const float view_axis_dot = fabsf(dot_v3v3(rv3d->viewinv[2], ipd->matrix_orient[plane_axis])); ipd->step[STEP_BASE].is_degenerate_view_align = view_axis_dot < eps_view_align; ipd->step[STEP_DEPTH].is_degenerate_view_align = fabsf(view_axis_dot - 1.0f) < eps_view_align; float view_axis[3]; normalize_v3_v3(view_axis, rv3d->viewinv[2]); plane_from_point_normal_v3(ipd->view_plane, ipd->co_src, view_axis); } if (ipd->step[STEP_BASE].is_degenerate_view_align || ipd->step[STEP_DEPTH].is_degenerate_view_align) { RegionView3D *rv3d = ipd->region->regiondata; float axis_view[3]; add_v3_v3v3(axis_view, rv3d->viewinv[0], rv3d->viewinv[1]); normalize_v3(axis_view); /* Setup fallback axes. */ for (int i = 0; i < 2; i++) { if (ipd->step[i].is_degenerate_view_align) { const int degenerate_axis = (i == STEP_BASE) ? /* For #STEP_BASE find the orient axis that align to the view. */ dot_v3_array_find_max_index(ipd->matrix_orient, 3, rv3d->viewinv[2], false) : /* For #STEP_DEPTH the orient axis is always view aligned when degenerate. */ ipd->orient_axis; float axis_fallback[4][3]; const int x_axis = (degenerate_axis + 1) % 3; const int y_axis = (degenerate_axis + 2) % 3; /* Assign 4x diagonal axes, find which one is closest to the viewport diagonal * bottom left to top right, for a predictable direction from a user perspective. */ add_v3_v3v3(axis_fallback[0], ipd->matrix_orient[x_axis], ipd->matrix_orient[y_axis]); sub_v3_v3v3(axis_fallback[1], ipd->matrix_orient[x_axis], ipd->matrix_orient[y_axis]); negate_v3_v3(axis_fallback[2], axis_fallback[0]); negate_v3_v3(axis_fallback[3], axis_fallback[1]); const int axis_best = dot_v3_array_find_max_index(axis_fallback, 4, axis_view, true); normalize_v3_v3(ipd->step[i].degenerate_diagonal, axis_fallback[axis_best]); ipd->step[i].degenerate_axis = degenerate_axis; /* `degenerate_view_plane_fallback` is used to map cursor motion from a view aligned * plane back onto the view aligned plane. * * The dot product check below ensures cursor motion * isn't inverted from a user perspective. */ const bool degenerate_axis_is_flip = dot_v3v3(ipd->matrix_orient[degenerate_axis], ((i == STEP_BASE) ? ipd->step[i].degenerate_diagonal : rv3d->viewinv[2])) < 0.0f; copy_v3_v3(ipd->step[i].degenerate_diagonal_display, ipd->step[i].degenerate_diagonal); if (degenerate_axis_is_flip) { negate_v3(ipd->step[i].degenerate_diagonal_display); } } } } ipd->is_snap_invert = ipd->snap_gizmo ? ED_gizmotypes_snap_3d_invert_snap_get(ipd->snap_gizmo) : false; { const ToolSettings *ts = ipd->scene->toolsettings; ipd->use_snap = (ipd->is_snap_invert == !(ts->snap_flag & SCE_SNAP)); } ipd->draw_handle_view = ED_region_draw_cb_activate( ipd->region->type, draw_primitive_view, ipd, REGION_DRAW_POST_VIEW); ED_region_tag_redraw(ipd->region); /* Setup the primitive type. */ { PropertyRNA *prop = RNA_struct_find_property(op->ptr, "primitive_type"); if (RNA_property_is_set(op->ptr, prop)) { ipd->primitive_type = RNA_property_enum_get(op->ptr, prop); ipd->use_tool = false; } else { ipd->use_tool = true; /* Get from the tool, a bit of a non-standard way of operating. */ const bToolRef *tref = ipd->area->runtime.tool; if (tref && STREQ(tref->idname, "builtin.primitive_cube_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_CUBE; } else if (tref && STREQ(tref->idname, "builtin.primitive_cylinder_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_CYLINDER; } else if (tref && STREQ(tref->idname, "builtin.primitive_cone_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_CONE; } else if (tref && STREQ(tref->idname, "builtin.primitive_uv_sphere_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_SPHERE_UV; } else if (tref && STREQ(tref->idname, "builtin.primitive_ico_sphere_add")) { ipd->primitive_type = PLACE_PRIMITIVE_TYPE_SPHERE_ICO; } else { /* If the user runs this as an operator they should set the 'primitive_type', * however running from operator search will end up at this point. */ ipd->primitive_type = PLACE_PRIMITIVE_TYPE_CUBE; ipd->use_tool = false; } } } } static int view3d_interactive_add_invoke(bContext *C, wmOperator *op, const wmEvent *event) { const bool wait_for_input = RNA_boolean_get(op->ptr, "wait_for_input"); struct InteractivePlaceData *ipd = MEM_callocN(sizeof(*ipd), __func__); op->customdata = ipd; ipd->scene = CTX_data_scene(C); ipd->area = CTX_wm_area(C); ipd->region = CTX_wm_region(C); ipd->v3d = CTX_wm_view3d(C); if (wait_for_input) { ipd->wait_for_input = true; /* TODO: support snapping when not using with tool. */ #if 0 WM_gizmo_group_type_ensure(view3d_gzgt_placement_id); #endif } else { view3d_interactive_add_begin(C, op, event); } WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; } static void view3d_interactive_add_exit(bContext *C, wmOperator *op) { UNUSED_VARS(C); struct InteractivePlaceData *ipd = op->customdata; ED_region_draw_cb_exit(ipd->region->type, ipd->draw_handle_view); ED_region_tag_redraw(ipd->region); { wmGizmoGroup *gzgroup = idp_gizmogroup_from_region(ipd->region); if (gzgroup != NULL) { if (gzgroup->customdata != NULL) { preview_plane_cursor_visible_set(gzgroup, true); } } } MEM_freeN(ipd); } static void view3d_interactive_add_cancel(bContext *C, wmOperator *op) { view3d_interactive_add_exit(C, op); } enum { PLACE_MODAL_SNAP_ON, PLACE_MODAL_SNAP_OFF, PLACE_MODAL_FIXED_ASPECT_ON, PLACE_MODAL_FIXED_ASPECT_OFF, PLACE_MODAL_PIVOT_CENTER_ON, PLACE_MODAL_PIVOT_CENTER_OFF, }; void viewplace_modal_keymap(wmKeyConfig *keyconf) { static const EnumPropertyItem modal_items[] = { {PLACE_MODAL_SNAP_ON, "SNAP_ON", 0, "Snap On", ""}, {PLACE_MODAL_SNAP_OFF, "SNAP_OFF", 0, "Snap Off", ""}, {PLACE_MODAL_FIXED_ASPECT_ON, "FIXED_ASPECT_ON", 0, "Fixed Aspect On", ""}, {PLACE_MODAL_FIXED_ASPECT_OFF, "FIXED_ASPECT_OFF", 0, "Fixed Aspect Off", ""}, {PLACE_MODAL_PIVOT_CENTER_ON, "PIVOT_CENTER_ON", 0, "Center Pivot On", ""}, {PLACE_MODAL_PIVOT_CENTER_OFF, "PIVOT_CENTER_OFF", 0, "Center Pivot Off", ""}, {0, NULL, 0, NULL, NULL}, }; const char *keymap_name = "View3D Placement Modal"; wmKeyMap *keymap = WM_modalkeymap_find(keyconf, keymap_name); /* This function is called for each space-type, only needs to add map once. */ if (keymap && keymap->modal_items) { return; } keymap = WM_modalkeymap_ensure(keyconf, keymap_name, modal_items); WM_modalkeymap_assign(keymap, "VIEW3D_OT_interactive_add"); } static int view3d_interactive_add_modal(bContext *C, wmOperator *op, const wmEvent *event) { UNUSED_VARS(C, op); struct InteractivePlaceData *ipd = op->customdata; ARegion *region = ipd->region; bool do_redraw = false; bool do_cursor_update = false; /* Handle modal key-map. */ if (event->type == EVT_MODAL_MAP) { bool is_fallthrough = false; switch (event->val) { case PLACE_MODAL_FIXED_ASPECT_ON: { is_fallthrough = true; ATTR_FALLTHROUGH; } case PLACE_MODAL_FIXED_ASPECT_OFF: { ipd->step[ipd->step_index].is_fixed_aspect = is_fallthrough ^ ipd->step[ipd->step_index].is_fixed_aspect_init; do_redraw = true; break; } case PLACE_MODAL_PIVOT_CENTER_ON: { is_fallthrough = true; ATTR_FALLTHROUGH; } case PLACE_MODAL_PIVOT_CENTER_OFF: { ipd->step[ipd->step_index].is_centered = is_fallthrough ^ ipd->step[ipd->step_index].is_centered_init; do_redraw = true; break; } case PLACE_MODAL_SNAP_ON: { is_fallthrough = true; ATTR_FALLTHROUGH; } case PLACE_MODAL_SNAP_OFF: { const ToolSettings *ts = ipd->scene->toolsettings; ipd->is_snap_invert = is_fallthrough; ipd->use_snap = (ipd->is_snap_invert == !(ts->snap_flag & SCE_SNAP)); do_cursor_update = true; break; } } } else { switch (event->type) { case EVT_ESCKEY: case RIGHTMOUSE: { view3d_interactive_add_exit(C, op); return OPERATOR_CANCELLED; } case MOUSEMOVE: { do_cursor_update = true; break; } } } if (ipd->wait_for_input) { if (ELEM(event->type, LEFTMOUSE)) { if (event->val == KM_PRESS) { view3d_interactive_add_begin(C, op, event); ipd->wait_for_input = false; return OPERATOR_RUNNING_MODAL; } } return OPERATOR_RUNNING_MODAL; } if (ipd->step_index == STEP_BASE) { if (ELEM(event->type, ipd->launch_event, LEFTMOUSE)) { if (event->val == KM_RELEASE) { /* Set secondary plane. */ /* Create normal. */ { RegionView3D *rv3d = region->regiondata; float no[3], no_temp[3]; if (ipd->step[STEP_DEPTH].is_degenerate_view_align) { cross_v3_v3v3(no_temp, ipd->step[0].plane, ipd->step[STEP_DEPTH].degenerate_diagonal); cross_v3_v3v3(no, no_temp, ipd->step[0].plane); } else { cross_v3_v3v3(no_temp, ipd->step[0].plane, rv3d->viewinv[2]); cross_v3_v3v3(no, no_temp, ipd->step[0].plane); } normalize_v3(no); plane_from_point_normal_v3(ipd->step[1].plane, ipd->step[0].co_dst, no); } copy_v3_v3(ipd->step[1].co_dst, ipd->step[0].co_dst); ipd->step_index = STEP_DEPTH; /* Use the toggle from the previous step. */ if (ipd->step[0].is_centered != ipd->step[0].is_centered_init) { ipd->step[1].is_centered = !ipd->step[1].is_centered; } if (ipd->step[0].is_fixed_aspect != ipd->step[0].is_fixed_aspect_init) { ipd->step[1].is_fixed_aspect = !ipd->step[1].is_fixed_aspect; } } } } else if (ipd->step_index == STEP_DEPTH) { if (ELEM(event->type, ipd->launch_event, LEFTMOUSE)) { if (event->val == KM_PRESS) { BoundBox bounds; calc_bbox(ipd, &bounds); float location[3]; float rotation[3]; float scale[3]; float matrix_orient_axis[3][3]; copy_m3_m3(matrix_orient_axis, ipd->matrix_orient); if (ipd->orient_axis != 2) { swap_v3_v3(matrix_orient_axis[2], matrix_orient_axis[ipd->orient_axis]); swap_v3_v3(matrix_orient_axis[0], matrix_orient_axis[1]); } /* Needed for shapes where the sign matters (cone for eg). */ { float delta[3]; sub_v3_v3v3(delta, bounds.vec[0], bounds.vec[4]); if (dot_v3v3(ipd->matrix_orient[ipd->orient_axis], delta) > 0.0f) { negate_v3(matrix_orient_axis[2]); /* Only flip Y so we don't flip a single axis which causes problems. */ negate_v3(matrix_orient_axis[1]); } } mat3_to_eul(rotation, matrix_orient_axis); mid_v3_v3v3(location, bounds.vec[0], bounds.vec[6]); const int cube_verts[3] = {3, 1, 4}; for (int i = 0; i < 3; i++) { scale[i] = len_v3v3(bounds.vec[0], bounds.vec[cube_verts[i]]); /* Primitives have size 2 by default, compensate for this here. */ scale[i] /= 2.0f; } wmOperatorType *ot = NULL; PointerRNA op_props; if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CUBE) { ot = WM_operatortype_find("MESH_OT_primitive_cube_add", false); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CYLINDER) { ot = WM_operatortype_find("MESH_OT_primitive_cylinder_add", false); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CONE) { ot = WM_operatortype_find("MESH_OT_primitive_cone_add", false); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_SPHERE_UV) { ot = WM_operatortype_find("MESH_OT_primitive_uv_sphere_add", false); } else if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_SPHERE_ICO) { ot = WM_operatortype_find("MESH_OT_primitive_ico_sphere_add", false); } if (ot != NULL) { WM_operator_properties_create_ptr(&op_props, ot); if (ipd->use_tool) { bToolRef *tref = ipd->area->runtime.tool; PointerRNA temp_props; WM_toolsystem_ref_properties_init_for_keymap(tref, &temp_props, &op_props, ot); SWAP(PointerRNA, temp_props, op_props); WM_operator_properties_free(&temp_props); } RNA_float_set_array(&op_props, "rotation", rotation); RNA_float_set_array(&op_props, "location", location); RNA_float_set_array(&op_props, "scale", scale); /* Always use the defaults here since desired bounds have been set interactively, it does * not make sense to use a different values from a previous command. */ if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CUBE) { RNA_float_set(&op_props, "size", 2.0f); } if (ELEM(ipd->primitive_type, PLACE_PRIMITIVE_TYPE_CYLINDER, PLACE_PRIMITIVE_TYPE_SPHERE_UV, PLACE_PRIMITIVE_TYPE_SPHERE_ICO)) { RNA_float_set(&op_props, "radius", 1.0f); } if (ELEM( ipd->primitive_type, PLACE_PRIMITIVE_TYPE_CYLINDER, PLACE_PRIMITIVE_TYPE_CONE)) { RNA_float_set(&op_props, "depth", 2.0f); } if (ipd->primitive_type == PLACE_PRIMITIVE_TYPE_CONE) { RNA_float_set(&op_props, "radius1", 1.0f); RNA_float_set(&op_props, "radius2", 0.0f); } WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &op_props); WM_operator_properties_free(&op_props); } else { BLI_assert(0); } view3d_interactive_add_exit(C, op); return OPERATOR_FINISHED; } } } else { BLI_assert(0); } if (do_cursor_update) { const float mval_fl[2] = {UNPACK2(event->mval)}; /* Calculate the snap location on mouse-move or when toggling snap. */ ipd->is_snap_found = false; if (ipd->use_snap) { if (ipd->snap_gizmo != NULL) { ED_gizmotypes_snap_3d_flag_set(ipd->snap_gizmo, ED_SNAPGIZMO_TOGGLE_ALWAYS_TRUE); if (ED_gizmotypes_snap_3d_update(ipd->snap_gizmo, CTX_data_ensure_evaluated_depsgraph(C), ipd->region, ipd->v3d, G_MAIN->wm.first, mval_fl)) { ED_gizmotypes_snap_3d_data_get(ipd->snap_gizmo, ipd->snap_co, NULL, NULL, NULL); ipd->is_snap_found = true; } ED_gizmotypes_snap_3d_flag_clear(ipd->snap_gizmo, ED_SNAPGIZMO_TOGGLE_ALWAYS_TRUE); } } if (ipd->step_index == STEP_BASE) { if (ipd->is_snap_found) { closest_to_plane_normalized_v3( ipd->step[STEP_BASE].co_dst, ipd->step[STEP_BASE].plane, ipd->snap_co); } else { if (view3d_win_to_3d_on_plane_maybe_fallback( region, ipd->step[STEP_BASE].plane, mval_fl, ipd->step[STEP_BASE].is_degenerate_view_align ? ipd->view_plane : NULL, ipd->step[STEP_BASE].co_dst)) { /* pass */ } if (ipd->use_snap && (ipd->snap_to == PLACE_SNAP_TO_DEFAULT)) { if (idp_snap_calc_incremental( ipd->scene, ipd->v3d, ipd->region, ipd->co_src, ipd->step[STEP_BASE].co_dst)) { } } } } else if (ipd->step_index == STEP_DEPTH) { if (ipd->is_snap_found) { closest_to_plane_normalized_v3( ipd->step[STEP_DEPTH].co_dst, ipd->step[STEP_DEPTH].plane, ipd->snap_co); } else { if (view3d_win_to_3d_on_plane_maybe_fallback( region, ipd->step[STEP_DEPTH].plane, mval_fl, ipd->step[STEP_DEPTH].is_degenerate_view_align ? ipd->view_plane : NULL, ipd->step[STEP_DEPTH].co_dst)) { /* pass */ } if (ipd->use_snap && (ipd->snap_to == PLACE_SNAP_TO_DEFAULT)) { if (idp_snap_calc_incremental( ipd->scene, ipd->v3d, ipd->region, ipd->co_src, ipd->step[STEP_DEPTH].co_dst)) { } } } /* Correct the point so it's aligned with the 'ipd->step[0].co_dst'. */ float close[3], delta[3]; closest_to_plane_normalized_v3( close, ipd->step[STEP_BASE].plane, ipd->step[STEP_DEPTH].co_dst); sub_v3_v3v3(delta, close, ipd->step[STEP_BASE].co_dst); sub_v3_v3(ipd->step[STEP_DEPTH].co_dst, delta); } do_redraw = true; } if (do_redraw) { ED_region_tag_redraw(region); } return OPERATOR_RUNNING_MODAL; } static bool view3d_interactive_add_poll(bContext *C) { const enum eContextObjectMode mode = CTX_data_mode_enum(C); return ELEM(mode, CTX_MODE_OBJECT, CTX_MODE_EDIT_MESH); } void VIEW3D_OT_interactive_add(struct wmOperatorType *ot) { /* identifiers */ ot->name = "Add Primitive Object"; ot->description = "Interactively add an object"; ot->idname = "VIEW3D_OT_interactive_add"; /* api callbacks */ ot->invoke = view3d_interactive_add_invoke; ot->modal = view3d_interactive_add_modal; ot->cancel = view3d_interactive_add_cancel; ot->poll = view3d_interactive_add_poll; /* NOTE: let the operator we call handle undo and registering itself. */ /* flags */ ot->flag = 0; /* properties */ PropertyRNA *prop; /* Normally not accessed directly, leave unset and check the active tool. */ static const EnumPropertyItem primitive_type[] = { {PLACE_PRIMITIVE_TYPE_CUBE, "CUBE", 0, "Cube", ""}, {PLACE_PRIMITIVE_TYPE_CYLINDER, "CYLINDER", 0, "Cylinder", ""}, {PLACE_PRIMITIVE_TYPE_CONE, "CONE", 0, "Cone", ""}, {PLACE_PRIMITIVE_TYPE_SPHERE_UV, "SPHERE_UV", 0, "UV Sphere", ""}, {PLACE_PRIMITIVE_TYPE_SPHERE_ICO, "SPHERE_ICO", 0, "ICO Sphere", ""}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_property(ot->srna, "primitive_type", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Primitive", ""); RNA_def_property_enum_items(prop, primitive_type); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_property(ot->srna, "plane_axis", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Plane Axis", "The axis used for placing the base region"); RNA_def_property_enum_default(prop, 2); RNA_def_property_enum_items(prop, rna_enum_axis_xyz_items); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_boolean(ot->srna, "plane_axis_auto", false, "Auto Axis", "Select the closest axis when placing objects " "(surface overrides)"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); static const EnumPropertyItem plane_depth_items[] = { {PLACE_DEPTH_SURFACE, "SURFACE", 0, "Surface", "Start placing on the surface, using the 3D cursor position as a fallback"}, {PLACE_DEPTH_CURSOR_PLANE, "CURSOR_PLANE", 0, "Cursor Plane", "Start placement using a point projected onto the orientation axis " "at the 3D cursor position"}, {PLACE_DEPTH_CURSOR_VIEW, "CURSOR_VIEW", 0, "Cursor View", "Start placement using a point projected onto the view plane at the 3D cursor position"}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_property(ot->srna, "plane_depth", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Position", "The initial depth used when placing the cursor"); RNA_def_property_enum_default(prop, PLACE_DEPTH_SURFACE); RNA_def_property_enum_items(prop, plane_depth_items); RNA_def_property_flag(prop, PROP_SKIP_SAVE); static const EnumPropertyItem plane_orientation_items[] = { {PLACE_ORIENT_SURFACE, "SURFACE", ICON_SNAP_NORMAL, "Surface", "Use the surface normal (using the transform orientation as a fallback)"}, {PLACE_ORIENT_DEFAULT, "DEFAULT", ICON_ORIENTATION_GLOBAL, "Default", "Use the current transform orientation"}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_property(ot->srna, "plane_orientation", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Orientation", "The initial depth used when placing the cursor"); RNA_def_property_enum_default(prop, PLACE_ORIENT_SURFACE); RNA_def_property_enum_items(prop, plane_orientation_items); RNA_def_property_flag(prop, PROP_SKIP_SAVE); static const EnumPropertyItem snap_to_items[] = { {PLACE_SNAP_TO_GEOMETRY, "GEOMETRY", 0, "Geometry", "Snap to all geometry"}, {PLACE_SNAP_TO_DEFAULT, "DEFAULT", 0, "Default", "Use the current snap settings"}, {0, NULL, 0, NULL, NULL}, }; prop = RNA_def_property(ot->srna, "snap_target", PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Snap to", "The target to use while snapping"); RNA_def_property_enum_default(prop, PLACE_SNAP_TO_GEOMETRY); RNA_def_property_enum_items(prop, snap_to_items); RNA_def_property_flag(prop, PROP_SKIP_SAVE); { /* Plane Origin. */ static const EnumPropertyItem items[] = { {PLACE_ORIGIN_BASE, "EDGE", 0, "Edge", "Start placing the edge position"}, {PLACE_ORIGIN_CENTER, "CENTER", 0, "Center", "Start placing the center position"}, {0, NULL, 0, NULL, NULL}, }; const char *identifiers[2] = {"plane_origin_base", "plane_origin_depth"}; for (int i = 0; i < 2; i++) { prop = RNA_def_property(ot->srna, identifiers[i], PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Origin", "The initial position for placement"); RNA_def_property_enum_default(prop, PLACE_ORIGIN_BASE); RNA_def_property_enum_items(prop, items); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } } { /* Plane Aspect. */ static const EnumPropertyItem items[] = { {PLACE_ASPECT_FREE, "FREE", 0, "Free", "Use an unconstrained aspect"}, {PLACE_ASPECT_FIXED, "FIXED", 0, "Fixed", "Use a fixed 1:1 aspect"}, {0, NULL, 0, NULL, NULL}, }; const char *identifiers[2] = {"plane_aspect_base", "plane_aspect_depth"}; for (int i = 0; i < 2; i++) { prop = RNA_def_property(ot->srna, identifiers[i], PROP_ENUM, PROP_NONE); RNA_def_property_ui_text(prop, "Aspect", "The initial aspect setting"); RNA_def_property_enum_default(prop, PLACE_ASPECT_FREE); RNA_def_property_enum_items(prop, items); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } } /* When not accessed via a tool. */ prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Placement Gizmo Group * * This is currently only used for snapping before the tool is initialized, * we could show a placement plane here. * \{ */ static void WIDGETGROUP_placement_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) { wmGizmo *gizmo; { /* The gizmo snap has to be the first gizmo. */ const wmGizmoType *gzt_snap; gzt_snap = WM_gizmotype_find("GIZMO_GT_snap_3d", true); gizmo = WM_gizmo_new_ptr(gzt_snap, gzgroup, NULL); WM_gizmo_set_color(gizmo, (float[4]){1.0f, 1.0f, 1.0f, 1.0f}); /* Don't handle any events, this is for display only. */ gizmo->flag |= WM_GIZMO_HIDDEN_KEYMAP; } /* Sets the gizmos custom-data which has its own free callback. */ preview_plane_cursor_setup(gzgroup); } void VIEW3D_GGT_placement(wmGizmoGroupType *gzgt) { gzgt->name = "Placement Widget"; gzgt->idname = view3d_gzgt_placement_id; gzgt->flag |= WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_SCALE | WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; gzgt->poll = ED_gizmo_poll_or_unlink_delayed_from_tool; gzgt->setup = WIDGETGROUP_placement_setup; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Placement Preview Plane * * Preview the plane that will be used for placement. * * Note that we might want to split this into its own file, * for now this is coupled with the 3D view placement gizmo. * \{ */ static void gizmo_plane_update_cursor(const bContext *C, ARegion *region, const int mval[2], float r_co[3], float r_matrix_orient[3][3], int *r_plane_axis) { wmOperatorType *ot = WM_operatortype_find("VIEW3D_OT_interactive_add", true); BLI_assert(ot != NULL); PointerRNA ptr; ScrArea *area = CTX_wm_area(C); BLI_assert(region == CTX_wm_region(C)); bToolRef *tref = area->runtime.tool; WM_toolsystem_ref_properties_ensure_from_operator(tref, ot, &ptr); const enum ePlace_SnapTo snap_to = RNA_enum_get(&ptr, "snap_target"); const int plane_axis = RNA_enum_get(&ptr, "plane_axis"); const bool plane_axis_auto = RNA_boolean_get(&ptr, "plane_axis_auto"); const enum ePlace_Depth plane_depth = RNA_enum_get(&ptr, "plane_depth"); const enum ePlace_Orient plane_orient = RNA_enum_get(&ptr, "plane_orientation"); const float mval_fl[2] = {UNPACK2(mval)}; Scene *scene = CTX_data_scene(C); View3D *v3d = CTX_wm_view3d(C); /* Assign snap gizmo which is may be used as part of the tool. */ wmGizmo *snap_gizmo = NULL; { wmGizmoGroup *gzgroup = idp_gizmogroup_from_region(region); if ((gzgroup != NULL) && gzgroup->gizmos.first) { snap_gizmo = gzgroup->gizmos.first; } } /* This ensures the snap gizmo has settings from this tool. * This function call could be moved a more appropriate place, * responding to the setting being changed for example, * however setting the value isn't expensive, so do it here. */ idp_snap_gizmo_update_snap_elements(scene, snap_to, snap_gizmo); view3d_interactive_add_calc_plane((bContext *)C, scene, v3d, region, mval_fl, snap_gizmo, snap_to, plane_depth, plane_orient, plane_axis, plane_axis_auto, r_co, r_matrix_orient); *r_plane_axis = plane_axis; } static void gizmo_plane_draw_grid(const int resolution, const float scale, const float scale_fade, const float matrix[4][4], const int plane_axis, const float color[4]) { BLI_assert(scale_fade <= scale); const int resolution_min = resolution - 1; float color_fade[4] = {UNPACK4(color)}; const float *center = matrix[3]; GPU_blend(GPU_BLEND_ADDITIVE); GPU_line_smooth(true); GPU_line_width(1.0f); GPUVertFormat *format = immVertexFormat(); const uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); const uint col_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR); const size_t coords_len = resolution * resolution; float(*coords)[3] = MEM_mallocN(sizeof(*coords) * coords_len, __func__); const int axis_x = (plane_axis + 0) % 3; const int axis_y = (plane_axis + 1) % 3; const int axis_z = (plane_axis + 2) % 3; int i; const float resolution_div = (float)1.0f / (float)resolution; i = 0; for (int x = 0; x < resolution; x++) { const float x_fl = (x * resolution_div) - 0.5f; for (int y = 0; y < resolution; y++) { const float y_fl = (y * resolution_div) - 0.5f; coords[i][axis_x] = 0.0f; coords[i][axis_y] = x_fl * scale; coords[i][axis_z] = y_fl * scale; mul_m4_v3(matrix, coords[i]); i += 1; } } BLI_assert(i == coords_len); immBeginAtMost(GPU_PRIM_LINES, coords_len * 4); i = 0; for (int x = 0; x < resolution_min; x++) { for (int y = 0; y < resolution_min; y++) { /* Add #resolution_div to ensure we fade-out entirely. */ #define FADE(v) \ max_ff(0.0f, (1.0f - square_f(((len_v3v3(v, center) / scale_fade) + resolution_div) * 2.0f))) const float *v0 = coords[(resolution * x) + y]; const float *v1 = coords[(resolution * (x + 1)) + y]; const float *v2 = coords[(resolution * x) + (y + 1)]; const float f0 = FADE(v0); const float f1 = FADE(v1); const float f2 = FADE(v2); if (f0 > 0.0f || f1 > 0.0f) { color_fade[3] = color[3] * f0; immAttr4fv(col_id, color_fade); immVertex3fv(pos_id, v0); color_fade[3] = color[3] * f1; immAttr4fv(col_id, color_fade); immVertex3fv(pos_id, v1); } if (f0 > 0.0f || f2 > 0.0f) { color_fade[3] = color[3] * f0; immAttr4fv(col_id, color_fade); immVertex3fv(pos_id, v0); color_fade[3] = color[3] * f2; immAttr4fv(col_id, color_fade); immVertex3fv(pos_id, v2); } #undef FADE i++; } } MEM_freeN(coords); immEnd(); immUnbindProgram(); GPU_line_smooth(false); GPU_blend(GPU_BLEND_NONE); } /* -------------------------------------------------------------------- */ /** \name Preview Plane Cursor * \{ */ struct PlacementCursor { /** * Back-pointer to the gizmo-group that uses this cursor. * Needed so we know that the cursor belongs to the region. */ wmGizmoGroup *gzgroup; /** * Enable this while the modal operator is running, * so the preview-plane doesn't show at the same time as add-object preview shape * since it's distracting & not helpful. */ bool do_draw; void *paintcursor; int plane_axis; float matrix[4][4]; /* Check if we need to re-calculate the plane matrix. */ int mval_prev[2]; float persmat_prev[4][4]; }; static void cursor_plane_draw(bContext *C, int x, int y, void *customdata) { struct PlacementCursor *plc = (struct PlacementCursor *)customdata; ARegion *region = CTX_wm_region(C); const RegionView3D *rv3d = region->regiondata; /* Early exit. * Note that we can't do most of these checks in the poll function (besides global checks) * so test them here instead. * * This cursor is only active while the gizmo is being used * so it's not so important to have a poll function. */ if (plc->do_draw == false) { return; } if (G.moving & (G_TRANSFORM_OBJ | G_TRANSFORM_EDIT)) { return; } if (rv3d->rflag & RV3D_NAVIGATING) { return; } /* Check this gizmo group is in the region. */ { wmGizmoMap *gzmap = region->gizmo_map; wmGizmoGroup *gzgroup_test = WM_gizmomap_group_find_ptr(gzmap, plc->gzgroup->type); if (gzgroup_test != plc->gzgroup) { /* Wrong viewport. */ return; } } const int mval[2] = {x - region->winrct.xmin, y - region->winrct.ymin}; /* Update matrix? */ if ((plc->mval_prev[0] != mval[0]) || (plc->mval_prev[1] != mval[1]) || !equals_m4m4(plc->persmat_prev, rv3d->persmat)) { plc->mval_prev[0] = mval[0]; plc->mval_prev[1] = mval[1]; float orient_matrix[3][3]; float co[3]; gizmo_plane_update_cursor(C, region, mval, co, orient_matrix, &plc->plane_axis); copy_m4_m3(plc->matrix, orient_matrix); copy_v3_v3(plc->matrix[3], co); copy_m4_m4(plc->persmat_prev, rv3d->persmat); } /* Draw */ float pixel_size; if (rv3d->is_persp) { float center[3]; negate_v3_v3(center, rv3d->ofs); pixel_size = ED_view3d_pixel_size(rv3d, center); } else { pixel_size = ED_view3d_pixel_size(rv3d, plc->matrix[3]); } if (pixel_size > FLT_EPSILON) { /* Arbitrary, 1.0 is a little too strong though. */ float color_alpha = 0.75f; if (rv3d->is_persp) { /* Scale down the alpha when this is drawn very small, * since the add shader causes the small size to show too dense & bright. */ const float relative_pixel_scale = pixel_size / ED_view3d_pixel_size(rv3d, plc->matrix[3]); if (relative_pixel_scale < 1.0f) { color_alpha *= max_ff(square_f(relative_pixel_scale), 0.3f); } } { /* Extra adjustment when it's near view-aligned as it seems overly bright. */ float view_vector[3]; ED_view3d_global_to_vector(rv3d, plc->matrix[3], view_vector); float view_dot = fabsf(dot_v3v3(plc->matrix[plc->plane_axis], view_vector)); color_alpha *= max_ff(0.3f, 1.0f - square_f(square_f(1.0f - view_dot))); } /* Setup viewport & matrix. */ wmViewport(®ion->winrct); GPU_matrix_push_projection(); GPU_matrix_push(); GPU_matrix_projection_set(rv3d->winmat); GPU_matrix_set(rv3d->viewmat); const float scale_mod = U.gizmo_size * 2 * U.dpi_fac / U.pixelsize; float final_scale = (scale_mod * pixel_size); const int lines_subdiv = 10; int lines = lines_subdiv; float final_scale_fade = final_scale; final_scale = ceil_power_of_10(final_scale); float fac = final_scale_fade / final_scale; float color[4] = {1, 1, 1, color_alpha}; color[3] *= square_f(1.0f - fac); if (color[3] > 0.0f) { gizmo_plane_draw_grid(lines * lines_subdiv, final_scale, final_scale_fade, plc->matrix, plc->plane_axis, color); } color[3] = color_alpha; /* When the grid is large, we only need the 2x lines in the middle. */ if (fac < 0.2f) { lines = 1; final_scale = final_scale_fade; } gizmo_plane_draw_grid( lines, final_scale, final_scale_fade, plc->matrix, plc->plane_axis, color); /* Restore matrix. */ GPU_matrix_pop(); GPU_matrix_pop_projection(); } } static void preview_plane_cursor_free(void *customdata) { struct PlacementCursor *plc = customdata; /* The window manager is freed first on exit. */ wmWindowManager *wm = G_MAIN->wm.first; if (UNLIKELY(wm != NULL)) { WM_paint_cursor_end(plc->paintcursor); } MEM_freeN(plc); } static void preview_plane_cursor_setup(wmGizmoGroup *gzgroup) { BLI_assert(gzgroup->customdata == NULL); struct PlacementCursor *plc = MEM_callocN(sizeof(*plc), __func__); plc->gzgroup = gzgroup; plc->paintcursor = WM_paint_cursor_activate( SPACE_VIEW3D, RGN_TYPE_WINDOW, NULL, cursor_plane_draw, plc); gzgroup->customdata = plc; gzgroup->customdata_free = preview_plane_cursor_free; preview_plane_cursor_visible_set(gzgroup, true); } static void preview_plane_cursor_visible_set(wmGizmoGroup *gzgroup, bool do_draw) { struct PlacementCursor *plc = gzgroup->customdata; plc->do_draw = do_draw; } /** \} */