Files
test/source/blender/editors/transform/transform_mode.cc
2025-02-28 12:38:10 -08:00

1254 lines
38 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edtransform
*/
#include <cstdlib>
#include "DNA_armature_types.h"
#include "DNA_constraint_types.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "BLI_listbase.h"
#include "BLI_math_base.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BKE_constraint.h"
#include "BKE_context.hh"
#include "BLT_translation.hh"
#include "transform.hh"
#include "transform_convert.hh"
#include "transform_gizmo.hh"
#include "transform_orientations.hh"
#include "transform_snap.hh"
/* Own include. */
#include "transform_mode.hh"
namespace blender::ed::transform {
eTfmMode transform_mode_really_used(bContext *C, eTfmMode mode)
{
if (mode == TFM_BONESIZE) {
Object *ob = CTX_data_active_object(C);
BLI_assert(ob);
if (ob->type != OB_ARMATURE) {
return TFM_RESIZE;
}
bArmature *arm = static_cast<bArmature *>(ob->data);
if (arm->drawtype == ARM_ENVELOPE) {
return TFM_BONE_ENVELOPE_DIST;
}
}
return mode;
}
bool transdata_check_local_center(const TransInfo *t, short around)
{
return ((around == V3D_AROUND_LOCAL_ORIGINS) &&
((t->options & (CTX_OBJECT | CTX_POSE_BONE)) ||
/* Implicit: `(t->flag & T_EDIT)`. */
ELEM(t->obedit_type,
OB_MESH,
OB_CURVES_LEGACY,
OB_CURVES,
OB_GREASE_PENCIL,
OB_MBALL,
OB_ARMATURE) ||
(t->spacetype == SPACE_GRAPH) ||
(t->options & (CTX_MOVIECLIP | CTX_MASK | CTX_PAINT_CURVE | CTX_SEQUENCER_IMAGE))));
}
bool transform_mode_is_changeable(const int mode)
{
return ELEM(mode,
TFM_ROTATION,
TFM_RESIZE,
TFM_TRACKBALL,
TFM_TRANSLATION,
TFM_EDGE_SLIDE,
TFM_VERT_SLIDE,
TFM_NORMAL_ROTATION);
}
bool transform_mode_affect_only_locations(const TransInfo *t)
{
return (t->flag & T_V3D_ALIGN) && (t->options & CTX_OBJECT) &&
(t->settings->transform_pivot_point != V3D_AROUND_CURSOR) && t->context &&
(CTX_DATA_COUNT(t->context, selected_editable_objects) == 1);
}
/* -------------------------------------------------------------------- */
/** \name Transform Locks
* \{ */
void protectedTransBits(short protectflag, float vec[3])
{
if (protectflag & OB_LOCK_LOCX) {
vec[0] = 0.0f;
}
if (protectflag & OB_LOCK_LOCY) {
vec[1] = 0.0f;
}
if (protectflag & OB_LOCK_LOCZ) {
vec[2] = 0.0f;
}
}
/* This function only does the delta rotation. */
static void protectedQuaternionBits(short protectflag, float quat[4], const float oldquat[4])
{
/* Check that protection flags are set. */
if ((protectflag & (OB_LOCK_ROTX | OB_LOCK_ROTY | OB_LOCK_ROTZ | OB_LOCK_ROTW)) == 0) {
return;
}
if (protectflag & OB_LOCK_ROT4D) {
/* Quaternions getting limited as 4D entities that they are. */
if (protectflag & OB_LOCK_ROTW) {
quat[0] = oldquat[0];
}
if (protectflag & OB_LOCK_ROTX) {
quat[1] = oldquat[1];
}
if (protectflag & OB_LOCK_ROTY) {
quat[2] = oldquat[2];
}
if (protectflag & OB_LOCK_ROTZ) {
quat[3] = oldquat[3];
}
}
else {
/* Quaternions get limited with euler... (compatibility mode). */
float eul[3], oldeul[3], nquat[4], noldquat[4];
float qlen;
qlen = normalize_qt_qt(nquat, quat);
normalize_qt_qt(noldquat, oldquat);
quat_to_eul(eul, nquat);
quat_to_eul(oldeul, noldquat);
if (protectflag & OB_LOCK_ROTX) {
eul[0] = oldeul[0];
}
if (protectflag & OB_LOCK_ROTY) {
eul[1] = oldeul[1];
}
if (protectflag & OB_LOCK_ROTZ) {
eul[2] = oldeul[2];
}
eul_to_quat(quat, eul);
/* Restore original quat size. */
mul_qt_fl(quat, qlen);
/* Quaternions flip w sign to accumulate rotations correctly. */
if ((nquat[0] < 0.0f && quat[0] > 0.0f) || (nquat[0] > 0.0f && quat[0] < 0.0f)) {
mul_qt_fl(quat, -1.0f);
}
}
}
static void protectedRotateBits(short protectflag, float eul[3], const float oldeul[3])
{
if (protectflag & OB_LOCK_ROTX) {
eul[0] = oldeul[0];
}
if (protectflag & OB_LOCK_ROTY) {
eul[1] = oldeul[1];
}
if (protectflag & OB_LOCK_ROTZ) {
eul[2] = oldeul[2];
}
}
/**
* This function only does the delta rotation.
* Axis-angle is usually internally stored as quaternions.
*/
static void protectedAxisAngleBits(
short protectflag, float axis[3], float *angle, const float oldAxis[3], float oldAngle)
{
/* Check that protection flags are set. */
if ((protectflag & (OB_LOCK_ROTX | OB_LOCK_ROTY | OB_LOCK_ROTZ | OB_LOCK_ROTW)) == 0) {
return;
}
if (protectflag & OB_LOCK_ROT4D) {
/* Axis-angle getting limited as 4D entities that they are... */
if (protectflag & OB_LOCK_ROTW) {
*angle = oldAngle;
}
if (protectflag & OB_LOCK_ROTX) {
axis[0] = oldAxis[0];
}
if (protectflag & OB_LOCK_ROTY) {
axis[1] = oldAxis[1];
}
if (protectflag & OB_LOCK_ROTZ) {
axis[2] = oldAxis[2];
}
}
else {
/* Axis-angle get limited with euler. */
float eul[3], oldeul[3];
axis_angle_to_eulO(eul, EULER_ORDER_DEFAULT, axis, *angle);
axis_angle_to_eulO(oldeul, EULER_ORDER_DEFAULT, oldAxis, oldAngle);
if (protectflag & OB_LOCK_ROTX) {
eul[0] = oldeul[0];
}
if (protectflag & OB_LOCK_ROTY) {
eul[1] = oldeul[1];
}
if (protectflag & OB_LOCK_ROTZ) {
eul[2] = oldeul[2];
}
eulO_to_axis_angle(axis, angle, eul, EULER_ORDER_DEFAULT);
/* When converting to axis-angle,
* we need a special exception for the case when there is no axis. */
if (IS_EQF(axis[0], axis[1]) && IS_EQF(axis[1], axis[2])) {
/* For now, rotate around y-axis then (so that it simply becomes the roll). */
axis[1] = 1.0f;
}
}
}
void protectedSizeBits(short protectflag, float size[3])
{
if (protectflag & OB_LOCK_SCALEX) {
size[0] = 1.0f;
}
if (protectflag & OB_LOCK_SCALEY) {
size[1] = 1.0f;
}
if (protectflag & OB_LOCK_SCALEZ) {
size[2] = 1.0f;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform Limits
* \{ */
void constraintTransLim(const TransInfo *t, const TransDataContainer *tc, TransData *td)
{
if (td->con) {
const bConstraintTypeInfo *ctiLoc = BKE_constraint_typeinfo_from_type(
CONSTRAINT_TYPE_LOCLIMIT);
const bConstraintTypeInfo *ctiDist = BKE_constraint_typeinfo_from_type(
CONSTRAINT_TYPE_DISTLIMIT);
bConstraintOb cob = {nullptr};
bConstraint *con;
float ctime = float(t->scene->r.cfra);
/* Make a temporary bConstraintOb for using these limit constraints
* - They only care that cob->matrix is correctly set ;-).
* - Current space should be local.
*/
unit_m4(cob.matrix);
copy_v3_v3(cob.matrix[3], td->loc);
/* Evaluate valid constraints. */
for (con = td->con; con; con = con->next) {
const bConstraintTypeInfo *cti = nullptr;
ListBase targets = {nullptr, nullptr};
/* Only consider constraint if enabled. */
if (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF)) {
continue;
}
if (con->enforce == 0.0f) {
continue;
}
/* Only use it if it's tagged for this purpose (and the right type). */
if (con->type == CONSTRAINT_TYPE_LOCLIMIT) {
bLocLimitConstraint *data = (bLocLimitConstraint *)con->data;
if ((data->flag2 & LIMIT_TRANSFORM) == 0) {
continue;
}
cti = ctiLoc;
}
else if (con->type == CONSTRAINT_TYPE_DISTLIMIT) {
bDistLimitConstraint *data = (bDistLimitConstraint *)con->data;
if ((data->flag & LIMITDIST_TRANSFORM) == 0) {
continue;
}
cti = ctiDist;
}
if (cti) {
/* Do space conversions. */
if (con->ownspace == CONSTRAINT_SPACE_WORLD) {
mul_m3_v3(td->mtx, cob.matrix[3]);
if (tc->use_local_mat) {
add_v3_v3(cob.matrix[3], tc->mat[3]);
}
}
else if (con->ownspace == CONSTRAINT_SPACE_POSE) {
/* Bone space without considering object transformations. */
mul_m3_v3(td->mtx, cob.matrix[3]);
mul_m3_v3(tc->imat3, cob.matrix[3]);
}
else if (con->ownspace != CONSTRAINT_SPACE_LOCAL) {
/* Skip... incompatible spacetype. */
continue;
}
/* Initialize the custom space for use in calculating the matrices. */
BKE_constraint_custom_object_space_init(&cob, con);
/* Get constraint targets if needed. */
BKE_constraint_targets_for_solving_get(t->depsgraph, con, &cob, &targets, ctime);
/* Do constraint. */
cti->evaluate_constraint(con, &cob, &targets);
/* Convert spaces again. */
if (con->ownspace == CONSTRAINT_SPACE_WORLD) {
if (tc->use_local_mat) {
sub_v3_v3(cob.matrix[3], tc->mat[3]);
}
mul_m3_v3(td->smtx, cob.matrix[3]);
}
else if (con->ownspace == CONSTRAINT_SPACE_POSE) {
mul_m3_v3(tc->mat3, cob.matrix[3]);
mul_m3_v3(td->smtx, cob.matrix[3]);
}
/* Free targets list. */
BLI_freelistN(&targets);
}
}
/* Copy results from `cob->matrix`. */
copy_v3_v3(td->loc, cob.matrix[3]);
}
}
static void constraintob_from_transdata(bConstraintOb *cob, TransData *td)
{
/* Make a temporary bConstraintOb for use by limit constraints
* - they only care that cob->matrix is correctly set ;-)
* - current space should be local
*/
memset(cob, 0, sizeof(bConstraintOb));
if (td->ext) {
if (td->ext->rotOrder == ROT_MODE_QUAT) {
/* Quaternion. */
/* Objects and bones do normalization first too, otherwise
* we don't necessarily end up with a rotation matrix, and
* then conversion back to quat gives a different result. */
float quat[4];
normalize_qt_qt(quat, td->ext->quat);
quat_to_mat4(cob->matrix, quat);
}
else if (td->ext->rotOrder == ROT_MODE_AXISANGLE) {
/* Axis angle. */
axis_angle_to_mat4(cob->matrix, td->ext->rotAxis, *td->ext->rotAngle);
}
else {
/* Eulers. */
eulO_to_mat4(cob->matrix, td->ext->rot, td->ext->rotOrder);
}
}
}
static void constraintRotLim(const TransInfo * /*t*/, TransData *td)
{
if (td->con) {
const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_from_type(CONSTRAINT_TYPE_ROTLIMIT);
bConstraintOb cob;
bConstraint *con;
bool do_limit = false;
/* Evaluate valid constraints. */
for (con = td->con; con; con = con->next) {
/* Only consider constraint if enabled. */
if (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF)) {
continue;
}
if (con->enforce == 0.0f) {
continue;
}
/* We're only interested in Limit-Rotation constraints. */
if (con->type == CONSTRAINT_TYPE_ROTLIMIT) {
bRotLimitConstraint *data = (bRotLimitConstraint *)con->data;
/* Only use it if it's tagged for this purpose. */
if ((data->flag2 & LIMIT_TRANSFORM) == 0) {
continue;
}
/* Skip incompatible space-types. */
if (!ELEM(con->ownspace, CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL)) {
continue;
}
/* Only do conversion if necessary, to preserve quaternion and euler rotations. */
if (do_limit == false) {
constraintob_from_transdata(&cob, td);
do_limit = true;
}
/* Do space conversions. */
if (con->ownspace == CONSTRAINT_SPACE_WORLD) {
/* Just multiply by `td->mtx` (this should be ok). */
mul_m4_m3m4(cob.matrix, td->mtx, cob.matrix);
}
/* Do constraint. */
cti->evaluate_constraint(con, &cob, nullptr);
/* Convert spaces again. */
if (con->ownspace == CONSTRAINT_SPACE_WORLD) {
/* Just multiply by `td->smtx` (this should be ok). */
mul_m4_m3m4(cob.matrix, td->smtx, cob.matrix);
}
}
}
if (do_limit) {
/* Copy results from `cob->matrix`. */
if (td->ext->rotOrder == ROT_MODE_QUAT) {
/* Quaternion. */
mat4_to_quat(td->ext->quat, cob.matrix);
}
else if (td->ext->rotOrder == ROT_MODE_AXISANGLE) {
/* Axis angle. */
mat4_to_axis_angle(td->ext->rotAxis, td->ext->rotAngle, cob.matrix);
}
else {
/* Eulers. */
mat4_to_eulO(td->ext->rot, td->ext->rotOrder, cob.matrix);
}
}
}
}
void constraintSizeLim(const TransInfo *t, const TransDataContainer *tc, TransData *td)
{
if (td->con && td->ext) {
const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_from_type(CONSTRAINT_TYPE_SIZELIMIT);
bConstraintOb cob = {nullptr};
bConstraint *con;
float size_sign[3], size_abs[3];
int i;
/* Make a temporary bConstraintOb for using these limit constraints
* - they only care that cob->matrix is correctly set ;-)
* - current space should be local
*/
if ((td->flag & TD_SINGLESIZE) && !(t->con.mode & CON_APPLY)) {
/* Scale val and reset size. */
return; /* TODO: fix this case. */
}
/* Reset val if SINGLESIZE but using a constraint. */
if (td->flag & TD_SINGLESIZE) {
return;
}
/* Separate out sign to apply back later. */
for (i = 0; i < 3; i++) {
size_sign[i] = signf(td->ext->size[i]);
size_abs[i] = fabsf(td->ext->size[i]);
}
size_to_mat4(cob.matrix, size_abs);
/* Evaluate valid constraints. */
for (con = td->con; con; con = con->next) {
/* Only consider constraint if enabled. */
if (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF)) {
continue;
}
if (con->enforce == 0.0f) {
continue;
}
/* We're only interested in Limit-Scale constraints. */
if (con->type == CONSTRAINT_TYPE_SIZELIMIT) {
bSizeLimitConstraint *data = static_cast<bSizeLimitConstraint *>(con->data);
/* Only use it if it's tagged for this purpose. */
if ((data->flag2 & LIMIT_TRANSFORM) == 0) {
continue;
}
/* Do space conversions. */
if (con->ownspace == CONSTRAINT_SPACE_WORLD) {
/* Just multiply by `td->mtx` (this should be ok). */
mul_m4_m3m4(cob.matrix, td->mtx, cob.matrix);
}
else if (con->ownspace == CONSTRAINT_SPACE_POSE) {
/* Bone space without considering object transformations. */
mul_m4_m3m4(cob.matrix, td->mtx, cob.matrix);
mul_m4_m3m4(cob.matrix, tc->imat3, cob.matrix);
}
else if (con->ownspace != CONSTRAINT_SPACE_LOCAL) {
/* Skip... incompatible `spacetype`. */
continue;
}
/* Do constraint. */
cti->evaluate_constraint(con, &cob, nullptr);
/* Convert spaces again. */
if (con->ownspace == CONSTRAINT_SPACE_WORLD) {
/* Just multiply by `td->smtx` (this should be ok). */
mul_m4_m3m4(cob.matrix, td->smtx, cob.matrix);
}
else if (con->ownspace == CONSTRAINT_SPACE_POSE) {
mul_m4_m3m4(cob.matrix, tc->mat3, cob.matrix);
mul_m4_m3m4(cob.matrix, td->smtx, cob.matrix);
}
}
}
/* Copy results from `cob->matrix`. */
if ((td->flag & TD_SINGLESIZE) && !(t->con.mode & CON_APPLY)) {
/* Scale val and reset size. */
return; /* TODO: fix this case. */
}
/* Reset val if SINGLESIZE but using a constraint. */
if (td->flag & TD_SINGLESIZE) {
return;
}
/* Extract scale from matrix and apply back sign. */
mat4_to_size(td->ext->size, cob.matrix);
mul_v3_v3(td->ext->size, size_sign);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform (Rotation Utils)
* \{ */
void headerRotation(TransInfo *t, char *str, const int str_size, float final)
{
size_t ofs = 0;
if (hasNumInput(&t->num)) {
char c[NUM_STR_REP_LEN];
outputNumInput(&(t->num), c, t->scene->unit);
ofs += BLI_snprintf_rlen(
str + ofs, str_size - ofs, IFACE_("Rotation: %s %s %s"), &c[0], t->con.text, t->proptext);
}
else {
ofs += BLI_snprintf_rlen(str + ofs,
str_size - ofs,
IFACE_("Rotation: %.2f%s %s"),
RAD2DEGF(final),
t->con.text,
t->proptext);
}
if (t->flag & T_PROP_EDIT_ALL) {
ofs += BLI_snprintf_rlen(
str + ofs, str_size - ofs, IFACE_(" Proportional size: %.2f"), t->prop_size);
}
}
void ElementRotation_ex(const TransInfo *t,
const TransDataContainer *tc,
TransData *td,
const float mat[3][3],
const float *center)
{
float vec[3], totmat[3][3], smat[3][3];
float eul[3], fmat[3][3], quat[4];
if (t->flag & T_POINTS) {
mul_m3_m3m3(totmat, mat, td->mtx);
mul_m3_m3m3(smat, td->smtx, totmat);
/* Apply gpencil falloff. */
if (t->options & CTX_GPENCIL_STROKES) {
if (t->obedit_type == OB_GREASE_PENCIL) {
const float *gp_falloff = static_cast<const float *>(td->extra);
if (gp_falloff != nullptr && *gp_falloff != 1.0f) {
float ident_mat[3][3];
unit_m3(ident_mat);
interp_m3_m3m3(smat, ident_mat, smat, *gp_falloff);
}
}
}
sub_v3_v3v3(vec, td->iloc, center);
mul_m3_v3(smat, vec);
add_v3_v3v3(td->loc, vec, center);
sub_v3_v3v3(vec, td->loc, td->iloc);
protectedTransBits(td->protectflag, vec);
add_v3_v3v3(td->loc, td->iloc, vec);
if (td->flag & TD_USEQUAT) {
mul_m3_series(fmat, td->smtx, mat, td->mtx);
mat3_to_quat(quat, fmat); /* Actual transform. */
if (td->ext->quat) {
mul_qt_qtqt(td->ext->quat, quat, td->ext->iquat);
/* Is there a reason not to have this here? -jahka. */
protectedQuaternionBits(td->protectflag, td->ext->quat, td->ext->iquat);
}
}
}
/**
* HACK WARNING
*
* This is some VERY ugly special case to deal with pose mode.
*
* The problem is that mtx and smtx include each bone orientation.
*
* That is needed to rotate each bone properly, HOWEVER, to calculate
* the translation component, we only need the actual armature object's
* matrix (and inverse). That is not all though. Once the proper translation
* has been computed, it has to be converted back into the bone's space.
*/
else if (t->options & CTX_POSE_BONE) {
/* Extract and invert armature object matrix. */
if ((td->flag & TD_NO_LOC) == 0) {
sub_v3_v3v3(vec, td->center, center);
mul_m3_v3(tc->mat3, vec); /* To Global space. */
mul_m3_v3(mat, vec); /* Applying rotation. */
mul_m3_v3(tc->imat3, vec); /* To Local space. */
add_v3_v3(vec, center);
/* `vec` now is the location where the object has to be. */
sub_v3_v3v3(vec, vec, td->center); /* Translation needed from the initial location. */
/* Special exception, see TD_PBONE_LOCAL_MTX definition comments. */
if (td->flag & TD_PBONE_LOCAL_MTX_P) {
/* Do nothing. */
}
else if (td->flag & TD_PBONE_LOCAL_MTX_C) {
mul_m3_v3(tc->mat3, vec); /* To Global space. */
mul_m3_v3(td->ext->l_smtx, vec); /* To Pose space (Local Location). */
}
else {
mul_m3_v3(tc->mat3, vec); /* To Global space. */
mul_m3_v3(td->smtx, vec); /* To Pose space. */
}
protectedTransBits(td->protectflag, vec);
add_v3_v3v3(td->loc, td->iloc, vec);
constraintTransLim(t, tc, td);
}
/* Rotation. */
/* MORE HACK: as in some cases the matrix to apply location and rot/scale is not the same,
* and ElementRotation() might be called in Translation context (with align snapping),
* we need to be sure to actually use the *rotation* matrix here...
* So no other way than storing it in some dedicated members of `td->ext`! */
if ((t->flag & T_V3D_ALIGN) == 0) { /* Align mode doesn't rotate objects itself. */
/* Euler or quaternion/axis-angle? */
if (td->ext->rotOrder == ROT_MODE_QUAT) {
mul_m3_series(fmat, td->ext->r_smtx, mat, td->ext->r_mtx);
mat3_to_quat(quat, fmat); /* Actual transform. */
mul_qt_qtqt(td->ext->quat, quat, td->ext->iquat);
/* This function works on end result. */
protectedQuaternionBits(td->protectflag, td->ext->quat, td->ext->iquat);
}
else if (td->ext->rotOrder == ROT_MODE_AXISANGLE) {
/* Calculate effect based on quaternions. */
float iquat[4], tquat[4];
axis_angle_to_quat(iquat, td->ext->irotAxis, td->ext->irotAngle);
mul_m3_series(fmat, td->ext->r_smtx, mat, td->ext->r_mtx);
mat3_to_quat(quat, fmat); /* Actual transform. */
mul_qt_qtqt(tquat, quat, iquat);
quat_to_axis_angle(td->ext->rotAxis, td->ext->rotAngle, tquat);
/* This function works on end result. */
protectedAxisAngleBits(td->protectflag,
td->ext->rotAxis,
td->ext->rotAngle,
td->ext->irotAxis,
td->ext->irotAngle);
}
else {
float eulmat[3][3];
mul_m3_m3m3(totmat, mat, td->ext->r_mtx);
mul_m3_m3m3(smat, td->ext->r_smtx, totmat);
/* Calculate the total rotation in eulers. */
copy_v3_v3(eul, td->ext->irot);
eulO_to_mat3(eulmat, eul, td->ext->rotOrder);
/* `mat = transform`, `obmat = bone rotation`. */
mul_m3_m3m3(fmat, smat, eulmat);
mat3_to_compatible_eulO(eul, td->ext->rot, td->ext->rotOrder, fmat);
/* And apply (to end result only). */
protectedRotateBits(td->protectflag, eul, td->ext->irot);
copy_v3_v3(td->ext->rot, eul);
}
constraintRotLim(t, td);
}
}
else {
if ((td->flag & TD_NO_LOC) == 0) {
/* Translation. */
sub_v3_v3v3(vec, td->center, center);
mul_m3_v3(mat, vec);
add_v3_v3(vec, center);
/* `vec` now is the location where the object has to be. */
sub_v3_v3(vec, td->center);
mul_m3_v3(td->smtx, vec);
protectedTransBits(td->protectflag, vec);
add_v3_v3v3(td->loc, td->iloc, vec);
}
constraintTransLim(t, tc, td);
/* Rotation. */
if ((t->flag & T_V3D_ALIGN) == 0) { /* Align mode doesn't rotate objects itself. */
/* Euler or quaternion? */
if ((td->ext->rotOrder == ROT_MODE_QUAT) || (td->flag & TD_USEQUAT)) {
/* Can be called for texture space translate for example, then opt out. */
if (td->ext->quat) {
mul_m3_series(fmat, td->smtx, mat, td->mtx);
if (!is_zero_v3(td->ext->dquat)) {
/* Correct for delta quat. */
float tmp_mat[3][3];
quat_to_mat3(tmp_mat, td->ext->dquat);
mul_m3_m3m3(fmat, fmat, tmp_mat);
}
mat3_to_quat(quat, fmat); /* Actual transform. */
if (!is_zero_v4(td->ext->dquat)) {
/* Correct back for delta quaternion. */
float idquat[4];
invert_qt_qt_normalized(idquat, td->ext->dquat);
mul_qt_qtqt(quat, idquat, quat);
}
mul_qt_qtqt(td->ext->quat, quat, td->ext->iquat);
/* This function works on end result. */
protectedQuaternionBits(td->protectflag, td->ext->quat, td->ext->iquat);
}
}
else if (td->ext->rotOrder == ROT_MODE_AXISANGLE) {
/* Calculate effect based on quaternions. */
float iquat[4], tquat[4];
axis_angle_to_quat(iquat, td->ext->irotAxis, td->ext->irotAngle);
mul_m3_series(fmat, td->smtx, mat, td->mtx);
mat3_to_quat(quat, fmat); /* Actual transform. */
mul_qt_qtqt(tquat, quat, iquat);
quat_to_axis_angle(td->ext->rotAxis, td->ext->rotAngle, tquat);
/* This function works on end result. */
protectedAxisAngleBits(td->protectflag,
td->ext->rotAxis,
td->ext->rotAngle,
td->ext->irotAxis,
td->ext->irotAngle);
}
else {
/* Calculate the total rotation in eulers. */
float obmat[3][3];
mul_m3_m3m3(totmat, mat, td->mtx);
mul_m3_m3m3(smat, td->smtx, totmat);
if (!is_zero_v3(td->ext->drot)) {
/* Correct for delta rot. */
add_eul_euleul(eul, td->ext->irot, td->ext->drot, td->ext->rotOrder);
}
else {
copy_v3_v3(eul, td->ext->irot);
}
eulO_to_mat3(obmat, eul, td->ext->rotOrder);
mul_m3_m3m3(fmat, smat, obmat);
mat3_to_compatible_eulO(eul, td->ext->rot, td->ext->rotOrder, fmat);
if (!is_zero_v3(td->ext->drot)) {
/* Correct back for delta rot. */
sub_eul_euleul(eul, eul, td->ext->drot, td->ext->rotOrder);
}
/* And apply. */
protectedRotateBits(td->protectflag, eul, td->ext->irot);
copy_v3_v3(td->ext->rot, eul);
}
constraintRotLim(t, td);
}
}
}
void ElementRotation(const TransInfo *t,
const TransDataContainer *tc,
TransData *td,
const float mat[3][3],
const short around)
{
const float *center;
/* Local constraint shouldn't alter center. */
if (transdata_check_local_center(t, around)) {
center = td->center;
}
else {
center = tc->center_local;
}
ElementRotation_ex(t, tc, td, mat, center);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform (Resize Utils)
* \{ */
void headerResize(TransInfo *t, const float vec[3], char *str, const int str_size)
{
char tvec[NUM_STR_REP_LEN * 3];
size_t ofs = 0;
if (hasNumInput(&t->num)) {
outputNumInput(&(t->num), tvec, t->scene->unit);
}
else {
BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.4f", vec[0]);
BLI_snprintf(&tvec[NUM_STR_REP_LEN], NUM_STR_REP_LEN, "%.4f", vec[1]);
BLI_snprintf(&tvec[NUM_STR_REP_LEN * 2], NUM_STR_REP_LEN, "%.4f", vec[2]);
}
if (t->con.mode & CON_APPLY) {
switch (t->num.idx_max) {
case 0:
ofs += BLI_snprintf_rlen(str + ofs,
str_size - ofs,
IFACE_("Scale: %s%s %s"),
&tvec[0],
t->con.text,
t->proptext);
break;
case 1:
ofs += BLI_snprintf_rlen(str + ofs,
str_size - ofs,
IFACE_("Scale: %s : %s%s %s"),
&tvec[0],
&tvec[NUM_STR_REP_LEN],
t->con.text,
t->proptext);
break;
case 2:
ofs += BLI_snprintf_rlen(str + ofs,
str_size - ofs,
IFACE_("Scale: %s : %s : %s%s %s"),
&tvec[0],
&tvec[NUM_STR_REP_LEN],
&tvec[NUM_STR_REP_LEN * 2],
t->con.text,
t->proptext);
break;
}
}
else {
if (t->flag & T_2D_EDIT) {
ofs += BLI_snprintf_rlen(str + ofs,
str_size - ofs,
IFACE_("Scale X: %s Y: %s%s %s"),
&tvec[0],
&tvec[NUM_STR_REP_LEN],
t->con.text,
t->proptext);
}
else {
ofs += BLI_snprintf_rlen(str + ofs,
str_size - ofs,
IFACE_("Scale X: %s Y: %s Z: %s%s %s"),
&tvec[0],
&tvec[NUM_STR_REP_LEN],
&tvec[NUM_STR_REP_LEN * 2],
t->con.text,
t->proptext);
}
}
if (t->flag & T_PROP_EDIT_ALL) {
ofs += BLI_snprintf_rlen(
str + ofs, str_size - ofs, IFACE_(" Proportional size: %.2f"), t->prop_size);
}
}
/**
* \a smat is reference matrix only.
*
* \note this is a tricky area, before making changes see: #29633, #42444
*/
static void TransMat3ToSize(const float mat[3][3], const float smat[3][3], float size[3])
{
float rmat[3][3];
mat3_to_rot_size(rmat, size, mat);
/* First tried with dot-product... but the sign flip is crucial. */
if (dot_v3v3(rmat[0], smat[0]) < 0.0f) {
size[0] = -size[0];
}
if (dot_v3v3(rmat[1], smat[1]) < 0.0f) {
size[1] = -size[1];
}
if (dot_v3v3(rmat[2], smat[2]) < 0.0f) {
size[2] = -size[2];
}
}
void ElementResize(const TransInfo *t,
const TransDataContainer *tc,
TransData *td,
const float mat[3][3])
{
float tmat[3][3], smat[3][3], center[3];
float vec[3];
if (t->flag & T_EDIT) {
mul_m3_m3m3(smat, mat, td->mtx);
mul_m3_m3m3(tmat, td->smtx, smat);
}
else {
copy_m3_m3(tmat, mat);
}
if (t->con.applySize) {
t->con.applySize(t, tc, td, tmat);
}
/* Local constraint shouldn't alter center. */
if (transdata_check_local_center(t, t->around)) {
copy_v3_v3(center, td->center);
}
else if (t->options & CTX_MOVIECLIP) {
if (td->flag & TD_INDIVIDUAL_SCALE) {
copy_v3_v3(center, td->center);
}
else {
copy_v3_v3(center, tc->center_local);
}
}
else {
copy_v3_v3(center, tc->center_local);
}
/* Size checked needed since the 3D cursor only uses rotation fields. */
if (td->ext && td->ext->size) {
float fsize[3];
if (ELEM(t->data_type,
&TransConvertType_Sculpt,
&TransConvertType_Object,
&TransConvertType_ObjectTexSpace,
&TransConvertType_Pose))
{
float obsizemat[3][3];
/* Reorient the size mat to fit the oriented object. */
mul_m3_m3m3(obsizemat, tmat, td->axismtx);
// print_m3("obsizemat", obsizemat);
TransMat3ToSize(obsizemat, td->axismtx, fsize);
// print_v3("fsize", fsize);
}
else {
mat3_to_size(fsize, tmat);
}
protectedSizeBits(td->protectflag, fsize);
if ((t->flag & T_V3D_ALIGN) == 0) { /* Align mode doesn't resize objects itself. */
if ((td->flag & TD_SINGLESIZE) && !(t->con.mode & CON_APPLY)) {
/* Scale val and reset size. */
*td->val = td->ival * (1 + (fsize[0] - 1) * td->factor);
td->ext->size[0] = td->ext->isize[0];
td->ext->size[1] = td->ext->isize[1];
td->ext->size[2] = td->ext->isize[2];
}
else {
/* Reset val if SINGLESIZE but using a constraint. */
if (td->flag & TD_SINGLESIZE) {
*td->val = td->ival;
}
td->ext->size[0] = td->ext->isize[0] * (1 + (fsize[0] - 1) * td->factor);
td->ext->size[1] = td->ext->isize[1] * (1 + (fsize[1] - 1) * td->factor);
td->ext->size[2] = td->ext->isize[2] * (1 + (fsize[2] - 1) * td->factor);
}
}
constraintSizeLim(t, tc, td);
}
/* For individual element center, Editmode need to use iloc. */
if (t->flag & T_POINTS) {
sub_v3_v3v3(vec, td->iloc, center);
}
else {
sub_v3_v3v3(vec, td->center, center);
}
mul_m3_v3(tmat, vec);
add_v3_v3(vec, center);
if (t->flag & T_POINTS) {
sub_v3_v3(vec, td->iloc);
}
else {
sub_v3_v3(vec, td->center);
}
/* Grease pencil falloff.
*
* FIXME: This is bad on multiple levels!
*
* - #applyNumInput is not intended to be run for every element,
* this writes back into the number input in a way that doesn't make sense to run many times.
*
* - Writing into #TransInfo should be avoided since it means order of operations
* may impact the result and isn't thread-safe.
*
* Operating on copies as a temporary solution.
*/
if (t->options & CTX_GPENCIL_STROKES) {
const float *gp_falloff_ptr = static_cast<const float *>(td->extra);
const float gp_falloff = gp_falloff_ptr != nullptr ? *gp_falloff_ptr : 1.0f;
mul_v3_fl(vec, td->factor * gp_falloff);
/* Scale stroke thickness. */
if (td->val) {
NumInput num_evil = t->num;
float values_final_evil[4];
copy_v4_v4(values_final_evil, t->values_final);
transform_snap_increment(t, values_final_evil);
applyNumInput(&num_evil, values_final_evil);
float ratio = values_final_evil[0];
float transformed_value = td->ival * fabs(ratio);
*td->val = math::max(math::interpolate(td->ival, transformed_value, gp_falloff), 0.001f);
}
}
else {
mul_v3_fl(vec, td->factor);
}
if (t->options & (CTX_OBJECT | CTX_POSE_BONE)) {
if (t->options & CTX_POSE_BONE) {
/* Without this, the resulting location of scaled bones aren't correct,
* especially noticeable scaling root or disconnected bones around the cursor, see #92515. */
mul_mat3_m4_v3(tc->poseobj->object_to_world().ptr(), vec);
}
mul_m3_v3(td->smtx, vec);
}
protectedTransBits(td->protectflag, vec);
if (td->loc) {
add_v3_v3v3(td->loc, td->iloc, vec);
}
constraintTransLim(t, tc, td);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform Mode Initialization
* \{ */
static TransModeInfo *mode_info_get(TransInfo *t, const int mode)
{
switch (mode) {
case TFM_TRANSLATION:
return &TransMode_translate;
case TFM_ROTATION:
return &TransMode_rotate;
case TFM_RESIZE:
return &TransMode_resize;
case TFM_SKIN_RESIZE:
return &TransMode_skinresize;
case TFM_TOSPHERE:
return &TransMode_tosphere;
case TFM_SHEAR:
return &TransMode_shear;
case TFM_BEND:
return &TransMode_bend;
case TFM_SHRINKFATTEN:
return &TransMode_shrinkfatten;
case TFM_TILT:
return &TransMode_tilt;
case TFM_CURVE_SHRINKFATTEN:
return &TransMode_curveshrinkfatten;
case TFM_MASK_SHRINKFATTEN:
return &TransMode_maskshrinkfatten;
case TFM_TRACKBALL:
return &TransMode_trackball;
case TFM_PUSHPULL:
return &TransMode_pushpull;
case TFM_EDGE_CREASE:
return &TransMode_edgecrease;
case TFM_VERT_CREASE:
return &TransMode_vertcrease;
case TFM_BONESIZE:
return &TransMode_bboneresize;
case TFM_BONE_ENVELOPE:
case TFM_BONE_ENVELOPE_DIST:
return &TransMode_boneenvelope;
case TFM_EDGE_SLIDE:
return &TransMode_edgeslide;
case TFM_VERT_SLIDE:
return &TransMode_vertslide;
case TFM_BONE_ROLL:
return &TransMode_boneroll;
case TFM_TIME_TRANSLATE:
return &TransMode_timetranslate;
case TFM_TIME_SLIDE:
return &TransMode_timeslide;
case TFM_TIME_SCALE:
return &TransMode_timescale;
case TFM_TIME_EXTEND:
/* Do TFM_TIME_TRANSLATE (for most Animation Editors because they have only 1D transforms for
* time values) or TFM_TRANSLATION (for Graph/NLA Editors only since they uses 'standard'
* transforms to get 2D movement) depending on which editor this was called from. */
if (ELEM(t->spacetype, SPACE_GRAPH, SPACE_NLA)) {
return &TransMode_translate;
}
return &TransMode_timetranslate;
case TFM_BAKE_TIME:
return &TransMode_baketime;
case TFM_MIRROR:
return &TransMode_mirror;
case TFM_BWEIGHT:
return &TransMode_bevelweight;
case TFM_ALIGN:
return &TransMode_align;
case TFM_SEQ_SLIDE:
return &TransMode_seqslide;
case TFM_NORMAL_ROTATION:
return &TransMode_rotatenormal;
case TFM_GPENCIL_OPACITY:
return &TransMode_gpopacity;
}
return nullptr;
}
void transform_mode_init(TransInfo *t, wmOperator *op, const int mode)
{
t->mode = eTfmMode(mode);
t->mode_info = mode_info_get(t, mode);
if (t->mode_info) {
t->flag |= eTFlag(t->mode_info->flags);
t->mode_info->init_fn(t, op);
}
if (t->data_type == &TransConvertType_Mesh) {
/* Init Custom Data correction.
* Ideally this should be called when creating the TransData. */
transform_convert_mesh_customdatacorrect_init(t);
}
transform_gizmo_3d_model_from_constraint_and_mode_set(t);
/* TODO(@mano-wii): Some of these operations change the `t->mode`.
* This can be bad for Redo. */
// BLI_assert(t->mode == mode);
}
void transform_mode_default_modal_orientation_set(TransInfo *t, int type)
{
/* Currently only these types are supported. */
BLI_assert(ELEM(type, V3D_ORIENT_GLOBAL, V3D_ORIENT_VIEW));
if (t->is_orient_default_overwrite) {
return;
}
if (!(t->flag & T_MODAL)) {
return;
}
if (t->orient[O_DEFAULT].type == type) {
return;
}
View3D *v3d = nullptr;
RegionView3D *rv3d = nullptr;
if ((type == V3D_ORIENT_VIEW) && (t->spacetype == SPACE_VIEW3D) && t->region &&
(t->region->regiontype == RGN_TYPE_WINDOW))
{
v3d = static_cast<View3D *>(t->view);
rv3d = static_cast<RegionView3D *>(t->region->regiondata);
}
t->orient[O_DEFAULT].type = calc_orientation_from_type_ex(t->scene,
t->view_layer,
v3d,
rv3d,
nullptr,
nullptr,
type,
V3D_AROUND_CENTER_BOUNDS,
t->orient[O_DEFAULT].matrix);
if (t->orient_curr == O_DEFAULT) {
/* Update Orientation. */
transform_orientations_current_set(t, O_DEFAULT);
}
}
/** \} */
} // namespace blender::ed::transform