The `object_to_world` and `world_to_object` matrices are set during depsgraph evaluation, calculated from the object's animated location, rotation, scale, parenting, and constraints. It's confusing and unnecessary to store them with the original data in DNA. This commit moves them to `ObjectRuntime` and moves the matrices to use the C++ `float4x4` type, giving the potential for simplified code using the C++ abstractions. The matrices are accessible with functions on `Object` directly since they are used so commonly. Though for write access, directly using the runtime struct is necessary. The inverse `world_to_object` matrix is often calculated before it's used, even though it's calculated as part of depsgraph evaluation. Long term we might not want to store this in `ObjectRuntime` at all, and just calculate it on demand. Or at least we should remove the redundant calculations. That should be done separately though. Pull Request: https://projects.blender.org/blender/blender/pulls/118210
444 lines
13 KiB
C++
444 lines
13 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*
|
|
* Deform coordinates by a curve object (used by modifier).
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "BLI_math_matrix.h"
|
|
#include "BLI_math_rotation.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_curve_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BKE_anim_path.h"
|
|
#include "BKE_curve.hh"
|
|
#include "BKE_editmesh.hh"
|
|
#include "BKE_modifier.hh"
|
|
#include "BKE_object_types.hh"
|
|
|
|
#include "BKE_deform.hh"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Curve Deform Internal Utilities
|
|
* \{ */
|
|
|
|
/**
|
|
* Calculations is in local space of deformed object
|
|
* so we store matrices to transform points to/from local-space.
|
|
*/
|
|
struct CurveDeform {
|
|
float dmin[3], dmax[3];
|
|
float curvespace[4][4], objectspace[4][4], objectspace3[3][3];
|
|
int no_rot_axis;
|
|
};
|
|
|
|
static void init_curve_deform(const Object *ob_curve, const Object *ob_target, CurveDeform *cd)
|
|
{
|
|
float imat[4][4];
|
|
invert_m4_m4(imat, ob_target->object_to_world().ptr());
|
|
mul_m4_m4m4(cd->objectspace, imat, ob_curve->object_to_world().ptr());
|
|
invert_m4_m4(cd->curvespace, cd->objectspace);
|
|
copy_m3_m4(cd->objectspace3, cd->objectspace);
|
|
cd->no_rot_axis = 0;
|
|
}
|
|
|
|
/**
|
|
* For each point, rotate & translate to curve.
|
|
*
|
|
* \param co: local coord, result local too.
|
|
* \param r_quat: returns quaternion for rotation,
|
|
* using #CurveDeform.no_rot_axis axis is using another define.
|
|
*/
|
|
static bool calc_curve_deform(
|
|
const Object *ob_curve, float co[3], const short axis, const CurveDeform *cd, float r_quat[4])
|
|
{
|
|
const Curve *cu = static_cast<const Curve *>(ob_curve->data);
|
|
float fac, loc[4], dir[3], new_quat[4], radius;
|
|
short index;
|
|
const bool is_neg_axis = (axis > 2);
|
|
|
|
if (ob_curve->runtime->curve_cache == nullptr) {
|
|
/* Happens with a cyclic dependencies. */
|
|
return false;
|
|
}
|
|
|
|
if (ob_curve->runtime->curve_cache->anim_path_accum_length == nullptr) {
|
|
return false; /* happens on append, cyclic dependencies and empty curves */
|
|
}
|
|
|
|
/* options */
|
|
if (is_neg_axis) {
|
|
index = axis - 3;
|
|
if (cu->flag & CU_STRETCH) {
|
|
const float divisor = cd->dmax[index] - cd->dmin[index];
|
|
if (LIKELY(divisor > FLT_EPSILON)) {
|
|
fac = -(co[index] - cd->dmax[index]) / divisor;
|
|
}
|
|
else {
|
|
fac = 0.0f;
|
|
}
|
|
}
|
|
else {
|
|
const CurveCache *cc = ob_curve->runtime->curve_cache;
|
|
float totdist = BKE_anim_path_get_length(cc);
|
|
if (LIKELY(totdist > FLT_EPSILON)) {
|
|
fac = -(co[index] - cd->dmax[index]) / totdist;
|
|
}
|
|
else {
|
|
fac = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
index = axis;
|
|
if (cu->flag & CU_STRETCH) {
|
|
const float divisor = cd->dmax[index] - cd->dmin[index];
|
|
if (LIKELY(divisor > FLT_EPSILON)) {
|
|
fac = (co[index] - cd->dmin[index]) / divisor;
|
|
}
|
|
else {
|
|
fac = 0.0f;
|
|
}
|
|
}
|
|
else {
|
|
const CurveCache *cc = ob_curve->runtime->curve_cache;
|
|
float totdist = BKE_anim_path_get_length(cc);
|
|
if (LIKELY(totdist > FLT_EPSILON)) {
|
|
fac = +(co[index] - cd->dmin[index]) / totdist;
|
|
}
|
|
else {
|
|
fac = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (BKE_where_on_path(ob_curve, fac, loc, dir, new_quat, &radius, nullptr)) { /* returns OK */
|
|
float quat[4], cent[3];
|
|
|
|
if (cd->no_rot_axis) { /* set by caller */
|
|
|
|
/* This is not exactly the same as 2.4x, since the axis is having rotation removed rather
|
|
* than changing the axis before calculating the tilt but serves much the same purpose. */
|
|
float dir_flat[3] = {0, 0, 0}, q[4];
|
|
copy_v3_v3(dir_flat, dir);
|
|
dir_flat[cd->no_rot_axis - 1] = 0.0f;
|
|
|
|
normalize_v3(dir);
|
|
normalize_v3(dir_flat);
|
|
|
|
rotation_between_vecs_to_quat(q, dir, dir_flat); /* Could this be done faster? */
|
|
|
|
mul_qt_qtqt(new_quat, q, new_quat);
|
|
}
|
|
|
|
/* Logic for 'cent' orientation *
|
|
*
|
|
* The way 'co' is copied to 'cent' may seem to have no meaning, but it does.
|
|
*
|
|
* Use a curve modifier to stretch a cube out, color each side RGB,
|
|
* positive side light, negative dark.
|
|
* view with X up (default), from the angle that you can see 3 faces RGB colors (light),
|
|
* anti-clockwise
|
|
* Notice X,Y,Z Up all have light colors and each ordered CCW.
|
|
*
|
|
* Now for Neg Up XYZ, the colors are all dark, and ordered clockwise - Campbell
|
|
*
|
|
* NOTE: moved functions into quat_apply_track/vec_apply_track
|
|
*/
|
|
copy_qt_qt(quat, new_quat);
|
|
copy_v3_v3(cent, co);
|
|
|
|
/* Zero the axis which is not used,
|
|
* the big block of text above now applies to these 3 lines.
|
|
* The `upflag` argument may be a dummy, set so no rotation is done. */
|
|
quat_apply_track(quat, axis, ELEM(axis, 0, 2) ? 1 : 0);
|
|
vec_apply_track(cent, axis);
|
|
cent[index] = 0.0f;
|
|
|
|
/* scale if enabled */
|
|
if (cu->flag & CU_PATH_RADIUS) {
|
|
mul_v3_fl(cent, radius);
|
|
}
|
|
|
|
/* local rotation */
|
|
normalize_qt(quat);
|
|
mul_qt_v3(quat, cent);
|
|
|
|
/* translation */
|
|
add_v3_v3v3(co, cent, loc);
|
|
|
|
if (r_quat) {
|
|
copy_qt_qt(r_quat, quat);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Curve Deform #BKE_curve_deform_coords API
|
|
*
|
|
* #BKE_curve_deform and related functions.
|
|
* \{ */
|
|
|
|
static void curve_deform_coords_impl(const Object *ob_curve,
|
|
const Object *ob_target,
|
|
float (*vert_coords)[3],
|
|
const int vert_coords_len,
|
|
const MDeformVert *dvert,
|
|
const int defgrp_index,
|
|
const short flag,
|
|
const short defaxis,
|
|
BMEditMesh *em_target)
|
|
{
|
|
Curve *cu;
|
|
int a;
|
|
CurveDeform cd;
|
|
const bool is_neg_axis = (defaxis > 2);
|
|
const bool invert_vgroup = (flag & MOD_CURVE_INVERT_VGROUP) != 0;
|
|
bool use_dverts = false;
|
|
int cd_dvert_offset;
|
|
|
|
if (ob_curve->type != OB_CURVES_LEGACY) {
|
|
return;
|
|
}
|
|
|
|
cu = static_cast<Curve *>(ob_curve->data);
|
|
|
|
init_curve_deform(ob_curve, ob_target, &cd);
|
|
|
|
if (cu->flag & CU_DEFORM_BOUNDS_OFF) {
|
|
/* Dummy bounds. */
|
|
if (is_neg_axis == false) {
|
|
cd.dmin[0] = cd.dmin[1] = cd.dmin[2] = 0.0f;
|
|
cd.dmax[0] = cd.dmax[1] = cd.dmax[2] = 1.0f;
|
|
}
|
|
else {
|
|
/* Negative, these bounds give a good rest position. */
|
|
cd.dmin[0] = cd.dmin[1] = cd.dmin[2] = -1.0f;
|
|
cd.dmax[0] = cd.dmax[1] = cd.dmax[2] = 0.0f;
|
|
}
|
|
}
|
|
else {
|
|
/* Set mesh min/max bounds. */
|
|
INIT_MINMAX(cd.dmin, cd.dmax);
|
|
}
|
|
|
|
if (em_target != nullptr) {
|
|
cd_dvert_offset = CustomData_get_offset(&em_target->bm->vdata, CD_MDEFORMVERT);
|
|
if (cd_dvert_offset != -1) {
|
|
use_dverts = true;
|
|
}
|
|
}
|
|
else {
|
|
if (dvert != nullptr) {
|
|
use_dverts = true;
|
|
}
|
|
}
|
|
|
|
if (use_dverts) {
|
|
if (cu->flag & CU_DEFORM_BOUNDS_OFF) {
|
|
|
|
#define DEFORM_OP(dvert) \
|
|
{ \
|
|
const float weight = invert_vgroup ? 1.0f - BKE_defvert_find_weight(dvert, defgrp_index) : \
|
|
BKE_defvert_find_weight(dvert, defgrp_index); \
|
|
if (weight > 0.0f) { \
|
|
float vec[3]; \
|
|
mul_m4_v3(cd.curvespace, vert_coords[a]); \
|
|
copy_v3_v3(vec, vert_coords[a]); \
|
|
calc_curve_deform(ob_curve, vec, defaxis, &cd, nullptr); \
|
|
interp_v3_v3v3(vert_coords[a], vert_coords[a], vec, weight); \
|
|
mul_m4_v3(cd.objectspace, vert_coords[a]); \
|
|
} \
|
|
} \
|
|
((void)0)
|
|
|
|
if (em_target != nullptr) {
|
|
BMIter iter;
|
|
BMVert *v;
|
|
BM_ITER_MESH_INDEX (v, &iter, em_target->bm, BM_VERTS_OF_MESH, a) {
|
|
dvert = static_cast<const MDeformVert *>(BM_ELEM_CD_GET_VOID_P(v, cd_dvert_offset));
|
|
DEFORM_OP(dvert);
|
|
}
|
|
}
|
|
else {
|
|
for (a = 0; a < vert_coords_len; a++) {
|
|
DEFORM_OP(&dvert[a]);
|
|
}
|
|
}
|
|
|
|
#undef DEFORM_OP
|
|
}
|
|
else {
|
|
|
|
#define DEFORM_OP_MINMAX(dvert) \
|
|
{ \
|
|
const float weight = invert_vgroup ? 1.0f - BKE_defvert_find_weight(dvert, defgrp_index) : \
|
|
BKE_defvert_find_weight(dvert, defgrp_index); \
|
|
if (weight > 0.0f) { \
|
|
mul_m4_v3(cd.curvespace, vert_coords[a]); \
|
|
minmax_v3v3_v3(cd.dmin, cd.dmax, vert_coords[a]); \
|
|
} \
|
|
} \
|
|
((void)0)
|
|
|
|
/* Already in 'cd.curvespace', previous for loop. */
|
|
#define DEFORM_OP_CLAMPED(dvert) \
|
|
{ \
|
|
const float weight = invert_vgroup ? 1.0f - BKE_defvert_find_weight(dvert, defgrp_index) : \
|
|
BKE_defvert_find_weight(dvert, defgrp_index); \
|
|
if (weight > 0.0f) { \
|
|
float vec[3]; \
|
|
copy_v3_v3(vec, vert_coords[a]); \
|
|
calc_curve_deform(ob_curve, vec, defaxis, &cd, nullptr); \
|
|
interp_v3_v3v3(vert_coords[a], vert_coords[a], vec, weight); \
|
|
mul_m4_v3(cd.objectspace, vert_coords[a]); \
|
|
} \
|
|
} \
|
|
((void)0)
|
|
|
|
if (em_target != nullptr) {
|
|
BMIter iter;
|
|
BMVert *v;
|
|
BM_ITER_MESH_INDEX (v, &iter, em_target->bm, BM_VERTS_OF_MESH, a) {
|
|
dvert = static_cast<const MDeformVert *>(BM_ELEM_CD_GET_VOID_P(v, cd_dvert_offset));
|
|
DEFORM_OP_MINMAX(dvert);
|
|
}
|
|
|
|
BM_ITER_MESH_INDEX (v, &iter, em_target->bm, BM_VERTS_OF_MESH, a) {
|
|
dvert = static_cast<const MDeformVert *>(BM_ELEM_CD_GET_VOID_P(v, cd_dvert_offset));
|
|
DEFORM_OP_CLAMPED(dvert);
|
|
}
|
|
}
|
|
else {
|
|
|
|
for (a = 0; a < vert_coords_len; a++) {
|
|
DEFORM_OP_MINMAX(&dvert[a]);
|
|
}
|
|
|
|
for (a = 0; a < vert_coords_len; a++) {
|
|
DEFORM_OP_CLAMPED(&dvert[a]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef DEFORM_OP_MINMAX
|
|
#undef DEFORM_OP_CLAMPED
|
|
}
|
|
else {
|
|
if (cu->flag & CU_DEFORM_BOUNDS_OFF) {
|
|
for (a = 0; a < vert_coords_len; a++) {
|
|
mul_m4_v3(cd.curvespace, vert_coords[a]);
|
|
calc_curve_deform(ob_curve, vert_coords[a], defaxis, &cd, nullptr);
|
|
mul_m4_v3(cd.objectspace, vert_coords[a]);
|
|
}
|
|
}
|
|
else {
|
|
for (a = 0; a < vert_coords_len; a++) {
|
|
mul_m4_v3(cd.curvespace, vert_coords[a]);
|
|
minmax_v3v3_v3(cd.dmin, cd.dmax, vert_coords[a]);
|
|
}
|
|
|
|
for (a = 0; a < vert_coords_len; a++) {
|
|
/* Already in 'cd.curvespace', previous for loop. */
|
|
calc_curve_deform(ob_curve, vert_coords[a], defaxis, &cd, nullptr);
|
|
mul_m4_v3(cd.objectspace, vert_coords[a]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_curve_deform_coords(const Object *ob_curve,
|
|
const Object *ob_target,
|
|
float (*vert_coords)[3],
|
|
const int vert_coords_len,
|
|
const MDeformVert *dvert,
|
|
const int defgrp_index,
|
|
const short flag,
|
|
const short defaxis)
|
|
{
|
|
curve_deform_coords_impl(ob_curve,
|
|
ob_target,
|
|
vert_coords,
|
|
vert_coords_len,
|
|
dvert,
|
|
defgrp_index,
|
|
flag,
|
|
defaxis,
|
|
nullptr);
|
|
}
|
|
|
|
void BKE_curve_deform_coords_with_editmesh(const Object *ob_curve,
|
|
const Object *ob_target,
|
|
float (*vert_coords)[3],
|
|
const int vert_coords_len,
|
|
const int defgrp_index,
|
|
const short flag,
|
|
const short defaxis,
|
|
BMEditMesh *em_target)
|
|
{
|
|
curve_deform_coords_impl(ob_curve,
|
|
ob_target,
|
|
vert_coords,
|
|
vert_coords_len,
|
|
nullptr,
|
|
defgrp_index,
|
|
flag,
|
|
defaxis,
|
|
em_target);
|
|
}
|
|
|
|
void BKE_curve_deform_co(const Object *ob_curve,
|
|
const Object *ob_target,
|
|
const float orco[3],
|
|
float vec[3],
|
|
const int no_rot_axis,
|
|
float r_mat[3][3])
|
|
{
|
|
CurveDeform cd;
|
|
float quat[4];
|
|
|
|
if (ob_curve->type != OB_CURVES_LEGACY) {
|
|
unit_m3(r_mat);
|
|
return;
|
|
}
|
|
|
|
init_curve_deform(ob_curve, ob_target, &cd);
|
|
cd.no_rot_axis = no_rot_axis; /* option to only rotate for XY, for example */
|
|
|
|
copy_v3_v3(cd.dmin, orco);
|
|
copy_v3_v3(cd.dmax, orco);
|
|
|
|
mul_m4_v3(cd.curvespace, vec);
|
|
|
|
if (calc_curve_deform(ob_curve, vec, ob_target->trackflag, &cd, quat)) {
|
|
float qmat[3][3];
|
|
|
|
quat_to_mat3(qmat, quat);
|
|
mul_m3_m3m3(r_mat, qmat, cd.objectspace3);
|
|
}
|
|
else {
|
|
unit_m3(r_mat);
|
|
}
|
|
|
|
mul_m4_v3(cd.objectspace, vec);
|
|
}
|
|
|
|
/** \} */
|