This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/animation/anim_motion_paths.c
Sergey Sharybin 615d5fa2fb Motion paths: Use minimal possible dependency graph
This change makes it so motion paths are using minimal possible
dependency graph which is sufficient to evaluate required motion
path targets.

Disclaimer: granularity is done on ID level, but it is possible
to go more granular if really needed.

Brings time down to 0.5 sec when updating motion path for the
Rain animation file used for benchmarks in the previous commits.

Reviewers: brecht

Differential Revision: https://developer.blender.org/D5874
2019-09-25 14:40:06 +02:00

503 lines
16 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup bke
*/
#include "MEM_guardedalloc.h"
#include <stdlib.h>
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_dlrbTree.h"
#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_scene_types.h"
#include "BKE_animsys.h"
#include "BKE_action.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_query.h"
#include "GPU_batch.h"
#include "GPU_vertex_buffer.h"
#include "ED_anim_api.h"
#include "ED_keyframes_draw.h"
#include "CLG_log.h"
static CLG_LogRef LOG = {"ed.anim.motion_paths"};
/* Motion path needing to be baked (mpt) */
typedef struct MPathTarget {
struct MPathTarget *next, *prev;
bMotionPath *mpath; /* motion path in question */
DLRBT_Tree keys; /* temp, to know where the keyframes are */
/* Original (Source Objects) */
Object *ob; /* source object */
bPoseChannel *pchan; /* source posechannel (if applicable) */
/* "Evaluated" Copies (these come from the background COW copy
* that provide all the coordinates we want to save off). */
Object *ob_eval; /* evaluated object */
} MPathTarget;
/* ........ */
/* update scene for current frame */
static void motionpaths_calc_update_scene(Main *bmain, struct Depsgraph *depsgraph)
{
BKE_scene_graph_update_for_newframe(depsgraph, bmain);
}
Depsgraph *animviz_depsgraph_build(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
ListBase *targets)
{
/* Allocate dependency graph. */
Depsgraph *depsgraph = DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_VIEWPORT);
/* Make a flat array of IDs for the DEG API. */
const int num_ids = BLI_listbase_count(targets);
ID **ids = MEM_malloc_arrayN(sizeof(ID *), num_ids, "animviz IDS");
int current_id_index = 0;
for (MPathTarget *mpt = targets->first; mpt != NULL; mpt = mpt->next) {
ids[current_id_index++] = &mpt->ob->id;
}
/* Build graph from all requested IDs. */
DEG_graph_build_from_ids(depsgraph, bmain, scene, view_layer, ids, num_ids);
MEM_freeN(ids);
/* Update once so we can access pointers of evaluated animation data. */
motionpaths_calc_update_scene(bmain, depsgraph);
return depsgraph;
}
/* get list of motion paths to be baked for the given object
* - assumes the given list is ready to be used
*/
/* TODO: it would be nice in future to be able to update objects dependent on these bones too? */
void animviz_get_object_motionpaths(Object *ob, ListBase *targets)
{
MPathTarget *mpt;
/* object itself first */
if ((ob->avs.recalc & ANIMVIZ_RECALC_PATHS) && (ob->mpath)) {
/* new target for object */
mpt = MEM_callocN(sizeof(MPathTarget), "MPathTarget Ob");
BLI_addtail(targets, mpt);
mpt->mpath = ob->mpath;
mpt->ob = ob;
}
/* bones */
if ((ob->pose) && (ob->pose->avs.recalc & ANIMVIZ_RECALC_PATHS)) {
bArmature *arm = ob->data;
bPoseChannel *pchan;
for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
if ((pchan->bone) && (arm->layer & pchan->bone->layer) && (pchan->mpath)) {
/* new target for bone */
mpt = MEM_callocN(sizeof(MPathTarget), "MPathTarget PoseBone");
BLI_addtail(targets, mpt);
mpt->mpath = pchan->mpath;
mpt->ob = ob;
mpt->pchan = pchan;
}
}
}
}
/* ........ */
/* perform baking for the targets on the current frame */
static void motionpaths_calc_bake_targets(ListBase *targets, int cframe)
{
MPathTarget *mpt;
/* for each target, check if it can be baked on the current frame */
for (mpt = targets->first; mpt; mpt = mpt->next) {
bMotionPath *mpath = mpt->mpath;
/* current frame must be within the range the cache works for
* - is inclusive of the first frame, but not the last otherwise we get buffer overruns
*/
if ((cframe < mpath->start_frame) || (cframe >= mpath->end_frame)) {
continue;
}
/* get the relevant cache vert to write to */
bMotionPathVert *mpv = mpath->points + (cframe - mpath->start_frame);
Object *ob_eval = mpt->ob_eval;
/* Lookup evaluated pose channel, here because the depsgraph
* evaluation can change them so they are not cached in mpt. */
bPoseChannel *pchan_eval = NULL;
if (mpt->pchan) {
pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, mpt->pchan->name);
}
/* pose-channel or object path baking? */
if (pchan_eval) {
/* heads or tails */
if (mpath->flag & MOTIONPATH_FLAG_BHEAD) {
copy_v3_v3(mpv->co, pchan_eval->pose_head);
}
else {
copy_v3_v3(mpv->co, pchan_eval->pose_tail);
}
/* result must be in worldspace */
mul_m4_v3(ob_eval->obmat, mpv->co);
}
else {
/* worldspace object location */
copy_v3_v3(mpv->co, ob_eval->obmat[3]);
}
float mframe = (float)(cframe);
/* Tag if it's a keyframe */
if (BLI_dlrbTree_search_exact(&mpt->keys, compare_ak_cfraPtr, &mframe)) {
mpv->flag |= MOTIONPATH_VERT_KEY;
}
else {
mpv->flag &= ~MOTIONPATH_VERT_KEY;
}
/* Incremental update on evaluated object if possible, for fast updating
* while dragging in transform. */
bMotionPath *mpath_eval = NULL;
if (mpt->pchan) {
mpath_eval = (pchan_eval) ? pchan_eval->mpath : NULL;
}
else {
mpath_eval = ob_eval->mpath;
}
if (mpath_eval && mpath_eval->length == mpath->length) {
bMotionPathVert *mpv_eval = mpath_eval->points + (cframe - mpath_eval->start_frame);
*mpv_eval = *mpv;
GPU_VERTBUF_DISCARD_SAFE(mpath_eval->points_vbo);
GPU_BATCH_DISCARD_SAFE(mpath_eval->batch_line);
GPU_BATCH_DISCARD_SAFE(mpath_eval->batch_points);
}
}
}
/* Get pointer to animviz settings for the given target. */
static bAnimVizSettings *animviz_target_settings_get(MPathTarget *mpt)
{
if (mpt->pchan != NULL) {
return &mpt->ob->pose->avs;
}
return &mpt->ob->avs;
}
static void motionpath_get_global_framerange(ListBase *targets, int *r_sfra, int *r_efra)
{
*r_sfra = INT_MAX;
*r_efra = INT_MIN;
for (MPathTarget *mpt = targets->first; mpt; mpt = mpt->next) {
*r_sfra = min_ii(*r_sfra, mpt->mpath->start_frame);
*r_efra = max_ii(*r_efra, mpt->mpath->end_frame);
}
}
static int motionpath_get_prev_keyframe(MPathTarget *mpt, DLRBT_Tree *fcu_keys, int current_frame)
{
/* If the current frame is outside of the configured motion path range we ignore update of this
* motion path by using invalid frame range where start frame is above the end frame. */
if (current_frame <= mpt->mpath->start_frame) {
return INT_MAX;
}
float current_frame_float = current_frame;
DLRBT_Node *node = BLI_dlrbTree_search_prev(fcu_keys, compare_ak_cfraPtr, &current_frame_float);
if (node == NULL) {
return mpt->mpath->start_frame;
}
ActKeyColumn *key_data = (ActKeyColumn *)node;
return key_data->cfra;
}
static int motionpath_get_prev_prev_keyframe(MPathTarget *mpt,
DLRBT_Tree *fcu_keys,
int current_frame)
{
int frame = motionpath_get_prev_keyframe(mpt, fcu_keys, current_frame);
return motionpath_get_prev_keyframe(mpt, fcu_keys, frame);
}
static int motionpath_get_next_keyframe(MPathTarget *mpt, DLRBT_Tree *fcu_keys, int current_frame)
{
/* If the current frame is outside of the configured motion path range we ignore update of this
* motion path by using invalid frame range where start frame is above the end frame. */
if (current_frame >= mpt->mpath->end_frame) {
return INT_MIN;
}
float current_frame_float = current_frame;
DLRBT_Node *node = BLI_dlrbTree_search_next(fcu_keys, compare_ak_cfraPtr, &current_frame_float);
if (node == NULL) {
return mpt->mpath->end_frame;
}
ActKeyColumn *key_data = (ActKeyColumn *)node;
return key_data->cfra;
}
static int motionpath_get_next_next_keyframe(MPathTarget *mpt,
DLRBT_Tree *fcu_keys,
int current_frame)
{
int frame = motionpath_get_next_keyframe(mpt, fcu_keys, current_frame);
return motionpath_get_next_keyframe(mpt, fcu_keys, frame);
}
static bool motionpath_check_can_use_keyframe_range(MPathTarget *UNUSED(mpt),
AnimData *adt,
ListBase *fcurve_list)
{
if (adt == NULL || fcurve_list == NULL) {
return false;
}
/* NOTE: We might needed to do a full frame range update if there is a specific setup of NLA
* or drivers or modifiers on the f-curves. */
return true;
}
static void motionpath_calculate_update_range(MPathTarget *mpt,
AnimData *adt,
ListBase *fcurve_list,
int current_frame,
int *r_sfra,
int *r_efra)
{
/* Similar to the case when there is only a single keyframe: need to update en entire range to
* a constant value. */
if (!motionpath_check_can_use_keyframe_range(mpt, adt, fcurve_list)) {
*r_sfra = mpt->mpath->start_frame;
*r_efra = mpt->mpath->end_frame;
return;
}
*r_sfra = INT_MAX;
*r_efra = INT_MIN;
/* NOTE: Iterate over individual f-curves, and check their keyframes individually and pick a
* widest range from them. This is because it's possible to have more narrow keyframe on a
* channel which wasn't edited.
* Could be optimized further by storing some flags about which channels has been modified so
* we ignore all others (which can potentially make an update range unnecessary wide). */
for (FCurve *fcu = fcurve_list->first; fcu != NULL; fcu = fcu->next) {
DLRBT_Tree fcu_keys;
BLI_dlrbTree_init(&fcu_keys);
fcurve_to_keylist(adt, fcu, &fcu_keys, 0);
int fcu_sfra = motionpath_get_prev_prev_keyframe(mpt, &fcu_keys, current_frame);
int fcu_efra = motionpath_get_next_next_keyframe(mpt, &fcu_keys, current_frame);
/* Extend range furher, since accelleration compensation propagates even further away. */
if (fcu->auto_smoothing != FCURVE_SMOOTH_NONE) {
fcu_sfra = motionpath_get_prev_prev_keyframe(mpt, &fcu_keys, fcu_sfra);
fcu_efra = motionpath_get_next_next_keyframe(mpt, &fcu_keys, fcu_efra);
}
if (fcu_sfra <= fcu_efra) {
*r_sfra = min_ii(*r_sfra, fcu_sfra);
*r_efra = max_ii(*r_efra, fcu_efra);
}
BLI_dlrbTree_free(&fcu_keys);
}
}
/* Perform baking of the given object's and/or its bones' transforms to motion paths
* - scene: current scene
* - ob: object whose flagged motionpaths should get calculated
* - recalc: whether we need to
*/
/* TODO: include reports pointer? */
void animviz_calc_motionpaths(Depsgraph *depsgraph,
Main *bmain,
Scene *scene,
ListBase *targets,
eAnimvizCalcRange range,
bool restore)
{
/* Sanity check. */
if (ELEM(NULL, targets, targets->first)) {
return;
}
const int cfra = CFRA;
int sfra = INT_MAX, efra = INT_MIN;
switch (range) {
case ANIMVIZ_CALC_RANGE_CURRENT_FRAME:
motionpath_get_global_framerange(targets, &sfra, &efra);
if (sfra > efra) {
return;
}
if (cfra < sfra || cfra > efra) {
return;
}
sfra = efra = cfra;
break;
case ANIMVIZ_CALC_RANGE_CHANGED:
/* Nothing to do here, will be handled later when iterating through the targets. */
break;
case ANIMVIZ_CALC_RANGE_FULL:
motionpath_get_global_framerange(targets, &sfra, &efra);
if (sfra > efra) {
return;
}
break;
}
/* get copies of objects/bones to get the calculated results from
* (for copy-on-write evaluation), so that we actually get some results
*/
/* TODO: Create a copy of background depsgraph that only contain these entities,
* and only evaluates them.
*
* For until that is done we force dependency graph to not be active, so we don't loose unkeyed
* changes during updating the motion path.
* This still doesn't include unkeyed changes to the path itself, but allows to have updates in
* an environment when auto-keying and pose paste is used. */
const bool is_active_depsgraph = DEG_is_active(depsgraph);
if (is_active_depsgraph) {
DEG_make_inactive(depsgraph);
}
for (MPathTarget *mpt = targets->first; mpt; mpt = mpt->next) {
mpt->ob_eval = DEG_get_evaluated_object(depsgraph, mpt->ob);
AnimData *adt = BKE_animdata_from_id(&mpt->ob_eval->id);
/* build list of all keyframes in active action for object or pchan */
BLI_dlrbTree_init(&mpt->keys);
ListBase *fcurve_list = NULL;
if (adt) {
/* get pointer to animviz settings for each target */
bAnimVizSettings *avs = animviz_target_settings_get(mpt);
/* it is assumed that keyframes for bones are all grouped in a single group
* unless an option is set to always use the whole action
*/
if ((mpt->pchan) && (avs->path_viewflag & MOTIONPATH_VIEW_KFACT) == 0) {
bActionGroup *agrp = BKE_action_group_find_name(adt->action, mpt->pchan->name);
if (agrp) {
fcurve_list = &agrp->channels;
agroup_to_keylist(adt, agrp, &mpt->keys, 0);
}
}
else {
fcurve_list = &adt->action->curves;
action_to_keylist(adt, adt->action, &mpt->keys, 0);
}
}
if (range == ANIMVIZ_CALC_RANGE_CHANGED) {
int mpt_sfra, mpt_efra;
motionpath_calculate_update_range(mpt, adt, fcurve_list, cfra, &mpt_sfra, &mpt_efra);
if (mpt_sfra <= mpt_efra) {
sfra = min_ii(sfra, mpt_sfra);
efra = max_ii(efra, mpt_efra);
}
}
}
if (sfra > efra) {
return;
}
/* calculate path over requested range */
CLOG_INFO(&LOG,
1,
"Calculating MotionPaths between frames %d - %d (%d frames)",
sfra,
efra,
efra - sfra + 1);
for (CFRA = sfra; CFRA <= efra; CFRA++) {
if (range == ANIMVIZ_CALC_RANGE_CURRENT_FRAME) {
/* For current frame, only update tagged. */
BKE_scene_graph_update_tagged(depsgraph, bmain);
}
else {
/* Update relevant data for new frame. */
motionpaths_calc_update_scene(bmain, depsgraph);
}
/* perform baking for targets */
motionpaths_calc_bake_targets(targets, CFRA);
}
/* reset original environment */
/* NOTE: We don't always need to reevaluate the main scene, as the depsgraph
* may be a temporary one that works on a subset of the data.
* We always have to restore the current frame though. */
CFRA = cfra;
if (range != ANIMVIZ_CALC_RANGE_CURRENT_FRAME && restore) {
motionpaths_calc_update_scene(bmain, depsgraph);
}
if (is_active_depsgraph) {
DEG_make_active(depsgraph);
}
/* clear recalc flags from targets */
for (MPathTarget *mpt = targets->first; mpt; mpt = mpt->next) {
bMotionPath *mpath = mpt->mpath;
/* get pointer to animviz settings for each target */
bAnimVizSettings *avs = animviz_target_settings_get(mpt);
/* clear the flag requesting recalculation of targets */
avs->recalc &= ~ANIMVIZ_RECALC_PATHS;
/* Clean temp data */
BLI_dlrbTree_free(&mpt->keys);
/* Free previous batches to force update. */
GPU_VERTBUF_DISCARD_SAFE(mpath->points_vbo);
GPU_BATCH_DISCARD_SAFE(mpath->batch_line);
GPU_BATCH_DISCARD_SAFE(mpath->batch_points);
}
}