tornavis/source/blender/editors/transform/transform_gizmo_3d.cc

2470 lines
78 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edtransform
*
* \name 3D Transform Gizmo
*
* Used for 3D View
*/
#include "BLI_function_ref.hh"
#include "DNA_armature_types.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_lattice_types.h"
#include "DNA_meta_types.h"
#include "BKE_armature.h"
#include "BKE_context.h"
#include "BKE_crazyspace.hh"
#include "BKE_curve.h"
#include "BKE_editmesh.h"
#include "BKE_global.h"
#include "BKE_gpencil_legacy.h"
#include "BKE_layer.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_pointcache.h"
#include "BKE_scene.h"
#include "BLI_array_utils.h"
#include "WM_api.h"
#include "WM_message.h"
#include "ED_armature.h"
#include "ED_curves.h"
#include "ED_gizmo_library.h"
#include "ED_gizmo_utils.h"
#include "ED_gpencil_legacy.h"
#include "ED_object.h"
#include "ED_particle.h"
#include "ED_screen.h"
#include "UI_resources.h"
#include "RNA_access.h"
#include "RNA_define.h"
/* local module include */
#include "transform.h"
#include "transform_convert.h"
#include "transform_gizmo.h"
#include "transform_snap.h"
static wmGizmoGroupType *g_GGT_xform_gizmo = nullptr;
static wmGizmoGroupType *g_GGT_xform_gizmo_context = nullptr;
static void gizmogroup_refresh_from_matrix(wmGizmoGroup *gzgroup,
const float twmat[4][4],
const float scale[3],
const bool ignore_hidden);
/* return codes for select, and drawing flags */
#define MAN_TRANS_X (1 << 0)
#define MAN_TRANS_Y (1 << 1)
#define MAN_TRANS_Z (1 << 2)
#define MAN_TRANS_C (MAN_TRANS_X | MAN_TRANS_Y | MAN_TRANS_Z)
#define MAN_ROT_X (1 << 3)
#define MAN_ROT_Y (1 << 4)
#define MAN_ROT_Z (1 << 5)
#define MAN_ROT_C (MAN_ROT_X | MAN_ROT_Y | MAN_ROT_Z)
#define MAN_SCALE_X (1 << 8)
#define MAN_SCALE_Y (1 << 9)
#define MAN_SCALE_Z (1 << 10)
#define MAN_SCALE_C (MAN_SCALE_X | MAN_SCALE_Y | MAN_SCALE_Z)
/* threshold for testing view aligned gizmo axis */
static struct {
float min, max;
} g_tw_axis_range[2] = {
/* Regular range */
{0.02f, 0.1f},
/* Use a different range because we flip the dot product,
* also the view aligned planes are harder to see so hiding early is preferred. */
{0.175f, 0.25f},
};
/* axes as index */
enum {
MAN_AXIS_TRANS_X = 0,
MAN_AXIS_TRANS_Y,
MAN_AXIS_TRANS_Z,
MAN_AXIS_TRANS_C,
MAN_AXIS_TRANS_XY,
MAN_AXIS_TRANS_YZ,
MAN_AXIS_TRANS_ZX,
#define MAN_AXIS_RANGE_TRANS_START MAN_AXIS_TRANS_X
#define MAN_AXIS_RANGE_TRANS_END (MAN_AXIS_TRANS_ZX + 1)
MAN_AXIS_ROT_X,
MAN_AXIS_ROT_Y,
MAN_AXIS_ROT_Z,
MAN_AXIS_ROT_C,
MAN_AXIS_ROT_T, /* trackball rotation */
#define MAN_AXIS_RANGE_ROT_START MAN_AXIS_ROT_X
#define MAN_AXIS_RANGE_ROT_END (MAN_AXIS_ROT_T + 1)
MAN_AXIS_SCALE_X,
MAN_AXIS_SCALE_Y,
MAN_AXIS_SCALE_Z,
MAN_AXIS_SCALE_C,
MAN_AXIS_SCALE_XY,
MAN_AXIS_SCALE_YZ,
MAN_AXIS_SCALE_ZX,
#define MAN_AXIS_RANGE_SCALE_START MAN_AXIS_SCALE_X
#define MAN_AXIS_RANGE_SCALE_END (MAN_AXIS_SCALE_ZX + 1)
MAN_AXIS_LAST = MAN_AXIS_SCALE_ZX + 1,
};
/* axis types */
enum {
MAN_AXES_ALL = 0,
MAN_AXES_TRANSLATE,
MAN_AXES_ROTATE,
MAN_AXES_SCALE,
};
struct GizmoGroup {
bool all_hidden;
int twtype;
/* Users may change the twtype, detect changes to re-setup gizmo options. */
int twtype_init;
int twtype_prev;
int use_twtype_refresh;
/* Only for view orientation. */
struct {
float viewinv_m3[3][3];
} prev;
/* Only for Rotate operator. */
float rotation;
wmGizmo *gizmos[MAN_AXIS_LAST];
};
/* -------------------------------------------------------------------- */
/** \name Utilities
* \{ */
/* loop over axes */
#define MAN_ITER_AXES_BEGIN(axis, axis_idx) \
{ \
wmGizmo *axis; \
int axis_idx; \
for (axis_idx = 0; axis_idx < MAN_AXIS_LAST; axis_idx++) { \
axis = gizmo_get_axis_from_index(ggd, axis_idx);
#define MAN_ITER_AXES_END \
} \
} \
((void)0)
static wmGizmo *gizmo_get_axis_from_index(const GizmoGroup *ggd, const short axis_idx)
{
BLI_assert(IN_RANGE_INCL(axis_idx, float(MAN_AXIS_TRANS_X), float(MAN_AXIS_LAST)));
return ggd->gizmos[axis_idx];
}
static short gizmo_get_axis_type(const int axis_idx)
{
if (axis_idx >= MAN_AXIS_RANGE_TRANS_START && axis_idx < MAN_AXIS_RANGE_TRANS_END) {
return MAN_AXES_TRANSLATE;
}
if (axis_idx >= MAN_AXIS_RANGE_ROT_START && axis_idx < MAN_AXIS_RANGE_ROT_END) {
return MAN_AXES_ROTATE;
}
if (axis_idx >= MAN_AXIS_RANGE_SCALE_START && axis_idx < MAN_AXIS_RANGE_SCALE_END) {
return MAN_AXES_SCALE;
}
BLI_assert(0);
return -1;
}
static uint gizmo_orientation_axis(const int axis_idx, bool *r_is_plane)
{
switch (axis_idx) {
case MAN_AXIS_TRANS_YZ:
case MAN_AXIS_SCALE_YZ:
if (r_is_plane) {
*r_is_plane = true;
}
ATTR_FALLTHROUGH;
case MAN_AXIS_TRANS_X:
case MAN_AXIS_ROT_X:
case MAN_AXIS_SCALE_X:
return 0;
case MAN_AXIS_TRANS_ZX:
case MAN_AXIS_SCALE_ZX:
if (r_is_plane) {
*r_is_plane = true;
}
ATTR_FALLTHROUGH;
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_ROT_Y:
case MAN_AXIS_SCALE_Y:
return 1;
case MAN_AXIS_TRANS_XY:
case MAN_AXIS_SCALE_XY:
if (r_is_plane) {
*r_is_plane = true;
}
ATTR_FALLTHROUGH;
case MAN_AXIS_TRANS_Z:
case MAN_AXIS_ROT_Z:
case MAN_AXIS_SCALE_Z:
return 2;
}
return 3;
}
static bool gizmo_is_axis_visible(const RegionView3D *rv3d,
const int twtype,
const float idot[3],
const int axis_type,
const int axis_idx)
{
if ((axis_idx >= MAN_AXIS_RANGE_ROT_START && axis_idx < MAN_AXIS_RANGE_ROT_END) == 0) {
bool is_plane = false;
const uint aidx_norm = gizmo_orientation_axis(axis_idx, &is_plane);
/* don't draw axis perpendicular to the view */
if (aidx_norm < 3) {
float idot_axis = idot[aidx_norm];
if (is_plane) {
idot_axis = 1.0f - idot_axis;
}
if (idot_axis < g_tw_axis_range[is_plane].min) {
return false;
}
}
}
if ((axis_type == MAN_AXES_TRANSLATE && !(twtype & V3D_GIZMO_SHOW_OBJECT_TRANSLATE)) ||
(axis_type == MAN_AXES_ROTATE && !(twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE)) ||
(axis_type == MAN_AXES_SCALE && !(twtype & V3D_GIZMO_SHOW_OBJECT_SCALE)))
{
return false;
}
switch (axis_idx) {
case MAN_AXIS_TRANS_X:
return (rv3d->twdrawflag & MAN_TRANS_X);
case MAN_AXIS_TRANS_Y:
return (rv3d->twdrawflag & MAN_TRANS_Y);
case MAN_AXIS_TRANS_Z:
return (rv3d->twdrawflag & MAN_TRANS_Z);
case MAN_AXIS_TRANS_C:
return (rv3d->twdrawflag & MAN_TRANS_C);
case MAN_AXIS_ROT_X:
return (rv3d->twdrawflag & MAN_ROT_X);
case MAN_AXIS_ROT_Y:
return (rv3d->twdrawflag & MAN_ROT_Y);
case MAN_AXIS_ROT_Z:
return (rv3d->twdrawflag & MAN_ROT_Z);
case MAN_AXIS_ROT_C:
case MAN_AXIS_ROT_T:
return (rv3d->twdrawflag & MAN_ROT_C);
case MAN_AXIS_SCALE_X:
return (rv3d->twdrawflag & MAN_SCALE_X);
case MAN_AXIS_SCALE_Y:
return (rv3d->twdrawflag & MAN_SCALE_Y);
case MAN_AXIS_SCALE_Z:
return (rv3d->twdrawflag & MAN_SCALE_Z);
case MAN_AXIS_SCALE_C:
return (rv3d->twdrawflag & MAN_SCALE_C && (twtype & V3D_GIZMO_SHOW_OBJECT_TRANSLATE) == 0);
case MAN_AXIS_TRANS_XY:
return (rv3d->twdrawflag & MAN_TRANS_X && rv3d->twdrawflag & MAN_TRANS_Y &&
(twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE) == 0);
case MAN_AXIS_TRANS_YZ:
return (rv3d->twdrawflag & MAN_TRANS_Y && rv3d->twdrawflag & MAN_TRANS_Z &&
(twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE) == 0);
case MAN_AXIS_TRANS_ZX:
return (rv3d->twdrawflag & MAN_TRANS_Z && rv3d->twdrawflag & MAN_TRANS_X &&
(twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE) == 0);
case MAN_AXIS_SCALE_XY:
return (rv3d->twdrawflag & MAN_SCALE_X && rv3d->twdrawflag & MAN_SCALE_Y &&
(twtype & V3D_GIZMO_SHOW_OBJECT_TRANSLATE) == 0 &&
(twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE) == 0);
case MAN_AXIS_SCALE_YZ:
return (rv3d->twdrawflag & MAN_SCALE_Y && rv3d->twdrawflag & MAN_SCALE_Z &&
(twtype & V3D_GIZMO_SHOW_OBJECT_TRANSLATE) == 0 &&
(twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE) == 0);
case MAN_AXIS_SCALE_ZX:
return (rv3d->twdrawflag & MAN_SCALE_Z && rv3d->twdrawflag & MAN_SCALE_X &&
(twtype & V3D_GIZMO_SHOW_OBJECT_TRANSLATE) == 0 &&
(twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE) == 0);
}
return false;
}
static void gizmo_get_axis_color(const int axis_idx,
const float idot[3],
float r_col[4],
float r_col_hi[4])
{
/* alpha values for normal/highlighted states */
const float alpha = 0.6f;
const float alpha_hi = 1.0f;
float alpha_fac;
if (axis_idx >= MAN_AXIS_RANGE_ROT_START && axis_idx < MAN_AXIS_RANGE_ROT_END) {
/* Never fade rotation rings. */
/* trackball rotation axis is a special case, we only draw a slight overlay */
alpha_fac = (axis_idx == MAN_AXIS_ROT_T) ? 0.05f : 1.0f;
}
else {
bool is_plane = false;
const int axis_idx_norm = gizmo_orientation_axis(axis_idx, &is_plane);
/* Get alpha fac based on axis angle,
* to fade axis out when hiding it because it points towards view. */
if (axis_idx_norm < 3) {
const float idot_min = g_tw_axis_range[is_plane].min;
const float idot_max = g_tw_axis_range[is_plane].max;
float idot_axis = idot[axis_idx_norm];
if (is_plane) {
idot_axis = 1.0f - idot_axis;
}
alpha_fac = ((idot_axis > idot_max) ? 1.0f :
(idot_axis < idot_min) ? 0.0f :
((idot_axis - idot_min) / (idot_max - idot_min)));
}
else {
alpha_fac = 1.0f;
}
}
switch (axis_idx) {
case MAN_AXIS_TRANS_X:
case MAN_AXIS_ROT_X:
case MAN_AXIS_SCALE_X:
case MAN_AXIS_TRANS_YZ:
case MAN_AXIS_SCALE_YZ:
UI_GetThemeColor4fv(TH_AXIS_X, r_col);
break;
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_ROT_Y:
case MAN_AXIS_SCALE_Y:
case MAN_AXIS_TRANS_ZX:
case MAN_AXIS_SCALE_ZX:
UI_GetThemeColor4fv(TH_AXIS_Y, r_col);
break;
case MAN_AXIS_TRANS_Z:
case MAN_AXIS_ROT_Z:
case MAN_AXIS_SCALE_Z:
case MAN_AXIS_TRANS_XY:
case MAN_AXIS_SCALE_XY:
UI_GetThemeColor4fv(TH_AXIS_Z, r_col);
break;
case MAN_AXIS_TRANS_C:
case MAN_AXIS_ROT_C:
case MAN_AXIS_SCALE_C:
case MAN_AXIS_ROT_T:
UI_GetThemeColor4fv(TH_GIZMO_VIEW_ALIGN, r_col);
break;
}
copy_v4_v4(r_col_hi, r_col);
r_col[3] = alpha * alpha_fac;
r_col_hi[3] = alpha_hi * alpha_fac;
}
static void gizmo_get_axis_constraint(const int axis_idx, bool r_axis[3])
{
ARRAY_SET_ITEMS(r_axis, 0, 0, 0);
switch (axis_idx) {
case MAN_AXIS_TRANS_X:
case MAN_AXIS_ROT_X:
case MAN_AXIS_SCALE_X:
r_axis[0] = 1;
break;
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_ROT_Y:
case MAN_AXIS_SCALE_Y:
r_axis[1] = 1;
break;
case MAN_AXIS_TRANS_Z:
case MAN_AXIS_ROT_Z:
case MAN_AXIS_SCALE_Z:
r_axis[2] = 1;
break;
case MAN_AXIS_TRANS_XY:
case MAN_AXIS_SCALE_XY:
r_axis[0] = r_axis[1] = 1;
break;
case MAN_AXIS_TRANS_YZ:
case MAN_AXIS_SCALE_YZ:
r_axis[1] = r_axis[2] = 1;
break;
case MAN_AXIS_TRANS_ZX:
case MAN_AXIS_SCALE_ZX:
r_axis[2] = r_axis[0] = 1;
break;
default:
break;
}
}
/* **************** Preparation Stuff **************** */
static void reset_tw_center(TransformBounds *tbounds)
{
INIT_MINMAX(tbounds->min, tbounds->max);
zero_v3(tbounds->center);
for (int i = 0; i < 3; i++) {
tbounds->axis_min[i] = +FLT_MAX;
tbounds->axis_max[i] = -FLT_MAX;
}
}
/* transform widget center calc helper for below */
static void calc_tw_center(TransformBounds *tbounds, const float co[3])
{
minmax_v3v3_v3(tbounds->min, tbounds->max, co);
add_v3_v3(tbounds->center, co);
for (int i = 0; i < 3; i++) {
const float d = dot_v3v3(tbounds->axis[i], co);
tbounds->axis_min[i] = min_ff(d, tbounds->axis_min[i]);
tbounds->axis_max[i] = max_ff(d, tbounds->axis_max[i]);
}
}
static void protectflag_to_drawflags(short protectflag, short *drawflags)
{
if (protectflag & OB_LOCK_LOCX) {
*drawflags &= ~MAN_TRANS_X;
}
if (protectflag & OB_LOCK_LOCY) {
*drawflags &= ~MAN_TRANS_Y;
}
if (protectflag & OB_LOCK_LOCZ) {
*drawflags &= ~MAN_TRANS_Z;
}
if (protectflag & OB_LOCK_ROTX) {
*drawflags &= ~MAN_ROT_X;
}
if (protectflag & OB_LOCK_ROTY) {
*drawflags &= ~MAN_ROT_Y;
}
if (protectflag & OB_LOCK_ROTZ) {
*drawflags &= ~MAN_ROT_Z;
}
if (protectflag & OB_LOCK_SCALEX) {
*drawflags &= ~MAN_SCALE_X;
}
if (protectflag & OB_LOCK_SCALEY) {
*drawflags &= ~MAN_SCALE_Y;
}
if (protectflag & OB_LOCK_SCALEZ) {
*drawflags &= ~MAN_SCALE_Z;
}
}
/* Similar to #transform_object_deform_pose_armature_get but does not check visibility. */
static Object *gizmo_3d_transform_space_object_get(Scene *scene, ViewLayer *view_layer)
{
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob = BKE_view_layer_active_object_get(view_layer);
if (ob && ob->mode & OB_MODE_WEIGHT_PAINT) {
/* It is assumed that when the object is in Weight Paint mode, it is not in Edit mode. So we
* don't need to check the #OB_MODE_EDIT flag. */
BLI_assert(!(ob->mode & OB_MODE_EDIT));
Object *obpose = BKE_object_pose_armature_get(ob);
if (obpose != nullptr) {
ob = obpose;
}
}
return ob;
}
/**
* Run \a user_fn for each coordinate of elements selected in View3D (vertices, particles...).
* \note Each coordinate has the space matrix of the active object.
*
* \param orient_index: A #TransformOrientationSlot.type. Here used for calculating \a r_drawflags.
* \param use_curve_handles: If true, the handles of curves are traversed.
* \param use_only_center: For objects in object mode, defines whether the corners of the bounds or
* just the center are traversed.
* \param user_fn: Callback that runs on each coordinate.
* \param r_mat: Returns the space matrix of the coordinates.
* \param r_drawflags: Drawing flags for gizmos. Usually stored in #RegionView3D::drawflags.
*/
static int gizmo_3d_foreach_selected(const bContext *C,
const short orient_index,
const bool use_curve_handles,
const bool use_only_center,
blender::FunctionRef<void(const blender::float3 &)> user_fn,
const float (**r_mat)[4],
short *r_drawflags)
{
using namespace blender;
const auto run_coord_with_matrix =
[&](const float co[3], const bool use_matrix, const float matrix[4][4]) {
float co_world[3];
if (use_matrix) {
mul_v3_m4v3(co_world, matrix, co);
co = co_world;
}
user_fn(co);
};
ScrArea *area = CTX_wm_area(C);
Scene *scene = CTX_data_scene(C);
/* TODO(sergey): This function is used from operator's modal() and from gizmo's refresh().
* Is it fine to possibly evaluate dependency graph here? */
Depsgraph *depsgraph = CTX_data_expect_evaluated_depsgraph(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
View3D *v3d = static_cast<View3D *>(area->spacedata.first);
Base *base;
bGPdata *gpd = CTX_data_gpencil_data(C);
const bool is_gp_edit = GPENCIL_ANY_MODE(gpd);
const bool is_curve_edit = GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd);
int a, totsel = 0;
Object *ob = gizmo_3d_transform_space_object_get(scene, view_layer);
if (is_gp_edit) {
float diff_mat[4][4];
const bool use_mat_local = true;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
/* only editable and visible layers are considered */
if (BKE_gpencil_layer_is_editable(gpl) && (gpl->actframe != nullptr)) {
/* calculate difference matrix */
BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat);
LISTBASE_FOREACH (bGPDstroke *, gps, &gpl->actframe->strokes) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if (is_curve_edit) {
if (gps->editcurve == nullptr) {
continue;
}
bGPDcurve *gpc = gps->editcurve;
if (gpc->flag & GP_CURVE_SELECT) {
for (uint32_t i = 0; i < gpc->tot_curve_points; i++) {
bGPDcurve_point *gpc_pt = &gpc->curve_points[i];
BezTriple *bezt = &gpc_pt->bezt;
if (gpc_pt->flag & GP_CURVE_POINT_SELECT) {
for (uint32_t j = 0; j < 3; j++) {
if (BEZT_ISSEL_IDX(bezt, j)) {
run_coord_with_matrix(bezt->vec[j], use_mat_local, diff_mat);
totsel++;
}
}
}
}
}
}
else {
/* we're only interested in selected points here... */
if (gps->flag & GP_STROKE_SELECT) {
bGPDspoint *pt;
int i;
/* Change selection status of all points, then make the stroke match */
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if (pt->flag & GP_SPOINT_SELECT) {
run_coord_with_matrix(&pt->x, use_mat_local, diff_mat);
totsel++;
}
}
}
}
}
}
}
}
else if (Object *obedit = OBEDIT_FROM_OBACT(ob)) {
#define FOREACH_EDIT_OBJECT_BEGIN(ob_iter, use_mat_local) \
{ \
invert_m4_m4(obedit->world_to_object, obedit->object_to_world); \
uint objects_len = 0; \
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode( \
scene, view_layer, CTX_wm_view3d(C), &objects_len); \
for (uint ob_index = 0; ob_index < objects_len; ob_index++) { \
Object *ob_iter = objects[ob_index]; \
const bool use_mat_local = (ob_iter != obedit);
#define FOREACH_EDIT_OBJECT_END() \
} \
MEM_freeN(objects); \
} \
((void)0)
ob = obedit;
if (obedit->type == OB_MESH) {
FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) {
BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter);
BMesh *bm = em_iter->bm;
if (bm->totvertsel == 0) {
continue;
}
BMVert *eve;
BMIter iter;
float mat_local[4][4];
if (use_mat_local) {
mul_m4_m4m4(mat_local, obedit->world_to_object, ob_iter->object_to_world);
}
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) {
if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) {
run_coord_with_matrix(eve->co, use_mat_local, mat_local);
totsel++;
}
}
}
}
FOREACH_EDIT_OBJECT_END();
} /* end editmesh */
else if (obedit->type == OB_ARMATURE) {
FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) {
bArmature *arm = static_cast<bArmature *>(ob_iter->data);
float mat_local[4][4];
if (use_mat_local) {
mul_m4_m4m4(mat_local, obedit->world_to_object, ob_iter->object_to_world);
}
LISTBASE_FOREACH (EditBone *, ebo, arm->edbo) {
if (EBONE_VISIBLE(arm, ebo)) {
if (ebo->flag & BONE_TIPSEL) {
run_coord_with_matrix(ebo->tail, use_mat_local, mat_local);
totsel++;
}
if ((ebo->flag & BONE_ROOTSEL) &&
/* don't include same point multiple times */
((ebo->flag & BONE_CONNECTED) && (ebo->parent != nullptr) &&
(ebo->parent->flag & BONE_TIPSEL) && EBONE_VISIBLE(arm, ebo->parent)) == 0)
{
run_coord_with_matrix(ebo->head, use_mat_local, mat_local);
totsel++;
if (r_drawflags) {
if (ebo->flag & (BONE_SELECTED | BONE_ROOTSEL | BONE_TIPSEL)) {
if (ebo->flag & BONE_EDITMODE_LOCKED) {
protectflag_to_drawflags(OB_LOCK_LOC | OB_LOCK_ROT | OB_LOCK_SCALE,
r_drawflags);
}
}
}
}
}
}
}
FOREACH_EDIT_OBJECT_END();
}
else if (ELEM(obedit->type, OB_CURVES_LEGACY, OB_SURF)) {
FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) {
Curve *cu = static_cast<Curve *>(ob_iter->data);
BezTriple *bezt;
BPoint *bp;
ListBase *nurbs = BKE_curve_editNurbs_get(cu);
float mat_local[4][4];
if (use_mat_local) {
mul_m4_m4m4(mat_local, obedit->world_to_object, ob_iter->object_to_world);
}
Nurb *nu = static_cast<Nurb *>(nurbs->first);
while (nu) {
if (nu->type == CU_BEZIER) {
bezt = nu->bezt;
a = nu->pntsu;
while (a--) {
/* exceptions
* if handles are hidden then only check the center points.
* If the center knot is selected then only use this as the center point.
*/
if (v3d->overlay.handle_display == CURVE_HANDLE_NONE) {
if (bezt->f2 & SELECT) {
run_coord_with_matrix(bezt->vec[1], use_mat_local, mat_local);
totsel++;
}
}
else if (bezt->f2 & SELECT) {
run_coord_with_matrix(bezt->vec[1], use_mat_local, mat_local);
totsel++;
}
else {
if (bezt->f1 & SELECT) {
const float *co = bezt->vec[!use_curve_handles ? 1 : 0];
run_coord_with_matrix(co, use_mat_local, mat_local);
totsel++;
}
if (bezt->f3 & SELECT) {
const float *co = bezt->vec[!use_curve_handles ? 1 : 2];
run_coord_with_matrix(co, use_mat_local, mat_local);
totsel++;
}
}
bezt++;
}
}
else {
bp = nu->bp;
a = nu->pntsu * nu->pntsv;
while (a--) {
if (bp->f1 & SELECT) {
run_coord_with_matrix(bp->vec, use_mat_local, mat_local);
totsel++;
}
bp++;
}
}
nu = nu->next;
}
}
FOREACH_EDIT_OBJECT_END();
}
else if (obedit->type == OB_MBALL) {
FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) {
MetaBall *mb = (MetaBall *)ob_iter->data;
float mat_local[4][4];
if (use_mat_local) {
mul_m4_m4m4(mat_local, obedit->world_to_object, ob_iter->object_to_world);
}
LISTBASE_FOREACH (MetaElem *, ml, mb->editelems) {
if (ml->flag & SELECT) {
run_coord_with_matrix(&ml->x, use_mat_local, mat_local);
totsel++;
}
}
}
FOREACH_EDIT_OBJECT_END();
}
else if (obedit->type == OB_LATTICE) {
FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) {
Lattice *lt = ((Lattice *)ob_iter->data)->editlatt->latt;
BPoint *bp = lt->def;
a = lt->pntsu * lt->pntsv * lt->pntsw;
float mat_local[4][4];
if (use_mat_local) {
mul_m4_m4m4(mat_local, obedit->world_to_object, ob_iter->object_to_world);
}
while (a--) {
if (bp->f1 & SELECT) {
run_coord_with_matrix(bp->vec, use_mat_local, mat_local);
totsel++;
}
bp++;
}
}
FOREACH_EDIT_OBJECT_END();
}
else if (obedit->type == OB_CURVES) {
FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) {
const Curves &curves_id = *static_cast<Curves *>(ob_iter->data);
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_curves_deformation(*depsgraph, *ob);
float4x4 mat_local;
if (use_mat_local) {
mat_local = float4x4(obedit->world_to_object) * float4x4(ob_iter->object_to_world);
}
IndexMaskMemory memory;
const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, memory);
const Span<float3> positions = deformation.positions;
totsel += selected_points.size();
selected_points.foreach_index([&](const int point_i) {
run_coord_with_matrix(positions[point_i], use_mat_local, mat_local.ptr());
});
}
FOREACH_EDIT_OBJECT_END();
}
#undef FOREACH_EDIT_OBJECT_BEGIN
#undef FOREACH_EDIT_OBJECT_END
}
else if (ob && (ob->mode & OB_MODE_POSE)) {
invert_m4_m4(ob->world_to_object, ob->object_to_world);
uint objects_len = 0;
Object **objects = BKE_object_pose_array_get(scene, view_layer, v3d, &objects_len);
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
Object *ob_iter = objects[ob_index];
const bool use_mat_local = (ob_iter != ob);
/* mislead counting bones... bah. We don't know the gizmo mode, could be mixed */
const int mode = TFM_ROTATION;
transform_convert_pose_transflags_update(ob_iter, mode, V3D_AROUND_CENTER_BOUNDS);
float mat_local[4][4];
if (use_mat_local) {
mul_m4_m4m4(mat_local, ob->world_to_object, ob_iter->object_to_world);
}
/* Use channels to get stats. */
LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_iter->pose->chanbase) {
if (!(pchan->bone->flag & BONE_TRANSFORM)) {
continue;
}
run_coord_with_matrix(pchan->pose_head, use_mat_local, mat_local);
totsel++;
if (r_drawflags) {
/* Protect-flags apply to local space in pose mode, so only let them influence axis
* visibility if we show the global orientation, otherwise it's confusing. */
if (ELEM(orient_index, V3D_ORIENT_LOCAL, V3D_ORIENT_GIMBAL)) {
protectflag_to_drawflags(pchan->protectflag, r_drawflags);
}
}
}
}
MEM_freeN(objects);
}
else if (ob && (ob->mode & OB_MODE_ALL_PAINT)) {
if (ob->mode & OB_MODE_SCULPT) {
totsel = 1;
run_coord_with_matrix(ob->sculpt->pivot_pos, false, ob->object_to_world);
}
}
else if (ob && ob->mode & OB_MODE_PARTICLE_EDIT) {
PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
PTCacheEditPoint *point;
PTCacheEditKey *ek;
int k;
if (edit) {
point = edit->points;
for (a = 0; a < edit->totpoint; a++, point++) {
if (point->flag & PEP_HIDE) {
continue;
}
for (k = 0, ek = point->keys; k < point->totkey; k++, ek++) {
if (ek->flag & PEK_SELECT) {
user_fn((ek->flag & PEK_USE_WCO) ? ek->world_co : ek->co);
totsel++;
}
}
}
}
}
else {
/* we need the one selected object, if its not active */
BKE_view_layer_synced_ensure(scene, view_layer);
base = BKE_view_layer_active_base_get(view_layer);
ob = base ? base->object : nullptr;
if (base && ((base->flag & BASE_SELECTED) == 0)) {
ob = nullptr;
}
for (base = static_cast<Base *>(BKE_view_layer_object_bases_get(view_layer)->first); base;
base = base->next)
{
if (!BASE_SELECTED_EDITABLE(v3d, base)) {
continue;
}
if (ob == nullptr) {
ob = base->object;
}
/* Get the boundbox out of the evaluated object. */
const BoundBox *bb = nullptr;
if (use_only_center == false) {
bb = BKE_object_boundbox_get(base->object);
}
if (use_only_center || (bb == nullptr)) {
user_fn(base->object->object_to_world[3]);
}
else {
for (uint j = 0; j < 8; j++) {
float co[3];
mul_v3_m4v3(co, base->object->object_to_world, bb->vec[j]);
user_fn(co);
}
}
totsel++;
if (r_drawflags) {
if (orient_index == V3D_ORIENT_GLOBAL) {
/* Protect-flags apply to world space in object mode,
* so only let them influence axis visibility if we show the global orientation,
* otherwise it's confusing. */
protectflag_to_drawflags(base->object->protectflag & OB_LOCK_LOC, r_drawflags);
}
else if (ELEM(orient_index, V3D_ORIENT_LOCAL, V3D_ORIENT_GIMBAL)) {
protectflag_to_drawflags(base->object->protectflag, r_drawflags);
}
}
}
}
if (r_mat && ob) {
*r_mat = ob->object_to_world;
}
return totsel;
}
int ED_transform_calc_gizmo_stats(const bContext *C,
const TransformCalcParams *params,
TransformBounds *tbounds,
RegionView3D *rv3d)
{
ScrArea *area = CTX_wm_area(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
View3D *v3d = static_cast<View3D *>(area->spacedata.first);
int totsel = 0;
const int pivot_point = scene->toolsettings->transform_pivot_point;
const short orient_index = params->orientation_index ?
(params->orientation_index - 1) :
BKE_scene_orientation_get_index(scene, SCE_ORIENT_DEFAULT);
Object *ob = gizmo_3d_transform_space_object_get(scene, view_layer);
Object *obedit = OBEDIT_FROM_OBACT(ob);
tbounds->use_matrix_space = false;
unit_m3(tbounds->axis);
/* global, local or normal orientation?
* if we could check 'totsel' now, this should be skipped with no selection. */
if (ob) {
float mat[3][3];
ED_transform_calc_orientation_from_type_ex(
scene, view_layer, v3d, rv3d, ob, obedit, orient_index, pivot_point, mat);
copy_m3_m3(tbounds->axis, mat);
}
reset_tw_center(tbounds);
if (rv3d) {
/* transform widget centroid/center */
copy_m4_m3(rv3d->twmat, tbounds->axis);
rv3d->twdrawflag = short(0xFFFF);
}
if (params->use_local_axis && (ob && ob->mode & (OB_MODE_EDIT | OB_MODE_POSE))) {
float diff_mat[3][3];
copy_m3_m4(diff_mat, ob->object_to_world);
normalize_m3(diff_mat);
invert_m3(diff_mat);
mul_m3_m3_pre(tbounds->axis, diff_mat);
normalize_m3(tbounds->axis);
tbounds->use_matrix_space = true;
copy_m4_m4(tbounds->matrix_space, ob->object_to_world);
}
const auto gizmo_3d_tbounds_calc_fn = [&](const blender::float3 &co) {
calc_tw_center(tbounds, co);
};
totsel = gizmo_3d_foreach_selected(C,
orient_index,
(pivot_point != V3D_AROUND_LOCAL_ORIGINS),
params->use_only_center,
gizmo_3d_tbounds_calc_fn,
nullptr,
rv3d ? &rv3d->twdrawflag : nullptr);
if (totsel) {
mul_v3_fl(tbounds->center, 1.0f / float(totsel)); /* centroid! */
bGPdata *gpd = CTX_data_gpencil_data(C);
const bool is_gp_edit = GPENCIL_ANY_MODE(gpd);
if (!is_gp_edit && (obedit || (ob && (ob->mode & (OB_MODE_POSE | OB_MODE_SCULPT))))) {
if (ob->mode & OB_MODE_POSE) {
invert_m4_m4(ob->world_to_object, ob->object_to_world);
}
mul_m4_v3(ob->object_to_world, tbounds->center);
mul_m4_v3(ob->object_to_world, tbounds->min);
mul_m4_v3(ob->object_to_world, tbounds->max);
}
}
if (rv3d) {
if (totsel == 0) {
unit_m4(rv3d->twmat);
unit_m3(rv3d->tw_axis_matrix);
zero_v3(rv3d->tw_axis_min);
zero_v3(rv3d->tw_axis_max);
}
else {
copy_m3_m3(rv3d->tw_axis_matrix, tbounds->axis);
copy_v3_v3(rv3d->tw_axis_min, tbounds->axis_min);
copy_v3_v3(rv3d->tw_axis_max, tbounds->axis_max);
}
}
return totsel;
}
static void gizmo_get_idot(const RegionView3D *rv3d, float r_idot[3])
{
float view_vec[3], axis_vec[3];
ED_view3d_global_to_vector(rv3d, rv3d->twmat[3], view_vec);
for (int i = 0; i < 3; i++) {
normalize_v3_v3(axis_vec, rv3d->twmat[i]);
r_idot[i] = 1.0f - fabsf(dot_v3v3(view_vec, axis_vec));
}
}
static bool gizmo_3d_calc_pos(const bContext *C,
const Scene *scene,
const TransformBounds *tbounds,
const short pivot_type,
float r_pivot_pos[3])
{
zero_v3(r_pivot_pos);
switch (pivot_type) {
case V3D_AROUND_CURSOR:
copy_v3_v3(r_pivot_pos, scene->cursor.location);
return true;
case V3D_AROUND_ACTIVE: {
ViewLayer *view_layer = CTX_data_view_layer(C);
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob = BKE_view_layer_active_object_get(view_layer);
if (ob != nullptr) {
if ((ob->mode & OB_MODE_ALL_SCULPT) && ob->sculpt) {
SculptSession *ss = ob->sculpt;
copy_v3_v3(r_pivot_pos, ss->pivot_pos);
return true;
}
else if (ED_object_calc_active_center(ob, false, r_pivot_pos)) {
return true;
}
}
}
[[fallthrough]];
case V3D_AROUND_CENTER_BOUNDS: {
TransformBounds tbounds_stack;
if (tbounds == nullptr) {
TransformCalcParams calc_params{};
calc_params.use_only_center = true;
if (ED_transform_calc_gizmo_stats(C, &calc_params, &tbounds_stack, nullptr)) {
tbounds = &tbounds_stack;
}
}
if (tbounds) {
mid_v3_v3v3(r_pivot_pos, tbounds->min, tbounds->max);
return true;
}
break;
}
case V3D_AROUND_LOCAL_ORIGINS:
case V3D_AROUND_CENTER_MEDIAN: {
if (tbounds) {
copy_v3_v3(r_pivot_pos, tbounds->center);
return true;
}
const auto gizmo_3d_calc_center_fn = [&](const blender::float3 &co) {
add_v3_v3(r_pivot_pos, co);
};
const float(*r_mat)[4] = nullptr;
int totsel;
totsel = gizmo_3d_foreach_selected(C,
0,
(pivot_type != V3D_AROUND_LOCAL_ORIGINS),
true,
gizmo_3d_calc_center_fn,
&r_mat,
nullptr);
if (totsel) {
mul_v3_fl(r_pivot_pos, 1.0f / float(totsel));
if (r_mat) {
mul_m4_v3(r_mat, r_pivot_pos);
}
return true;
}
}
}
return false;
}
void gizmo_prepare_mat(const struct bContext *C,
struct RegionView3D *rv3d,
const struct TransformBounds *tbounds)
{
Scene *scene = CTX_data_scene(C);
gizmo_3d_calc_pos(C, scene, tbounds, scene->toolsettings->transform_pivot_point, rv3d->twmat[3]);
}
/**
* Sets up \a r_start and \a r_len to define arrow line range.
* Needed to adjust line drawing for combined gizmo axis types.
*/
static void gizmo_line_range(const int twtype, const short axis_type, float *r_start, float *r_end)
{
float start = 0.2f;
float end = 1.0f;
switch (axis_type) {
case MAN_AXES_TRANSLATE:
if (twtype & V3D_GIZMO_SHOW_OBJECT_SCALE) {
start = end - 0.125f;
}
if (twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE) {
/* Avoid rotate and translate gizmos overlap. */
const float rotate_offset = 0.215f;
start += rotate_offset;
end += rotate_offset + 0.2f;
}
break;
case MAN_AXES_SCALE:
if (twtype & (V3D_GIZMO_SHOW_OBJECT_TRANSLATE | V3D_GIZMO_SHOW_OBJECT_ROTATE)) {
end -= 0.225f;
}
break;
}
if (r_start) {
*r_start = start;
}
if (r_end) {
*r_end = end;
}
}
void gizmo_xform_message_subscribe(wmGizmoGroup *gzgroup,
wmMsgBus *mbus,
Scene *scene,
bScreen *screen,
ScrArea *area,
ARegion *region,
void (*type_fn)(wmGizmoGroupType *))
{
/* Subscribe to view properties */
wmMsgSubscribeValue msg_sub_value_gz_tag_refresh{};
msg_sub_value_gz_tag_refresh.owner = region;
msg_sub_value_gz_tag_refresh.user_data = gzgroup->parent_gzmap;
msg_sub_value_gz_tag_refresh.notify = WM_gizmo_do_msg_notify_tag_refresh;
int orient_flag = 0;
if (type_fn == VIEW3D_GGT_xform_gizmo) {
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
orient_flag = ggd->twtype_init;
}
else if (type_fn == VIEW3D_GGT_xform_cage) {
orient_flag = V3D_GIZMO_SHOW_OBJECT_SCALE;
/* pass */
}
else if (type_fn == VIEW3D_GGT_xform_shear) {
orient_flag = V3D_GIZMO_SHOW_OBJECT_ROTATE;
}
TransformOrientationSlot *orient_slot = BKE_scene_orientation_slot_get_from_flag(scene,
orient_flag);
PointerRNA orient_ref_ptr;
RNA_pointer_create(&scene->id, &RNA_TransformOrientationSlot, orient_slot, &orient_ref_ptr);
const ToolSettings *ts = scene->toolsettings;
PointerRNA scene_ptr;
RNA_id_pointer_create(&scene->id, &scene_ptr);
{
const PropertyRNA *props[] = {
&rna_Scene_transform_orientation_slots,
};
for (int i = 0; i < ARRAY_SIZE(props); i++) {
WM_msg_subscribe_rna(mbus, &scene_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
}
}
if ((ts->transform_pivot_point == V3D_AROUND_CURSOR) || (orient_slot->type == V3D_ORIENT_CURSOR))
{
/* We could be more specific here, for now subscribe to any cursor change. */
PointerRNA cursor_ptr;
RNA_pointer_create(&scene->id, &RNA_View3DCursor, &scene->cursor, &cursor_ptr);
WM_msg_subscribe_rna(mbus, &cursor_ptr, nullptr, &msg_sub_value_gz_tag_refresh, __func__);
}
{
const PropertyRNA *props[] = {
&rna_TransformOrientationSlot_type,
&rna_TransformOrientationSlot_use,
};
for (int i = 0; i < ARRAY_SIZE(props); i++) {
if (props[i]) {
WM_msg_subscribe_rna(
mbus, &orient_ref_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
}
}
}
PointerRNA toolsettings_ptr;
RNA_pointer_create(&scene->id, &RNA_ToolSettings, scene->toolsettings, &toolsettings_ptr);
if (ELEM(type_fn, VIEW3D_GGT_xform_gizmo, VIEW3D_GGT_xform_shear)) {
const PropertyRNA *props[] = {
&rna_ToolSettings_transform_pivot_point,
};
for (int i = 0; i < ARRAY_SIZE(props); i++) {
WM_msg_subscribe_rna(
mbus, &toolsettings_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
}
}
{
const PropertyRNA *props[] = {
&rna_ToolSettings_workspace_tool_type,
};
for (int i = 0; i < ARRAY_SIZE(props); i++) {
WM_msg_subscribe_rna(
mbus, &toolsettings_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
}
}
PointerRNA view3d_ptr;
RNA_pointer_create(&screen->id, &RNA_SpaceView3D, area->spacedata.first, &view3d_ptr);
if (type_fn == VIEW3D_GGT_xform_gizmo) {
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
if (ggd->use_twtype_refresh) {
const PropertyRNA *props[] = {
&rna_SpaceView3D_show_gizmo_object_translate,
&rna_SpaceView3D_show_gizmo_object_rotate,
&rna_SpaceView3D_show_gizmo_object_scale,
};
for (int i = 0; i < ARRAY_SIZE(props); i++) {
WM_msg_subscribe_rna(mbus, &view3d_ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__);
}
}
}
else if (type_fn == VIEW3D_GGT_xform_cage) {
/* pass */
}
else if (type_fn == VIEW3D_GGT_xform_shear) {
/* pass */
}
else {
BLI_assert(0);
}
WM_msg_subscribe_rna_anon_prop(mbus, Window, view_layer, &msg_sub_value_gz_tag_refresh);
WM_msg_subscribe_rna_anon_prop(mbus, EditBone, lock, &msg_sub_value_gz_tag_refresh);
}
static void gizmo_3d_dial_matrixbasis_calc(const ARegion *region,
const float axis[3],
const float center_global[3],
const float mval_init[2],
float r_mat_basis[4][4])
{
plane_from_point_normal_v3(r_mat_basis[2], center_global, axis);
copy_v3_v3(r_mat_basis[3], center_global);
if (ED_view3d_win_to_3d_on_plane(region, r_mat_basis[2], mval_init, false, r_mat_basis[1])) {
sub_v3_v3(r_mat_basis[1], center_global);
normalize_v3(r_mat_basis[1]);
cross_v3_v3v3(r_mat_basis[0], r_mat_basis[1], r_mat_basis[2]);
}
else {
/* The plane and the mouse direction are parallel.
* Calculate a matrix orthogonal to the axis. */
ortho_basis_v3v3_v3(r_mat_basis[0], r_mat_basis[1], r_mat_basis[2]);
}
r_mat_basis[0][3] = 0.0f;
r_mat_basis[1][3] = 0.0f;
r_mat_basis[2][3] = 0.0f;
r_mat_basis[3][3] = 1.0f;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform Gizmo
* \{ */
/** Scale of the two-axis planes. */
#define MAN_AXIS_SCALE_PLANE_SCALE 0.7f
static void rotation_get_fn(const wmGizmo * /*gz*/, wmGizmoProperty *gz_prop, void *value)
{
const GizmoGroup *ggd = (const GizmoGroup *)gz_prop->custom_func.user_data;
*(float *)value = ggd->rotation;
}
static void rotation_set_fn(const wmGizmo * /*gz*/, wmGizmoProperty *gz_prop, const void *value)
{
GizmoGroup *ggd = (GizmoGroup *)gz_prop->custom_func.user_data;
ggd->rotation = *(const float *)value;
}
static void gizmo_3d_setup_default_matrix(wmGizmo *axis, const int axis_idx)
{
float matrix[3][3];
switch (axis_idx) {
/* Arrow. */
case MAN_AXIS_TRANS_X:
case MAN_AXIS_SCALE_X:
case MAN_AXIS_ROT_X:
copy_v3_fl3(matrix[0], 0.0f, -1.0f, 0.0f);
copy_v3_fl3(matrix[1], 0.0f, 0.0f, -1.0f);
copy_v3_fl3(matrix[2], 1.0f, 0.0f, 0.0f);
break;
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_SCALE_Y:
case MAN_AXIS_ROT_Y:
copy_v3_fl3(matrix[0], 1.0f, 0.0f, 0.0f);
copy_v3_fl3(matrix[1], 0.0f, 0.0f, -1.0f);
copy_v3_fl3(matrix[2], 0.0f, 1.0f, 0.0f);
break;
case MAN_AXIS_TRANS_Z:
case MAN_AXIS_SCALE_Z:
case MAN_AXIS_ROT_Z:
copy_v3_fl3(matrix[0], 1.0f, 0.0f, 0.0f);
copy_v3_fl3(matrix[1], 0.0f, 1.0f, 0.0f);
copy_v3_fl3(matrix[2], 0.0f, 0.0f, 1.0f);
break;
case MAN_AXIS_TRANS_XY:
case MAN_AXIS_SCALE_XY:
copy_v3_fl3(matrix[0], -M_SQRT1_2, M_SQRT1_2, 0.0f);
copy_v3_fl3(matrix[1], 0.0f, 0.0f, 1.0f);
copy_v3_fl3(matrix[2], M_SQRT1_2, M_SQRT1_2, 0.0f);
break;
case MAN_AXIS_TRANS_YZ:
case MAN_AXIS_SCALE_YZ:
copy_v3_fl3(matrix[0], 0.0f, -M_SQRT1_2, M_SQRT1_2);
copy_v3_fl3(matrix[1], 1.0f, 0.0f, 0.0f);
copy_v3_fl3(matrix[2], 0, M_SQRT1_2, M_SQRT1_2);
break;
case MAN_AXIS_SCALE_ZX:
case MAN_AXIS_TRANS_ZX:
copy_v3_fl3(matrix[0], M_SQRT1_2, 0.0f, -M_SQRT1_2);
copy_v3_fl3(matrix[1], 0.0f, 1.0f, 0.0f);
copy_v3_fl3(matrix[2], M_SQRT1_2, 0.0f, M_SQRT1_2);
break;
case MAN_AXIS_TRANS_C:
case MAN_AXIS_SCALE_C:
case MAN_AXIS_ROT_C:
case MAN_AXIS_ROT_T:
default:
return;
}
copy_m4_m3(axis->matrix_offset, matrix);
}
static void gizmo_3d_setup_draw_default(wmGizmo *axis, const int axis_idx)
{
gizmo_3d_setup_default_matrix(axis, axis_idx);
switch (axis_idx) {
/* Arrow. */
case MAN_AXIS_TRANS_X:
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_TRANS_Z:
RNA_enum_set(axis->ptr, "draw_style", ED_GIZMO_ARROW_STYLE_NORMAL);
break;
case MAN_AXIS_SCALE_X:
case MAN_AXIS_SCALE_Y:
case MAN_AXIS_SCALE_Z:
RNA_enum_set(axis->ptr, "draw_style", ED_GIZMO_ARROW_STYLE_BOX);
RNA_enum_set(axis->ptr, "draw_options", ED_GIZMO_ARROW_DRAW_FLAG_STEM);
break;
case MAN_AXIS_TRANS_XY:
case MAN_AXIS_TRANS_YZ:
case MAN_AXIS_TRANS_ZX:
case MAN_AXIS_SCALE_XY:
case MAN_AXIS_SCALE_YZ:
case MAN_AXIS_SCALE_ZX:
RNA_enum_set(axis->ptr, "draw_style", ED_GIZMO_ARROW_STYLE_PLANE);
RNA_enum_set(axis->ptr, "draw_options", 0);
RNA_float_set(axis->ptr, "length", MAN_AXIS_SCALE_PLANE_SCALE);
break;
/* Primitive. */
case MAN_AXIS_TRANS_C:
RNA_enum_set(axis->ptr, "draw_style", ED_GIZMO_PRIMITIVE_STYLE_CIRCLE);
RNA_boolean_set(axis->ptr, "draw_inner", false);
WM_gizmo_set_scale(axis, 0.2f);
/* Prevent axis gizmos overlapping the center point, see: #63744. */
axis->select_bias = 2.0f;
break;
case MAN_AXIS_SCALE_C:
RNA_enum_set(axis->ptr, "draw_style", ED_GIZMO_PRIMITIVE_STYLE_ANNULUS);
RNA_boolean_set(axis->ptr, "draw_inner", false);
/* Use 6 since this is '1.2' if the main scale is 0.2. */
RNA_float_set(axis->ptr, "arc_inner_factor", 6.0f);
WM_gizmo_set_scale(axis, 0.2f);
/* Prevent axis gizmos overlapping the center point, see: #63744. */
axis->select_bias = -2.0f;
break;
/* Dial. */
case MAN_AXIS_ROT_X:
case MAN_AXIS_ROT_Y:
case MAN_AXIS_ROT_Z:
RNA_enum_set(axis->ptr, "draw_options", ED_GIZMO_DIAL_DRAW_FLAG_CLIP);
WM_gizmo_set_flag(axis, WM_GIZMO_DRAW_VALUE, true);
break;
case MAN_AXIS_ROT_C:
RNA_enum_set(axis->ptr, "draw_options", ED_GIZMO_DIAL_DRAW_FLAG_NOP);
WM_gizmo_set_flag(axis, WM_GIZMO_DRAW_VALUE, true);
WM_gizmo_set_scale(axis, 1.2f);
break;
case MAN_AXIS_ROT_T:
RNA_enum_set(axis->ptr, "draw_options", ED_GIZMO_DIAL_DRAW_FLAG_FILL);
WM_gizmo_set_flag(axis, WM_GIZMO_SELECT_BACKGROUND, true);
WM_gizmo_set_flag(axis, WM_GIZMO_DRAW_HOVER, true);
break;
}
switch (axis_idx) {
case MAN_AXIS_TRANS_X:
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_TRANS_Z:
case MAN_AXIS_SCALE_X:
case MAN_AXIS_SCALE_Y:
WM_gizmo_set_line_width(axis, GIZMO_AXIS_LINE_WIDTH);
break;
case MAN_AXIS_TRANS_XY:
case MAN_AXIS_TRANS_YZ:
case MAN_AXIS_TRANS_ZX:
case MAN_AXIS_SCALE_XY:
case MAN_AXIS_SCALE_YZ:
case MAN_AXIS_SCALE_ZX:
WM_gizmo_set_line_width(axis, 1.0f);
break;
case MAN_AXIS_ROT_X:
case MAN_AXIS_ROT_Y:
case MAN_AXIS_ROT_Z:
/* Increased line width for better display. */
WM_gizmo_set_line_width(axis, GIZMO_AXIS_LINE_WIDTH + 1.0f);
break;
default:
WM_gizmo_set_line_width(axis, GIZMO_AXIS_LINE_WIDTH);
break;
}
const short axis_type = gizmo_get_axis_type(axis_idx);
switch (axis_type) {
case MAN_AXES_ROTATE: {
RNA_float_set(axis->ptr, "incremental_angle", 0.0f);
axis->select_bias = 0.0f;
break;
}
default:
break;
}
}
static void gizmo_3d_setup_draw_from_twtype(wmGizmo *axis, const int axis_idx, const int twtype)
{
switch (axis_idx) {
case MAN_AXIS_TRANS_X:
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_TRANS_Z:
RNA_enum_set(axis->ptr,
"draw_options",
(twtype & (V3D_GIZMO_SHOW_OBJECT_ROTATE | V3D_GIZMO_SHOW_OBJECT_SCALE)) ?
0 :
ED_GIZMO_ARROW_DRAW_FLAG_STEM);
break;
default:
break;
}
const short axis_type = gizmo_get_axis_type(axis_idx);
switch (axis_idx) {
case MAN_AXIS_TRANS_X:
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_TRANS_Z:
case MAN_AXIS_SCALE_X:
case MAN_AXIS_SCALE_Y:
case MAN_AXIS_SCALE_Z: {
float start;
float end;
gizmo_line_range(twtype, axis_type, &start, &end);
mul_v3_v3fl(axis->matrix_offset[3], axis->matrix_offset[2], start);
RNA_float_set(axis->ptr, "length", end - start);
WM_gizmo_set_flag(axis, WM_GIZMO_DRAW_OFFSET_SCALE, true);
break;
}
default:
break;
}
switch (axis_type) {
case MAN_AXES_ROTATE: {
if ((twtype & V3D_GIZMO_SHOW_OBJECT_SCALE) && twtype & V3D_GIZMO_SHOW_OBJECT_ROTATE) {
axis->select_bias = -2.0f;
}
}
}
}
static void gizmo_3d_setup_draw_modal(wmGizmo *axis, const int axis_idx, const int twtype)
{
const short axis_type = gizmo_get_axis_type(axis_idx);
switch (axis_idx) {
case MAN_AXIS_TRANS_X:
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_TRANS_Z:
case MAN_AXIS_SCALE_X:
case MAN_AXIS_SCALE_Y:
case MAN_AXIS_SCALE_Z: {
float end, start_co[3] = {0.0f, 0.0f, 0.0f};
gizmo_line_range(twtype, axis_type, nullptr, &end);
RNA_float_set(axis->ptr, "length", end);
RNA_enum_set(axis->ptr,
"draw_options",
ED_GIZMO_ARROW_DRAW_FLAG_STEM | ED_GIZMO_ARROW_DRAW_FLAG_ORIGIN);
WM_gizmo_set_matrix_offset_location(axis, start_co);
WM_gizmo_set_flag(axis, WM_GIZMO_DRAW_OFFSET_SCALE, false);
break;
}
case MAN_AXIS_TRANS_XY:
case MAN_AXIS_TRANS_YZ:
case MAN_AXIS_TRANS_ZX:
case MAN_AXIS_SCALE_XY:
case MAN_AXIS_SCALE_YZ:
case MAN_AXIS_SCALE_ZX:
RNA_enum_set(axis->ptr, "draw_options", ED_GIZMO_ARROW_DRAW_FLAG_ORIGIN);
break;
case MAN_AXIS_SCALE_C:
RNA_enum_set(axis->ptr, "draw_style", ED_GIZMO_PRIMITIVE_STYLE_CIRCLE);
break;
default:
break;
}
switch (axis_type) {
case MAN_AXES_ROTATE: {
PropertyRNA *prop = RNA_struct_find_property(axis->ptr, "draw_options");
const int dial_flag = RNA_property_enum_get(axis->ptr, prop);
RNA_property_enum_set(axis->ptr, prop, dial_flag | ED_GIZMO_DIAL_DRAW_FLAG_ANGLE_VALUE);
break;
}
default:
break;
}
}
static GizmoGroup *gizmogroup_init(wmGizmoGroup *gzgroup)
{
GizmoGroup *ggd = MEM_cnew<GizmoGroup>(__func__);
const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_3d", true);
const wmGizmoType *gzt_dial = WM_gizmotype_find("GIZMO_GT_dial_3d", true);
const wmGizmoType *gzt_prim = WM_gizmotype_find("GIZMO_GT_primitive_3d", true);
wmGizmoPropertyFnParams params{};
params.value_get_fn = rotation_get_fn;
params.value_set_fn = rotation_set_fn;
params.user_data = ggd;
#define GIZMO_NEW_ARROW(v) \
{ \
ggd->gizmos[v] = WM_gizmo_new_ptr(gzt_arrow, gzgroup, nullptr); \
} \
((void)0)
#define GIZMO_NEW_DIAL(v) \
{ \
ggd->gizmos[v] = WM_gizmo_new_ptr(gzt_dial, gzgroup, nullptr); \
WM_gizmo_target_property_def_func(ggd->gizmos[v], "offset", &params); \
} \
((void)0)
#define GIZMO_NEW_PRIM(v) \
{ \
ggd->gizmos[v] = WM_gizmo_new_ptr(gzt_prim, gzgroup, nullptr); \
} \
((void)0)
/* add/init widgets - order matters! */
GIZMO_NEW_DIAL(MAN_AXIS_ROT_T);
GIZMO_NEW_PRIM(MAN_AXIS_SCALE_C);
GIZMO_NEW_ARROW(MAN_AXIS_SCALE_X);
GIZMO_NEW_ARROW(MAN_AXIS_SCALE_Y);
GIZMO_NEW_ARROW(MAN_AXIS_SCALE_Z);
GIZMO_NEW_ARROW(MAN_AXIS_SCALE_XY);
GIZMO_NEW_ARROW(MAN_AXIS_SCALE_YZ);
GIZMO_NEW_ARROW(MAN_AXIS_SCALE_ZX);
GIZMO_NEW_DIAL(MAN_AXIS_ROT_X);
GIZMO_NEW_DIAL(MAN_AXIS_ROT_Y);
GIZMO_NEW_DIAL(MAN_AXIS_ROT_Z);
/* init screen aligned widget last here, looks better, behaves better */
GIZMO_NEW_DIAL(MAN_AXIS_ROT_C);
GIZMO_NEW_PRIM(MAN_AXIS_TRANS_C);
GIZMO_NEW_ARROW(MAN_AXIS_TRANS_X);
GIZMO_NEW_ARROW(MAN_AXIS_TRANS_Y);
GIZMO_NEW_ARROW(MAN_AXIS_TRANS_Z);
GIZMO_NEW_ARROW(MAN_AXIS_TRANS_XY);
GIZMO_NEW_ARROW(MAN_AXIS_TRANS_YZ);
GIZMO_NEW_ARROW(MAN_AXIS_TRANS_ZX);
MAN_ITER_AXES_BEGIN (axis, axis_idx) {
gizmo_3d_setup_draw_default(axis, axis_idx);
}
MAN_ITER_AXES_END;
return ggd;
}
/**
* Custom handler for gizmo widgets
*/
static int gizmo_modal(bContext *C,
wmGizmo *widget,
const wmEvent *event,
eWM_GizmoFlagTweak /*tweak_flag*/)
{
/* Avoid unnecessary updates, partially address: #55458. */
if (ELEM(event->type, TIMER, INBETWEEN_MOUSEMOVE)) {
return OPERATOR_RUNNING_MODAL;
}
ARegion *region = CTX_wm_region(C);
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
wmGizmoGroup *gzgroup = widget->parent_gzgroup;
/* Recalculating the orientation has two problems.
* - The matrix calculated based on the transformed selection may not match the matrix
* that was set when transform started.
* - Inspecting the selection for every update is expensive (for *every* redraw).
*
* Instead, use #transform_apply_matrix to transform `rv3d->twmat` or the final scale value
* when scaling.
*/
if (false) {
TransformBounds tbounds;
TransformCalcParams calc_params{};
calc_params.use_only_center = true;
if (ED_transform_calc_gizmo_stats(C, &calc_params, &tbounds, rv3d)) {
gizmo_prepare_mat(C, rv3d, &tbounds);
for (wmGizmo *gz = static_cast<wmGizmo *>(gzgroup->gizmos.first); gz; gz = gz->next) {
WM_gizmo_set_matrix_location(gz, rv3d->twmat[3]);
}
}
}
else {
wmWindow *win = CTX_wm_window(C);
wmOperator *op = nullptr;
for (int i = 0; i < widget->op_data_len; i++) {
wmGizmoOpElem *gzop = WM_gizmo_operator_get(widget, i);
op = WM_operator_find_modal_by_type(win, gzop->type);
if (op != nullptr) {
break;
}
}
if (op != nullptr) {
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
const int axis_idx = BLI_array_findindex(ggd->gizmos, ARRAY_SIZE(ggd->gizmos), &widget);
const short axis_type = gizmo_get_axis_type(axis_idx);
float twmat[4][4];
float scale_buf[3];
float *scale = nullptr;
bool update = false;
copy_m4_m4(twmat, rv3d->twmat);
if (axis_type == MAN_AXES_SCALE) {
scale = scale_buf;
transform_final_value_get(static_cast<const TransInfo *>(op->customdata), scale, 3);
update = true;
}
else if (axis_type == MAN_AXES_ROTATE) {
transform_final_value_get(
static_cast<const TransInfo *>(op->customdata), &ggd->rotation, 1);
if (widget != ggd->gizmos[MAN_AXIS_ROT_C]) {
ggd->rotation *= -1;
}
RNA_float_set(
widget->ptr,
"incremental_angle",
transform_snap_increment_get(static_cast<const TransInfo *>(op->customdata)));
}
else if (transform_apply_matrix(static_cast<TransInfo *>(op->customdata), twmat)) {
update = true;
}
if (update) {
gizmogroup_refresh_from_matrix(gzgroup, twmat, scale, true);
ED_region_tag_redraw_editor_overlays(region);
}
}
}
return OPERATOR_RUNNING_MODAL;
}
static void gizmogroup_init_properties_from_twtype(wmGizmoGroup *gzgroup)
{
struct {
wmOperatorType *translate, *rotate, *trackball, *resize;
} ot_store = {nullptr};
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
MAN_ITER_AXES_BEGIN (axis, axis_idx) {
const short axis_type = gizmo_get_axis_type(axis_idx);
bool constraint_axis[3] = {1, 0, 0};
PointerRNA *ptr = nullptr;
gizmo_get_axis_constraint(axis_idx, constraint_axis);
/* custom handler! */
WM_gizmo_set_fn_custom_modal(axis, gizmo_modal);
gizmo_3d_setup_draw_from_twtype(axis, axis_idx, ggd->twtype);
switch (axis_type) {
case MAN_AXES_TRANSLATE:
if (ot_store.translate == nullptr) {
ot_store.translate = WM_operatortype_find("TRANSFORM_OT_translate", true);
}
ptr = WM_gizmo_operator_set(axis, 0, ot_store.translate, nullptr);
break;
case MAN_AXES_ROTATE: {
wmOperatorType *ot_rotate;
if (axis_idx == MAN_AXIS_ROT_T) {
if (ot_store.trackball == nullptr) {
ot_store.trackball = WM_operatortype_find("TRANSFORM_OT_trackball", true);
}
ot_rotate = ot_store.trackball;
}
else {
if (ot_store.rotate == nullptr) {
ot_store.rotate = WM_operatortype_find("TRANSFORM_OT_rotate", true);
}
ot_rotate = ot_store.rotate;
}
ptr = WM_gizmo_operator_set(axis, 0, ot_rotate, nullptr);
break;
}
case MAN_AXES_SCALE: {
if (ot_store.resize == nullptr) {
ot_store.resize = WM_operatortype_find("TRANSFORM_OT_resize", true);
}
ptr = WM_gizmo_operator_set(axis, 0, ot_store.resize, nullptr);
break;
}
}
if (ptr) {
PropertyRNA *prop;
if (ELEM(true, UNPACK3(constraint_axis))) {
if ((prop = RNA_struct_find_property(ptr, "constraint_axis"))) {
RNA_property_boolean_set_array(ptr, prop, constraint_axis);
}
}
RNA_boolean_set(ptr, "release_confirm", 1);
}
}
MAN_ITER_AXES_END;
}
static void WIDGETGROUP_gizmo_setup(const bContext *C, wmGizmoGroup *gzgroup)
{
GizmoGroup *ggd = gizmogroup_init(gzgroup);
gzgroup->customdata = ggd;
{
ScrArea *area = CTX_wm_area(C);
const bToolRef *tref = area->runtime.tool;
ggd->twtype = 0;
if (tref && STREQ(tref->idname, "builtin.move")) {
ggd->twtype |= V3D_GIZMO_SHOW_OBJECT_TRANSLATE;
}
else if (tref && STREQ(tref->idname, "builtin.rotate")) {
ggd->twtype |= V3D_GIZMO_SHOW_OBJECT_ROTATE;
}
else if (tref && STREQ(tref->idname, "builtin.scale")) {
ggd->twtype |= V3D_GIZMO_SHOW_OBJECT_SCALE;
}
else if (tref && STREQ(tref->idname, "builtin.transform")) {
ggd->twtype = V3D_GIZMO_SHOW_OBJECT_TRANSLATE | V3D_GIZMO_SHOW_OBJECT_ROTATE |
V3D_GIZMO_SHOW_OBJECT_SCALE;
}
else {
/* This is also correct logic for 'builtin.transform', no special check needed. */
/* Setup all gizmos, they can be toggled via 'ToolSettings.gizmo_flag' */
ggd->twtype = V3D_GIZMO_SHOW_OBJECT_TRANSLATE | V3D_GIZMO_SHOW_OBJECT_ROTATE |
V3D_GIZMO_SHOW_OBJECT_SCALE;
ggd->use_twtype_refresh = true;
}
BLI_assert(ggd->twtype != 0);
ggd->twtype_init = ggd->twtype;
}
/* *** set properties for axes *** */
gizmogroup_init_properties_from_twtype(gzgroup);
}
/**
* Set properties for axes.
*
* \param twmat: The transform matrix (typically #RegionView3D.twmat).
* \param scale: Optional scale, to show scale while modally dragging the scale handles.
*/
static void gizmo_refresh_from_matrix(wmGizmo *axis,
const int axis_idx,
const int twtype,
const float twmat[4][4],
const float scale[3])
{
const short axis_type = gizmo_get_axis_type(axis_idx);
const int aidx_norm = gizmo_orientation_axis(axis_idx, nullptr);
switch (axis_idx) {
case MAN_AXIS_TRANS_XY:
case MAN_AXIS_TRANS_YZ:
case MAN_AXIS_TRANS_ZX:
case MAN_AXIS_SCALE_XY:
case MAN_AXIS_SCALE_YZ:
case MAN_AXIS_SCALE_ZX:
copy_m4_m4(axis->matrix_basis, twmat);
if (scale) {
RNA_float_set(axis->ptr,
"length",
MAN_AXIS_SCALE_PLANE_SCALE * scale[aidx_norm == 2 ? 0 : aidx_norm + 1]);
}
break;
case MAN_AXIS_SCALE_X:
case MAN_AXIS_SCALE_Y:
case MAN_AXIS_SCALE_Z:
copy_m4_m4(axis->matrix_basis, twmat);
if (scale) {
float end;
gizmo_line_range(twtype, axis_type, nullptr, &end);
RNA_float_set(axis->ptr, "length", end * scale[aidx_norm]);
}
break;
case MAN_AXIS_TRANS_X:
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_TRANS_Z:
copy_m4_m4(axis->matrix_basis, twmat);
break;
case MAN_AXIS_SCALE_C:
WM_gizmo_set_matrix_location(axis, twmat[3]);
if (scale) {
WM_gizmo_set_scale(axis, 0.2f * scale[0]);
}
break;
case MAN_AXIS_ROT_X:
case MAN_AXIS_ROT_Y:
case MAN_AXIS_ROT_Z:
copy_m4_m4(axis->matrix_basis, twmat);
orthogonalize_m4(axis->matrix_basis, aidx_norm);
break;
case MAN_AXIS_ROT_C:
case MAN_AXIS_ROT_T:
default:
WM_gizmo_set_matrix_location(axis, twmat[3]);
break;
}
}
static void gizmogroup_refresh_from_matrix(wmGizmoGroup *gzgroup,
const float twmat[4][4],
const float scale[3],
const bool ignore_hidden)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
MAN_ITER_AXES_BEGIN (axis, axis_idx) {
if (ignore_hidden && axis->flag & WM_GIZMO_HIDDEN) {
continue;
}
gizmo_refresh_from_matrix(axis, axis_idx, ggd->twtype, twmat, scale);
}
MAN_ITER_AXES_END;
}
static void WIDGETGROUP_gizmo_refresh(const bContext *C, wmGizmoGroup *gzgroup)
{
ARegion *region = CTX_wm_region(C);
{
wmGizmo *gz = WM_gizmomap_get_modal(region->gizmo_map);
if (gz && gz->parent_gzgroup == gzgroup) {
return;
}
}
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
Scene *scene = CTX_data_scene(C);
ScrArea *area = CTX_wm_area(C);
View3D *v3d = static_cast<View3D *>(area->spacedata.first);
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
TransformBounds tbounds;
if (ggd->use_twtype_refresh) {
ggd->twtype = v3d->gizmo_show_object & ggd->twtype_init;
if (ggd->twtype != ggd->twtype_prev) {
ggd->twtype_prev = ggd->twtype;
gizmogroup_init_properties_from_twtype(gzgroup);
}
}
const int orient_index = BKE_scene_orientation_get_index_from_flag(scene, ggd->twtype_init);
/* skip, we don't draw anything anyway */
TransformCalcParams calc_params{};
calc_params.use_only_center = true;
calc_params.orientation_index = orient_index + 1;
if ((ggd->all_hidden = (ED_transform_calc_gizmo_stats(C, &calc_params, &tbounds, rv3d) == 0))) {
return;
}
gizmo_3d_calc_pos(
C, scene, &tbounds, scene->toolsettings->transform_pivot_point, rv3d->twmat[3]);
gizmogroup_refresh_from_matrix(gzgroup, rv3d->twmat, nullptr, false);
}
static void WIDGETGROUP_gizmo_message_subscribe(const bContext *C,
wmGizmoGroup *gzgroup,
wmMsgBus *mbus)
{
Scene *scene = CTX_data_scene(C);
bScreen *screen = CTX_wm_screen(C);
ScrArea *area = CTX_wm_area(C);
ARegion *region = CTX_wm_region(C);
gizmo_xform_message_subscribe(
gzgroup, mbus, scene, screen, area, region, VIEW3D_GGT_xform_gizmo);
}
static void gizmogroup_hide_all(GizmoGroup *ggd)
{
MAN_ITER_AXES_BEGIN (axis, axis_idx) {
WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, true);
}
MAN_ITER_AXES_END;
}
static void WIDGETGROUP_gizmo_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
// ScrArea *area = CTX_wm_area(C);
ARegion *region = CTX_wm_region(C);
// View3D *v3d =static_cast< View3D *> (area->spacedata.first);
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
float viewinv_m3[3][3];
copy_m3_m4(viewinv_m3, rv3d->viewinv);
float idot[3];
/* Re-calculate hidden unless modal. */
bool is_modal = false;
{
wmGizmo *gz = WM_gizmomap_get_modal(region->gizmo_map);
if (gz && gz->parent_gzgroup == gzgroup) {
is_modal = true;
}
}
/* when looking through a selected camera, the gizmo can be at the
* exact same position as the view, skip so we don't break selection */
if (ggd->all_hidden || fabsf(ED_view3d_pixel_size(rv3d, rv3d->twmat[3])) < 5e-7f) {
if (!is_modal) {
gizmogroup_hide_all(ggd);
}
return;
}
gizmo_get_idot(rv3d, idot);
/* *** set properties for axes *** */
MAN_ITER_AXES_BEGIN (axis, axis_idx) {
if (is_modal) {
if (axis->flag & WM_GIZMO_HIDDEN) {
continue;
}
}
else {
const short axis_type = gizmo_get_axis_type(axis_idx);
if (gizmo_is_axis_visible(rv3d, ggd->twtype, idot, axis_type, axis_idx)) {
/* XXX maybe unset _HIDDEN flag on redraw? */
WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, false);
}
else {
WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, true);
continue;
}
/* Align to view. */
switch (axis_idx) {
case MAN_AXIS_TRANS_C:
case MAN_AXIS_ROT_C:
case MAN_AXIS_SCALE_C:
case MAN_AXIS_ROT_T:
WM_gizmo_set_matrix_rotation_from_z_axis(axis, rv3d->viewinv[2]);
break;
}
}
float color[4], color_hi[4];
gizmo_get_axis_color(axis_idx, idot, color, color_hi);
WM_gizmo_set_color(axis, color);
WM_gizmo_set_color_highlight(axis, color_hi);
}
MAN_ITER_AXES_END;
/* Refresh handled above when using view orientation. */
if (!equals_m3m3(viewinv_m3, ggd->prev.viewinv_m3)) {
{
Scene *scene = CTX_data_scene(C);
const TransformOrientationSlot *orient_slot = BKE_scene_orientation_slot_get_from_flag(
scene, ggd->twtype_init);
switch (orient_slot->type) {
case V3D_ORIENT_VIEW: {
WIDGETGROUP_gizmo_refresh(C, gzgroup);
break;
}
}
}
copy_m3_m4(ggd->prev.viewinv_m3, rv3d->viewinv);
}
}
static void gizmo_3d_draw_invoke(wmGizmoGroup *gzgroup,
const ARegion *region,
const int axis_idx_active,
const float mval[2])
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
const RegionView3D *rv3d = static_cast<const RegionView3D *>(region->regiondata);
wmGizmo *axis_active = ggd->gizmos[axis_idx_active];
const short axis_active_type = gizmo_get_axis_type(axis_idx_active);
/* Display only the active gizmo. */
gizmogroup_hide_all(ggd);
WM_gizmo_set_flag(axis_active, WM_GIZMO_HIDDEN, false);
gizmo_refresh_from_matrix(axis_active, axis_idx_active, ggd->twtype, rv3d->twmat, nullptr);
if (ELEM(axis_idx_active, MAN_AXIS_TRANS_C, MAN_AXIS_SCALE_C, MAN_AXIS_ROT_C, MAN_AXIS_ROT_T)) {
WM_gizmo_set_matrix_rotation_from_z_axis(axis_active, rv3d->viewinv[2]);
}
gizmo_3d_setup_draw_modal(axis_active, axis_idx_active, ggd->twtype);
if (axis_active_type == MAN_AXES_TRANSLATE) {
/* Arrows are used for visual reference, so keep all visible. */
for (int axis_idx = MAN_AXIS_TRANS_X; axis_idx <= MAN_AXIS_TRANS_Z; axis_idx++) {
if (axis_idx == axis_idx_active) {
continue;
}
wmGizmo *axis = ggd->gizmos[axis_idx];
WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, false);
gizmo_refresh_from_matrix(axis, axis_idx, ggd->twtype, rv3d->twmat, nullptr);
gizmo_3d_setup_draw_default(axis, axis_idx);
gizmo_3d_setup_draw_from_twtype(axis, axis_idx, ggd->twtype);
RNA_enum_set(axis->ptr, "draw_options", ED_GIZMO_ARROW_DRAW_FLAG_STEM);
}
}
else if (axis_active_type == MAN_AXES_ROTATE && axis_idx_active != MAN_AXIS_ROT_T) {
float mat[3][3];
mul_m3_m4m4(mat, axis_active->matrix_basis, axis_active->matrix_offset);
gizmo_3d_dial_matrixbasis_calc(
region, mat[2], axis_active->matrix_basis[3], mval, axis_active->matrix_offset);
copy_m3_m4(mat, axis_active->matrix_basis);
invert_m3(mat);
mul_m4_m3m4(axis_active->matrix_offset, mat, axis_active->matrix_offset);
zero_v3(axis_active->matrix_offset[3]);
}
}
static void WIDGETGROUP_gizmo_invoke_prepare(const bContext *C,
wmGizmoGroup *gzgroup,
wmGizmo *gz,
const wmEvent *event)
{
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup->customdata);
const int axis_idx = BLI_array_findindex(ggd->gizmos, ARRAY_SIZE(ggd->gizmos), &gz);
const float mval[2] = {float(event->mval[0]), float(event->mval[1])};
gizmo_3d_draw_invoke(gzgroup, CTX_wm_region(C), axis_idx, mval);
/* Support gizmo specific orientation. */
if (gz != ggd->gizmos[MAN_AXIS_ROT_T]) {
Scene *scene = CTX_data_scene(C);
wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0);
PointerRNA *ptr = &gzop->ptr;
PropertyRNA *prop_orient_type = RNA_struct_find_property(ptr, "orient_type");
const TransformOrientationSlot *orient_slot = BKE_scene_orientation_slot_get_from_flag(
scene, ggd->twtype_init);
if ((gz == ggd->gizmos[MAN_AXIS_ROT_C]) ||
(orient_slot == &scene->orientation_slots[SCE_ORIENT_DEFAULT]))
{
/* #MAN_AXIS_ROT_C always uses the #V3D_ORIENT_VIEW orientation,
* optionally we could set this orientation instead of unset the property. */
RNA_property_unset(ptr, prop_orient_type);
}
else {
/* TODO: APIfunction */
int index = BKE_scene_orientation_slot_get_index(orient_slot);
RNA_property_enum_set(ptr, prop_orient_type, index);
}
}
/* Support shift click to constrain axis. */
int axis = -1;
switch (axis_idx) {
case MAN_AXIS_TRANS_X:
case MAN_AXIS_TRANS_Y:
case MAN_AXIS_TRANS_Z:
axis = axis_idx - MAN_AXIS_TRANS_X;
break;
case MAN_AXIS_SCALE_X:
case MAN_AXIS_SCALE_Y:
case MAN_AXIS_SCALE_Z:
axis = axis_idx - MAN_AXIS_SCALE_X;
break;
}
if (axis != -1) {
/* Swap single axis for two-axis constraint. */
const bool flip = (event->modifier & KM_SHIFT) != 0;
BLI_assert(axis_idx != -1);
const short axis_type = gizmo_get_axis_type(axis_idx);
if (axis_type != MAN_AXES_ROTATE) {
wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0);
PointerRNA *ptr = &gzop->ptr;
PropertyRNA *prop_constraint_axis = RNA_struct_find_property(ptr, "constraint_axis");
if (prop_constraint_axis) {
bool constraint[3] = {false};
constraint[axis] = true;
if (flip) {
for (int i = 0; i < ARRAY_SIZE(constraint); i++) {
constraint[i] = !constraint[i];
}
}
RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint);
}
}
}
}
static bool WIDGETGROUP_gizmo_poll_generic(View3D *v3d)
{
if (v3d->gizmo_flag & V3D_GIZMO_HIDE) {
return false;
}
if (G.moving & (G_TRANSFORM_OBJ | G_TRANSFORM_EDIT)) {
return false;
}
return true;
}
static bool WIDGETGROUP_gizmo_poll_context(const bContext *C, wmGizmoGroupType * /*gzgt*/)
{
ScrArea *area = CTX_wm_area(C);
View3D *v3d = static_cast<View3D *>(area->spacedata.first);
if (!WIDGETGROUP_gizmo_poll_generic(v3d)) {
return false;
}
const bToolRef *tref = area->runtime.tool;
if (v3d->gizmo_flag & V3D_GIZMO_HIDE_CONTEXT) {
return false;
}
if ((v3d->gizmo_show_object & (V3D_GIZMO_SHOW_OBJECT_TRANSLATE | V3D_GIZMO_SHOW_OBJECT_ROTATE |
V3D_GIZMO_SHOW_OBJECT_SCALE)) == 0)
{
return false;
}
/* Don't show if the tool has a gizmo. */
if (tref && tref->runtime && tref->runtime->gizmo_group[0]) {
return false;
}
return true;
}
static bool WIDGETGROUP_gizmo_poll_tool(const bContext *C, wmGizmoGroupType *gzgt)
{
if (!ED_gizmo_poll_or_unlink_delayed_from_tool(C, gzgt)) {
return false;
}
ScrArea *area = CTX_wm_area(C);
View3D *v3d = static_cast<View3D *>(area->spacedata.first);
if (!WIDGETGROUP_gizmo_poll_generic(v3d)) {
return false;
}
if (v3d->gizmo_flag & V3D_GIZMO_HIDE_TOOL) {
return false;
}
return true;
}
/* Expose as multiple gizmos so tools use one, persistent context another.
* Needed because they use different options which isn't so simple to dynamically update. */
void VIEW3D_GGT_xform_gizmo(wmGizmoGroupType *gzgt)
{
gzgt->name = "3D View: Transform Gizmo";
gzgt->idname = "VIEW3D_GGT_xform_gizmo";
gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
gzgt->poll = WIDGETGROUP_gizmo_poll_tool;
gzgt->setup = WIDGETGROUP_gizmo_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->refresh = WIDGETGROUP_gizmo_refresh;
gzgt->message_subscribe = WIDGETGROUP_gizmo_message_subscribe;
gzgt->draw_prepare = WIDGETGROUP_gizmo_draw_prepare;
gzgt->invoke_prepare = WIDGETGROUP_gizmo_invoke_prepare;
static const EnumPropertyItem rna_enum_gizmo_items[] = {
{V3D_GIZMO_SHOW_OBJECT_TRANSLATE, "TRANSLATE", 0, "Move", ""},
{V3D_GIZMO_SHOW_OBJECT_ROTATE, "ROTATE", 0, "Rotate", ""},
{V3D_GIZMO_SHOW_OBJECT_SCALE, "SCALE", 0, "Scale", ""},
{0, "NONE", 0, "None", ""},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_enum(gzgt->srna,
"drag_action",
rna_enum_gizmo_items,
V3D_GIZMO_SHOW_OBJECT_TRANSLATE,
"Drag Action",
"");
g_GGT_xform_gizmo = gzgt;
}
void VIEW3D_GGT_xform_gizmo_context(wmGizmoGroupType *gzgt)
{
gzgt->name = "3D View: Transform Gizmo Context";
gzgt->idname = "VIEW3D_GGT_xform_gizmo_context";
gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_PERSISTENT |
WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK;
gzgt->poll = WIDGETGROUP_gizmo_poll_context;
gzgt->setup = WIDGETGROUP_gizmo_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->refresh = WIDGETGROUP_gizmo_refresh;
gzgt->message_subscribe = WIDGETGROUP_gizmo_message_subscribe;
gzgt->draw_prepare = WIDGETGROUP_gizmo_draw_prepare;
gzgt->invoke_prepare = WIDGETGROUP_gizmo_invoke_prepare;
g_GGT_xform_gizmo_context = gzgt;
}
/** \} */
static wmGizmoGroup *gizmogroup_xform_find(TransInfo *t)
{
struct wmGizmoMap *gizmo_map = t->region->gizmo_map;
if (gizmo_map == nullptr) {
BLI_assert_msg(false, "#T_NO_GIZMO should already be set to return early before.");
return nullptr;
}
wmGizmo *gizmo_modal_current = WM_gizmomap_get_modal(gizmo_map);
if (gizmo_modal_current) {
wmGizmoGroup *gzgroup = gizmo_modal_current->parent_gzgroup;
/* Check #wmGizmoGroup::customdata to make sure the GizmoGroup has been initialized. */
if (gzgroup->customdata && ELEM(gzgroup->type, g_GGT_xform_gizmo, g_GGT_xform_gizmo_context)) {
return gzgroup;
}
}
else {
/* See #WM_gizmomap_group_find_ptr. */
LISTBASE_FOREACH (wmGizmoGroup *, gzgroup, WM_gizmomap_group_list(gizmo_map)) {
if (ELEM(gzgroup->type, g_GGT_xform_gizmo, g_GGT_xform_gizmo_context)) {
/* Choose the one that has been initialized. */
if (gzgroup->customdata) {
return gzgroup;
}
}
}
}
return nullptr;
}
void transform_gizmo_3d_model_from_constraint_and_mode_init(TransInfo *t)
{
wmGizmo *gizmo_modal_current = t->region && t->region->gizmo_map ?
WM_gizmomap_get_modal(t->region->gizmo_map) :
nullptr;
if (!gizmo_modal_current || !ELEM(gizmo_modal_current->parent_gzgroup->type,
g_GGT_xform_gizmo,
g_GGT_xform_gizmo_context))
{
t->flag |= T_NO_GIZMO;
}
}
void transform_gizmo_3d_model_from_constraint_and_mode_set(TransInfo *t)
{
if (t->flag & T_NO_GIZMO) {
return;
}
wmGizmoGroup *gzgroup_xform = gizmogroup_xform_find(t);
if (gzgroup_xform == nullptr) {
return;
}
int axis_idx = -1;
if (t->mode == TFM_TRACKBALL) {
/* Pass. Do not display gizmo. */
}
else if (ELEM(t->mode, TFM_TRANSLATION, TFM_ROTATION, TFM_RESIZE)) {
const int axis_map[3][7] = {
{MAN_AXIS_TRANS_X,
MAN_AXIS_TRANS_Y,
MAN_AXIS_TRANS_XY,
MAN_AXIS_TRANS_Z,
MAN_AXIS_TRANS_ZX,
MAN_AXIS_TRANS_YZ,
MAN_AXIS_TRANS_C},
{MAN_AXIS_ROT_X,
MAN_AXIS_ROT_Y,
MAN_AXIS_ROT_Z,
MAN_AXIS_ROT_Z,
MAN_AXIS_ROT_Y,
MAN_AXIS_ROT_X,
MAN_AXIS_ROT_C},
{MAN_AXIS_SCALE_X,
MAN_AXIS_SCALE_Y,
MAN_AXIS_SCALE_XY,
MAN_AXIS_SCALE_Z,
MAN_AXIS_SCALE_ZX,
MAN_AXIS_SCALE_YZ,
MAN_AXIS_SCALE_C},
};
BLI_STATIC_ASSERT(
/* Assert mode values. */
((TFM_ROTATION == TFM_TRANSLATION + 1) && (TFM_RESIZE == TFM_TRANSLATION + 2) &&
/* Assert constrain values. */
(CON_AXIS0 == (1 << 1)) && (CON_AXIS1 == (1 << 2)) && (CON_AXIS2 == (1 << 3))),
"");
const int trans_mode = t->mode - TFM_TRANSLATION;
int con_mode = ((CON_AXIS0 | CON_AXIS1 | CON_AXIS2) >> 1) - 1;
if (t->con.mode & CON_APPLY) {
con_mode = ((t->con.mode & (CON_AXIS0 | CON_AXIS1 | CON_AXIS2)) >> 1) - 1;
}
axis_idx = axis_map[trans_mode][con_mode];
}
wmGizmo *gizmo_modal_current = WM_gizmomap_get_modal(t->region->gizmo_map);
if (axis_idx != -1) {
RegionView3D *rv3d = static_cast<RegionView3D *>(t->region->regiondata);
float(*mat_cmp)[3] = t->orient[t->orient_curr != O_DEFAULT ? t->orient_curr : O_SCENE].matrix;
bool update_orientation = !(equals_v3v3(rv3d->twmat[0], mat_cmp[0]) &&
equals_v3v3(rv3d->twmat[1], mat_cmp[1]) &&
equals_v3v3(rv3d->twmat[2], mat_cmp[2]));
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup_xform->customdata);
wmGizmo *gizmo_expected = ggd->gizmos[axis_idx];
if (update_orientation || gizmo_modal_current != gizmo_expected) {
if (update_orientation) {
copy_m4_m3(rv3d->twmat, mat_cmp);
copy_v3_v3(rv3d->twmat[3], t->center_global);
}
wmEvent event = {nullptr};
/* Set the initial mouse value. Used for rotation gizmos. */
copy_v2_v2_int(event.mval, t->mouse.imval);
/* We need to update the position of the gizmo before invoking otherwise
* #wmGizmo::scale_final could be calculated wrong. */
gizmo_refresh_from_matrix(gizmo_expected, axis_idx, ggd->twtype, rv3d->twmat, nullptr);
BLI_assert_msg(!gizmo_modal_current || gizmo_modal_current->highlight_part == 0,
"Avoid changing the highlight part");
gizmo_expected->highlight_part = 0;
WM_gizmo_modal_set_while_modal(t->region->gizmo_map, t->context, gizmo_expected, &event);
WM_gizmo_highlight_set(t->region->gizmo_map, gizmo_expected);
}
}
else if (gizmo_modal_current) {
WM_gizmo_modal_set_while_modal(t->region->gizmo_map, t->context, nullptr, nullptr);
}
}
void transform_gizmo_3d_model_from_constraint_and_mode_restore(TransInfo *t)
{
if (t->flag & T_NO_GIZMO) {
return;
}
wmGizmoGroup *gzgroup_xform = gizmogroup_xform_find(t);
if (gzgroup_xform == nullptr) {
return;
}
GizmoGroup *ggd = static_cast<GizmoGroup *>(gzgroup_xform->customdata);
/* #wmGizmoGroup::draw_prepare will handle the rest. */
MAN_ITER_AXES_BEGIN (axis, axis_idx) {
gizmo_3d_setup_draw_default(axis, axis_idx);
gizmo_3d_setup_draw_from_twtype(axis, axis_idx, ggd->twtype);
}
MAN_ITER_AXES_END;
}
bool ED_transform_calc_pivot_pos(const struct bContext *C,
const short pivot_type,
float r_pivot_pos[3])
{
Scene *scene = CTX_data_scene(C);
return gizmo_3d_calc_pos(C, scene, nullptr, pivot_type, r_pivot_pos);
}