We already had the ability to bake fcurves but no way to convert the baked result back without using python. This patch adds and operator that is available now next to the bake operator in the drop down menu, Reviewed By: Sybren Differential Revision: http://developer.blender.org/D6379
2457 lines
71 KiB
C
2457 lines
71 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.
|
|
*
|
|
* The Original Code is Copyright (C) 2009 Blender Foundation, Joshua Leung
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_text_types.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_easing.h"
|
|
#include "BLI_math.h"
|
|
|
|
#include "BKE_anim_data.h"
|
|
#include "BKE_animsys.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_curve.h"
|
|
#include "BKE_fcurve.h"
|
|
#include "BKE_fcurve_driver.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_idprop.h"
|
|
#include "BKE_lib_query.h"
|
|
#include "BKE_nla.h"
|
|
|
|
#include "BLO_read_write.h"
|
|
|
|
#include "RNA_access.h"
|
|
|
|
#include "CLG_log.h"
|
|
|
|
#define SMALL -1.0e-10
|
|
#define SELECT 1
|
|
|
|
static CLG_LogRef LOG = {"bke.fcurve"};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name F-Curve Data Create
|
|
* \{ */
|
|
|
|
FCurve *BKE_fcurve_create(void)
|
|
{
|
|
FCurve *fcu = MEM_callocN(sizeof(FCurve), __func__);
|
|
return fcu;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name F-Curve Data Free
|
|
* \{ */
|
|
|
|
/* Frees the F-Curve itself too, so make sure BLI_remlink is called before calling this... */
|
|
void BKE_fcurve_free(FCurve *fcu)
|
|
{
|
|
if (fcu == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Free curve data. */
|
|
MEM_SAFE_FREE(fcu->bezt);
|
|
MEM_SAFE_FREE(fcu->fpt);
|
|
|
|
/* Free RNA-path, as this were allocated when getting the path string. */
|
|
MEM_SAFE_FREE(fcu->rna_path);
|
|
|
|
/* Free extra data - i.e. modifiers, and driver. */
|
|
fcurve_free_driver(fcu);
|
|
free_fmodifiers(&fcu->modifiers);
|
|
|
|
/* Free the f-curve itself. */
|
|
MEM_freeN(fcu);
|
|
}
|
|
|
|
/* Frees a list of F-Curves. */
|
|
void BKE_fcurves_free(ListBase *list)
|
|
{
|
|
FCurve *fcu, *fcn;
|
|
|
|
/* Sanity check. */
|
|
if (list == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Free data - no need to call remlink before freeing each curve,
|
|
* as we store reference to next, and freeing only touches the curve
|
|
* it's given.
|
|
*/
|
|
for (fcu = list->first; fcu; fcu = fcn) {
|
|
fcn = fcu->next;
|
|
BKE_fcurve_free(fcu);
|
|
}
|
|
|
|
/* Clear pointers just in case. */
|
|
BLI_listbase_clear(list);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name F-Curve Data Copy
|
|
* \{ */
|
|
|
|
/* Duplicate a F-Curve. */
|
|
FCurve *BKE_fcurve_copy(const FCurve *fcu)
|
|
{
|
|
FCurve *fcu_d;
|
|
|
|
/* Sanity check. */
|
|
if (fcu == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Make a copy. */
|
|
fcu_d = MEM_dupallocN(fcu);
|
|
|
|
fcu_d->next = fcu_d->prev = NULL;
|
|
fcu_d->grp = NULL;
|
|
|
|
/* Copy curve data. */
|
|
fcu_d->bezt = MEM_dupallocN(fcu_d->bezt);
|
|
fcu_d->fpt = MEM_dupallocN(fcu_d->fpt);
|
|
|
|
/* Copy rna-path. */
|
|
fcu_d->rna_path = MEM_dupallocN(fcu_d->rna_path);
|
|
|
|
/* Copy driver. */
|
|
fcu_d->driver = fcurve_copy_driver(fcu_d->driver);
|
|
|
|
/* Copy modifiers. */
|
|
copy_fmodifiers(&fcu_d->modifiers, &fcu->modifiers);
|
|
|
|
/* Return new data. */
|
|
return fcu_d;
|
|
}
|
|
|
|
/* Duplicate a list of F-Curves. */
|
|
void BKE_fcurves_copy(ListBase *dst, ListBase *src)
|
|
{
|
|
FCurve *dfcu, *sfcu;
|
|
|
|
/* Sanity checks. */
|
|
if (ELEM(NULL, dst, src)) {
|
|
return;
|
|
}
|
|
|
|
/* Clear destination list first. */
|
|
BLI_listbase_clear(dst);
|
|
|
|
/* Copy one-by-one. */
|
|
for (sfcu = src->first; sfcu; sfcu = sfcu->next) {
|
|
dfcu = BKE_fcurve_copy(sfcu);
|
|
BLI_addtail(dst, dfcu);
|
|
}
|
|
}
|
|
|
|
/** Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of
|
|
* `IDTypeInfo` structure). */
|
|
void BKE_fcurve_foreach_id(FCurve *fcu, LibraryForeachIDData *data)
|
|
{
|
|
ChannelDriver *driver = fcu->driver;
|
|
|
|
if (driver != NULL) {
|
|
LISTBASE_FOREACH (DriverVar *, dvar, &driver->variables) {
|
|
/* only used targets */
|
|
DRIVER_TARGETS_USED_LOOPER_BEGIN (dvar) {
|
|
BKE_LIB_FOREACHID_PROCESS_ID(data, dtar->id, IDWALK_CB_NOP);
|
|
}
|
|
DRIVER_TARGETS_LOOPER_END;
|
|
}
|
|
}
|
|
|
|
LISTBASE_FOREACH (FModifier *, fcm, &fcu->modifiers) {
|
|
switch (fcm->type) {
|
|
case FMODIFIER_TYPE_PYTHON: {
|
|
FMod_Python *fcm_py = (FMod_Python *)fcm->data;
|
|
BKE_LIB_FOREACHID_PROCESS(data, fcm_py->script, IDWALK_CB_NOP);
|
|
|
|
IDP_foreach_property(fcm_py->prop,
|
|
IDP_TYPE_FILTER_ID,
|
|
BKE_lib_query_idpropertiesForeachIDLink_callback,
|
|
data);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ----------------- Finding F-Curves -------------------------- */
|
|
|
|
/* High level function to get an fcurve from C without having the RNA. */
|
|
FCurve *id_data_find_fcurve(
|
|
ID *id, void *data, StructRNA *type, const char *prop_name, int index, bool *r_driven)
|
|
{
|
|
/* Anim vars */
|
|
AnimData *adt = BKE_animdata_from_id(id);
|
|
FCurve *fcu = NULL;
|
|
|
|
/* Rna vars */
|
|
PointerRNA ptr;
|
|
PropertyRNA *prop;
|
|
char *path;
|
|
|
|
if (r_driven) {
|
|
*r_driven = false;
|
|
}
|
|
|
|
/* Only use the current action ??? */
|
|
if (ELEM(NULL, adt, adt->action)) {
|
|
return NULL;
|
|
}
|
|
|
|
RNA_pointer_create(id, type, data, &ptr);
|
|
prop = RNA_struct_find_property(&ptr, prop_name);
|
|
if (prop == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
path = RNA_path_from_ID_to_property(&ptr, prop);
|
|
if (path == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Animation takes priority over drivers. */
|
|
if (adt->action && adt->action->curves.first) {
|
|
fcu = BKE_fcurve_find(&adt->action->curves, path, index);
|
|
}
|
|
|
|
/* If not animated, check if driven. */
|
|
if (fcu == NULL && adt->drivers.first) {
|
|
fcu = BKE_fcurve_find(&adt->drivers, path, index);
|
|
if (fcu && r_driven) {
|
|
*r_driven = true;
|
|
}
|
|
fcu = NULL;
|
|
}
|
|
|
|
MEM_freeN(path);
|
|
|
|
return fcu;
|
|
}
|
|
|
|
/* Find the F-Curve affecting the given RNA-access path + index,
|
|
* in the list of F-Curves provided. */
|
|
FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], const int array_index)
|
|
{
|
|
FCurve *fcu;
|
|
|
|
/* Sanity checks. */
|
|
if (ELEM(NULL, list, rna_path) || (array_index < 0)) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Check paths of curves, then array indices... */
|
|
for (fcu = list->first; fcu; fcu = fcu->next) {
|
|
/* Simple string-compare (this assumes that they have the same root...) */
|
|
if (fcu->rna_path && STREQ(fcu->rna_path, rna_path)) {
|
|
/* Now check indices. */
|
|
if (fcu->array_index == array_index) {
|
|
return fcu;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Quick way to loop over all fcurves of a given 'path'. */
|
|
FCurve *BKE_fcurve_iter_step(FCurve *fcu_iter, const char rna_path[])
|
|
{
|
|
FCurve *fcu;
|
|
|
|
/* Sanity checks. */
|
|
if (ELEM(NULL, fcu_iter, rna_path)) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Check paths of curves, then array indices... */
|
|
for (fcu = fcu_iter; fcu; fcu = fcu->next) {
|
|
/* Simple string-compare (this assumes that they have the same root...) */
|
|
if (fcu->rna_path && STREQ(fcu->rna_path, rna_path)) {
|
|
return fcu;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Get list of LinkData's containing pointers to the F-Curves
|
|
* which control the types of data indicated.
|
|
*
|
|
* Lists...
|
|
* - dst: list of LinkData's matching the criteria returned.
|
|
* List must be freed after use, and is assumed to be empty when passed.
|
|
* - src: list of F-Curves to search through
|
|
* Filters...
|
|
* - dataPrefix: i.e. 'pose.bones[' or 'nodes['
|
|
* - dataName: name of entity within "" immediately following the prefix
|
|
*/
|
|
int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, const char *dataName)
|
|
{
|
|
FCurve *fcu;
|
|
int matches = 0;
|
|
|
|
/* Sanity checks. */
|
|
if (ELEM(NULL, dst, src, dataPrefix, dataName)) {
|
|
return 0;
|
|
}
|
|
if ((dataPrefix[0] == 0) || (dataName[0] == 0)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Search each F-Curve one by one. */
|
|
for (fcu = src->first; fcu; fcu = fcu->next) {
|
|
/* Check if quoted string matches the path. */
|
|
if (fcu->rna_path == NULL || !strstr(fcu->rna_path, dataPrefix)) {
|
|
continue;
|
|
}
|
|
|
|
char *quotedName = BLI_str_quoted_substrN(fcu->rna_path, dataPrefix);
|
|
if (quotedName == NULL) {
|
|
continue;
|
|
}
|
|
|
|
/* Check if the quoted name matches the required name. */
|
|
if (STREQ(quotedName, dataName)) {
|
|
LinkData *ld = MEM_callocN(sizeof(LinkData), __func__);
|
|
|
|
ld->data = fcu;
|
|
BLI_addtail(dst, ld);
|
|
|
|
matches++;
|
|
}
|
|
|
|
/* Always free the quoted string, since it needs freeing. */
|
|
MEM_freeN(quotedName);
|
|
}
|
|
/* Return the number of matches. */
|
|
return matches;
|
|
}
|
|
|
|
FCurve *BKE_fcurve_find_by_rna(PointerRNA *ptr,
|
|
PropertyRNA *prop,
|
|
int rnaindex,
|
|
AnimData **r_adt,
|
|
bAction **r_action,
|
|
bool *r_driven,
|
|
bool *r_special)
|
|
{
|
|
return BKE_fcurve_find_by_rna_context_ui(
|
|
NULL, ptr, prop, rnaindex, r_adt, r_action, r_driven, r_special);
|
|
}
|
|
|
|
FCurve *BKE_fcurve_find_by_rna_context_ui(bContext *C,
|
|
PointerRNA *ptr,
|
|
PropertyRNA *prop,
|
|
int rnaindex,
|
|
AnimData **r_animdata,
|
|
bAction **r_action,
|
|
bool *r_driven,
|
|
bool *r_special)
|
|
{
|
|
FCurve *fcu = NULL;
|
|
PointerRNA tptr = *ptr;
|
|
|
|
*r_driven = false;
|
|
*r_special = false;
|
|
|
|
if (r_animdata) {
|
|
*r_animdata = NULL;
|
|
}
|
|
if (r_action) {
|
|
*r_action = NULL;
|
|
}
|
|
|
|
/* Special case for NLA Control Curves... */
|
|
if (BKE_nlastrip_has_curves_for_property(ptr, prop)) {
|
|
NlaStrip *strip = ptr->data;
|
|
|
|
/* Set the special flag, since it cannot be a normal action/driver
|
|
* if we've been told to start looking here...
|
|
*/
|
|
*r_special = true;
|
|
|
|
/* The F-Curve either exists or it doesn't here... */
|
|
fcu = BKE_fcurve_find(&strip->fcurves, RNA_property_identifier(prop), rnaindex);
|
|
return fcu;
|
|
}
|
|
|
|
/* There must be some RNA-pointer + property combo. */
|
|
if (prop && tptr.owner_id && RNA_property_animateable(&tptr, prop)) {
|
|
AnimData *adt = BKE_animdata_from_id(tptr.owner_id);
|
|
int step = (
|
|
/* Always 1 in case we have no context (can't check in 'ancestors' of given RNA ptr). */
|
|
C ? 2 : 1);
|
|
char *path = NULL;
|
|
|
|
if (!adt && C) {
|
|
path = BKE_animdata_driver_path_hack(C, &tptr, prop, NULL);
|
|
adt = BKE_animdata_from_id(tptr.owner_id);
|
|
step--;
|
|
}
|
|
|
|
/* Standard F-Curve - Animation (Action) or Drivers. */
|
|
while (adt && step--) {
|
|
if ((adt->action == NULL || adt->action->curves.first == NULL) &&
|
|
(adt->drivers.first == NULL)) {
|
|
continue;
|
|
}
|
|
|
|
/* XXX This function call can become a performance bottleneck. */
|
|
if (step) {
|
|
path = RNA_path_from_ID_to_property(&tptr, prop);
|
|
}
|
|
if (path == NULL) {
|
|
continue;
|
|
}
|
|
|
|
/* XXX: The logic here is duplicated with a function up above. */
|
|
/* animation takes priority over drivers. */
|
|
if (adt->action && adt->action->curves.first) {
|
|
fcu = BKE_fcurve_find(&adt->action->curves, path, rnaindex);
|
|
|
|
if (fcu && r_action) {
|
|
*r_action = adt->action;
|
|
}
|
|
}
|
|
|
|
/* If not animated, check if driven. */
|
|
if (!fcu && (adt->drivers.first)) {
|
|
fcu = BKE_fcurve_find(&adt->drivers, path, rnaindex);
|
|
|
|
if (fcu) {
|
|
if (r_animdata) {
|
|
*r_animdata = adt;
|
|
}
|
|
*r_driven = true;
|
|
}
|
|
}
|
|
|
|
if (fcu && r_action) {
|
|
if (r_animdata) {
|
|
*r_animdata = adt;
|
|
}
|
|
*r_action = adt->action;
|
|
break;
|
|
}
|
|
|
|
if (step) {
|
|
char *tpath = BKE_animdata_driver_path_hack(C, &tptr, prop, path);
|
|
if (tpath && tpath != path) {
|
|
MEM_freeN(path);
|
|
path = tpath;
|
|
adt = BKE_animdata_from_id(tptr.owner_id);
|
|
}
|
|
else {
|
|
adt = NULL;
|
|
}
|
|
}
|
|
}
|
|
MEM_SAFE_FREE(path);
|
|
}
|
|
|
|
return fcu;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Finding Keyframes/Extents
|
|
* \{ */
|
|
|
|
/* Binary search algorithm for finding where to insert BezTriple,
|
|
* with optional argument for precision required.
|
|
* Returns the index to insert at (data already at that index will be offset if replace is 0)
|
|
*/
|
|
static int BKE_fcurve_bezt_binarysearch_index_ex(
|
|
BezTriple array[], float frame, int arraylen, float threshold, bool *r_replace)
|
|
{
|
|
int start = 0, end = arraylen;
|
|
int loopbreaker = 0, maxloop = arraylen * 2;
|
|
|
|
/* Initialize replace-flag first. */
|
|
*r_replace = false;
|
|
|
|
/* Sneaky optimizations (don't go through searching process if...):
|
|
* - Keyframe to be added is to be added out of current bounds.
|
|
* - Keyframe to be added would replace one of the existing ones on bounds.
|
|
*/
|
|
if ((arraylen <= 0) || (array == NULL)) {
|
|
CLOG_WARN(&LOG, "encountered invalid array");
|
|
return 0;
|
|
}
|
|
|
|
/* Check whether to add before/after/on. */
|
|
float framenum;
|
|
|
|
/* 'First' Keyframe (when only one keyframe, this case is used) */
|
|
framenum = array[0].vec[1][0];
|
|
if (IS_EQT(frame, framenum, threshold)) {
|
|
*r_replace = true;
|
|
return 0;
|
|
}
|
|
if (frame < framenum) {
|
|
return 0;
|
|
}
|
|
|
|
/* 'Last' Keyframe */
|
|
framenum = array[(arraylen - 1)].vec[1][0];
|
|
if (IS_EQT(frame, framenum, threshold)) {
|
|
*r_replace = true;
|
|
return (arraylen - 1);
|
|
}
|
|
if (frame > framenum) {
|
|
return arraylen;
|
|
}
|
|
|
|
/* Most of the time, this loop is just to find where to put it
|
|
* 'loopbreaker' is just here to prevent infinite loops.
|
|
*/
|
|
for (loopbreaker = 0; (start <= end) && (loopbreaker < maxloop); loopbreaker++) {
|
|
/* Compute and get midpoint. */
|
|
|
|
/* We calculate the midpoint this way to avoid int overflows... */
|
|
int mid = start + ((end - start) / 2);
|
|
|
|
float midfra = array[mid].vec[1][0];
|
|
|
|
/* Check if exactly equal to midpoint. */
|
|
if (IS_EQT(frame, midfra, threshold)) {
|
|
*r_replace = true;
|
|
return mid;
|
|
}
|
|
|
|
/* Repeat in upper/lower half. */
|
|
if (frame > midfra) {
|
|
start = mid + 1;
|
|
}
|
|
else if (frame < midfra) {
|
|
end = mid - 1;
|
|
}
|
|
}
|
|
|
|
/* Print error if loop-limit exceeded. */
|
|
if (loopbreaker == (maxloop - 1)) {
|
|
CLOG_ERROR(&LOG, "search taking too long");
|
|
|
|
/* Include debug info. */
|
|
CLOG_ERROR(&LOG,
|
|
"\tround = %d: start = %d, end = %d, arraylen = %d",
|
|
loopbreaker,
|
|
start,
|
|
end,
|
|
arraylen);
|
|
}
|
|
|
|
/* Not found, so return where to place it. */
|
|
return start;
|
|
}
|
|
|
|
/* Binary search algorithm for finding where to insert BezTriple. (for use by insert_bezt_fcurve)
|
|
* Returns the index to insert at (data already at that index will be offset if replace is 0)
|
|
*/
|
|
int BKE_fcurve_bezt_binarysearch_index(BezTriple array[],
|
|
float frame,
|
|
int arraylen,
|
|
bool *r_replace)
|
|
{
|
|
/* This is just a wrapper which uses the default threshold. */
|
|
return BKE_fcurve_bezt_binarysearch_index_ex(
|
|
array, frame, arraylen, BEZT_BINARYSEARCH_THRESH, r_replace);
|
|
}
|
|
|
|
/* ...................................... */
|
|
|
|
/* Helper for calc_fcurve_* functions -> find first and last BezTriple to be used. */
|
|
static short get_fcurve_end_keyframes(FCurve *fcu,
|
|
BezTriple **first,
|
|
BezTriple **last,
|
|
const bool do_sel_only)
|
|
{
|
|
bool found = false;
|
|
|
|
/* Init outputs. */
|
|
*first = NULL;
|
|
*last = NULL;
|
|
|
|
/* Sanity checks. */
|
|
if (fcu->bezt == NULL) {
|
|
return found;
|
|
}
|
|
|
|
/* Only include selected items? */
|
|
if (do_sel_only) {
|
|
BezTriple *bezt;
|
|
|
|
/* Find first selected. */
|
|
bezt = fcu->bezt;
|
|
for (int i = 0; i < fcu->totvert; bezt++, i++) {
|
|
if (BEZT_ISSEL_ANY(bezt)) {
|
|
*first = bezt;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Find last selected. */
|
|
bezt = ARRAY_LAST_ITEM(fcu->bezt, BezTriple, fcu->totvert);
|
|
for (int i = 0; i < fcu->totvert; bezt--, i++) {
|
|
if (BEZT_ISSEL_ANY(bezt)) {
|
|
*last = bezt;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* Use the whole array. */
|
|
*first = fcu->bezt;
|
|
*last = ARRAY_LAST_ITEM(fcu->bezt, BezTriple, fcu->totvert);
|
|
found = true;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/* Calculate the extents of F-Curve's data. */
|
|
bool BKE_fcurve_calc_bounds(FCurve *fcu,
|
|
float *xmin,
|
|
float *xmax,
|
|
float *ymin,
|
|
float *ymax,
|
|
const bool do_sel_only,
|
|
const bool include_handles)
|
|
{
|
|
float xminv = 999999999.0f, xmaxv = -999999999.0f;
|
|
float yminv = 999999999.0f, ymaxv = -999999999.0f;
|
|
bool foundvert = false;
|
|
|
|
if (fcu->totvert) {
|
|
if (fcu->bezt) {
|
|
BezTriple *bezt_first = NULL, *bezt_last = NULL;
|
|
|
|
if (xmin || xmax) {
|
|
/* Get endpoint keyframes. */
|
|
foundvert = get_fcurve_end_keyframes(fcu, &bezt_first, &bezt_last, do_sel_only);
|
|
|
|
if (bezt_first) {
|
|
BLI_assert(bezt_last != NULL);
|
|
|
|
if (include_handles) {
|
|
xminv = min_fff(xminv, bezt_first->vec[0][0], bezt_first->vec[1][0]);
|
|
xmaxv = max_fff(xmaxv, bezt_last->vec[1][0], bezt_last->vec[2][0]);
|
|
}
|
|
else {
|
|
xminv = min_ff(xminv, bezt_first->vec[1][0]);
|
|
xmaxv = max_ff(xmaxv, bezt_last->vec[1][0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Only loop over keyframes to find extents for values if needed. */
|
|
if (ymin || ymax) {
|
|
BezTriple *bezt, *prevbezt = NULL;
|
|
|
|
int i;
|
|
for (bezt = fcu->bezt, i = 0; i < fcu->totvert; prevbezt = bezt, bezt++, i++) {
|
|
if ((do_sel_only == false) || BEZT_ISSEL_ANY(bezt)) {
|
|
/* Keyframe itself. */
|
|
yminv = min_ff(yminv, bezt->vec[1][1]);
|
|
ymaxv = max_ff(ymaxv, bezt->vec[1][1]);
|
|
|
|
if (include_handles) {
|
|
/* Left handle - only if applicable.
|
|
* NOTE: for the very first keyframe,
|
|
* the left handle actually has no bearings on anything. */
|
|
if (prevbezt && (prevbezt->ipo == BEZT_IPO_BEZ)) {
|
|
yminv = min_ff(yminv, bezt->vec[0][1]);
|
|
ymaxv = max_ff(ymaxv, bezt->vec[0][1]);
|
|
}
|
|
|
|
/* Right handle - only if applicable. */
|
|
if (bezt->ipo == BEZT_IPO_BEZ) {
|
|
yminv = min_ff(yminv, bezt->vec[2][1]);
|
|
ymaxv = max_ff(ymaxv, bezt->vec[2][1]);
|
|
}
|
|
}
|
|
|
|
foundvert = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (fcu->fpt) {
|
|
/* Frame range can be directly calculated from end verts. */
|
|
if (xmin || xmax) {
|
|
xminv = min_ff(xminv, fcu->fpt[0].vec[0]);
|
|
xmaxv = max_ff(xmaxv, fcu->fpt[fcu->totvert - 1].vec[0]);
|
|
}
|
|
|
|
/* Only loop over keyframes to find extents for values if needed. */
|
|
if (ymin || ymax) {
|
|
FPoint *fpt;
|
|
int i;
|
|
|
|
for (fpt = fcu->fpt, i = 0; i < fcu->totvert; fpt++, i++) {
|
|
if (fpt->vec[1] < yminv) {
|
|
yminv = fpt->vec[1];
|
|
}
|
|
if (fpt->vec[1] > ymaxv) {
|
|
ymaxv = fpt->vec[1];
|
|
}
|
|
|
|
foundvert = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundvert) {
|
|
if (xmin) {
|
|
*xmin = xminv;
|
|
}
|
|
if (xmax) {
|
|
*xmax = xmaxv;
|
|
}
|
|
|
|
if (ymin) {
|
|
*ymin = yminv;
|
|
}
|
|
if (ymax) {
|
|
*ymax = ymaxv;
|
|
}
|
|
}
|
|
else {
|
|
if (G.debug & G_DEBUG) {
|
|
printf("F-Curve calc bounds didn't find anything, so assuming minimum bounds of 1.0\n");
|
|
}
|
|
|
|
if (xmin) {
|
|
*xmin = 0.0f;
|
|
}
|
|
if (xmax) {
|
|
*xmax = 1.0f;
|
|
}
|
|
|
|
if (ymin) {
|
|
*ymin = 0.0f;
|
|
}
|
|
if (ymax) {
|
|
*ymax = 1.0f;
|
|
}
|
|
}
|
|
|
|
return foundvert;
|
|
}
|
|
|
|
/* Calculate the extents of F-Curve's keyframes. */
|
|
bool BKE_fcurve_calc_range(
|
|
FCurve *fcu, float *start, float *end, const bool do_sel_only, const bool do_min_length)
|
|
{
|
|
float min = 999999999.0f, max = -999999999.0f;
|
|
bool foundvert = false;
|
|
|
|
if (fcu->totvert) {
|
|
if (fcu->bezt) {
|
|
BezTriple *bezt_first = NULL, *bezt_last = NULL;
|
|
|
|
/* Get endpoint keyframes. */
|
|
get_fcurve_end_keyframes(fcu, &bezt_first, &bezt_last, do_sel_only);
|
|
|
|
if (bezt_first) {
|
|
BLI_assert(bezt_last != NULL);
|
|
|
|
min = min_ff(min, bezt_first->vec[1][0]);
|
|
max = max_ff(max, bezt_last->vec[1][0]);
|
|
|
|
foundvert = true;
|
|
}
|
|
}
|
|
else if (fcu->fpt) {
|
|
min = min_ff(min, fcu->fpt[0].vec[0]);
|
|
max = max_ff(max, fcu->fpt[fcu->totvert - 1].vec[0]);
|
|
|
|
foundvert = true;
|
|
}
|
|
}
|
|
|
|
if (foundvert == false) {
|
|
min = max = 0.0f;
|
|
}
|
|
|
|
if (do_min_length) {
|
|
/* Minimum length is 1 frame. */
|
|
if (min == max) {
|
|
max += 1.0f;
|
|
}
|
|
}
|
|
|
|
*start = min;
|
|
*end = max;
|
|
|
|
return foundvert;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Active Keyframe
|
|
* \{ */
|
|
|
|
/**
|
|
* Set the index that stores the FCurve's active keyframe, assuming that \a active_bezt
|
|
* is already part of `fcu->bezt`. If NULL, set active keyframe index to "none."
|
|
*/
|
|
void BKE_fcurve_active_keyframe_set(FCurve *fcu, const BezTriple *active_bezt)
|
|
{
|
|
/* The active keyframe should always be selected. */
|
|
BLI_assert((active_bezt == NULL) ||
|
|
((active_bezt->f1 | active_bezt->f2 | active_bezt->f3) & SELECT));
|
|
fcu->active_keyframe_index = (active_bezt == NULL) ? FCURVE_ACTIVE_KEYFRAME_NONE :
|
|
active_bezt - fcu->bezt;
|
|
}
|
|
|
|
/**
|
|
* Get the active keyframe index, with sanity checks for point bounds.
|
|
*/
|
|
int BKE_fcurve_active_keyframe_index(const FCurve *fcu)
|
|
{
|
|
const int active_keyframe_index = fcu->active_keyframe_index;
|
|
|
|
/* Array access boundary checks. */
|
|
if ((fcu->bezt == NULL) || (active_keyframe_index >= fcu->totvert) ||
|
|
(active_keyframe_index < 0)) {
|
|
return FCURVE_ACTIVE_KEYFRAME_NONE;
|
|
}
|
|
|
|
const BezTriple *active_bezt = &fcu->bezt[active_keyframe_index];
|
|
if (((active_bezt->f1 | active_bezt->f2 | active_bezt->f3) & SELECT) == 0) {
|
|
/* The active keyframe should always be selected. If it's not selected, it can't be active. */
|
|
return FCURVE_ACTIVE_KEYFRAME_NONE;
|
|
}
|
|
|
|
return active_keyframe_index;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Status Checks
|
|
* \{ */
|
|
|
|
/* Are keyframes on F-Curve of any use?
|
|
* Usability of keyframes refers to whether they should be displayed,
|
|
* and also whether they will have any influence on the final result.
|
|
*/
|
|
bool BKE_fcurve_are_keyframes_usable(FCurve *fcu)
|
|
{
|
|
/* F-Curve must exist. */
|
|
if (fcu == NULL) {
|
|
return false;
|
|
}
|
|
|
|
/* F-Curve must not have samples - samples are mutually exclusive of keyframes. */
|
|
if (fcu->fpt) {
|
|
return false;
|
|
}
|
|
|
|
/* If it has modifiers, none of these should "drastically" alter the curve. */
|
|
if (fcu->modifiers.first) {
|
|
FModifier *fcm;
|
|
|
|
/* Check modifiers from last to first, as last will be more influential. */
|
|
/* TODO: optionally, only check modifier if it is the active one... (Joshua Leung 2010) */
|
|
for (fcm = fcu->modifiers.last; fcm; fcm = fcm->prev) {
|
|
/* Ignore if muted/disabled. */
|
|
if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED)) {
|
|
continue;
|
|
}
|
|
|
|
/* Type checks. */
|
|
switch (fcm->type) {
|
|
/* Clearly harmless - do nothing. */
|
|
case FMODIFIER_TYPE_CYCLES:
|
|
case FMODIFIER_TYPE_STEPPED:
|
|
case FMODIFIER_TYPE_NOISE:
|
|
break;
|
|
|
|
/* Sometimes harmful - depending on whether they're "additive" or not. */
|
|
case FMODIFIER_TYPE_GENERATOR: {
|
|
FMod_Generator *data = (FMod_Generator *)fcm->data;
|
|
|
|
if ((data->flag & FCM_GENERATOR_ADDITIVE) == 0) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case FMODIFIER_TYPE_FN_GENERATOR: {
|
|
FMod_FunctionGenerator *data = (FMod_FunctionGenerator *)fcm->data;
|
|
|
|
if ((data->flag & FCM_GENERATOR_ADDITIVE) == 0) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
/* Always harmful - cannot allow. */
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Keyframes are usable. */
|
|
return true;
|
|
}
|
|
|
|
bool BKE_fcurve_is_protected(FCurve *fcu)
|
|
{
|
|
return ((fcu->flag & FCURVE_PROTECTED) || ((fcu->grp) && (fcu->grp->flag & AGRP_PROTECTED)));
|
|
}
|
|
|
|
/* Can keyframes be added to F-Curve?
|
|
* Keyframes can only be added if they are already visible.
|
|
*/
|
|
bool BKE_fcurve_is_keyframable(FCurve *fcu)
|
|
{
|
|
/* F-Curve's keyframes must be "usable" (i.e. visible + have an effect on final result) */
|
|
if (BKE_fcurve_are_keyframes_usable(fcu) == 0) {
|
|
return false;
|
|
}
|
|
|
|
/* F-Curve must currently be editable too. */
|
|
if (BKE_fcurve_is_protected(fcu)) {
|
|
return false;
|
|
}
|
|
|
|
/* F-Curve is keyframable. */
|
|
return true;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Keyframe Column Tools
|
|
* \{ */
|
|
|
|
/* Add a BezTriple to a column. */
|
|
static void UNUSED_FUNCTION(bezt_add_to_cfra_elem)(ListBase *lb, BezTriple *bezt)
|
|
{
|
|
CfraElem *ce, *cen;
|
|
|
|
for (ce = lb->first; ce; ce = ce->next) {
|
|
/* Double key? */
|
|
if (IS_EQT(ce->cfra, bezt->vec[1][0], BEZT_BINARYSEARCH_THRESH)) {
|
|
if (bezt->f2 & SELECT) {
|
|
ce->sel = bezt->f2;
|
|
}
|
|
return;
|
|
}
|
|
/* Should key be inserted before this column? */
|
|
if (ce->cfra > bezt->vec[1][0]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Create a new column */
|
|
cen = MEM_callocN(sizeof(CfraElem), "add_to_cfra_elem");
|
|
if (ce) {
|
|
BLI_insertlinkbefore(lb, ce, cen);
|
|
}
|
|
else {
|
|
BLI_addtail(lb, cen);
|
|
}
|
|
|
|
cen->cfra = bezt->vec[1][0];
|
|
cen->sel = bezt->f2;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Samples Utilities
|
|
* \{ */
|
|
|
|
/* Some utilities for working with FPoints (i.e. 'sampled' animation curve data, such as
|
|
* data imported from BVH/Mocap files), which are specialized for use with high density datasets,
|
|
* which BezTriples/Keyframe data are ill equipped to do.
|
|
*/
|
|
|
|
/* Basic sampling callback which acts as a wrapper for evaluate_fcurve()
|
|
* 'data' arg here is unneeded here...
|
|
*/
|
|
float fcurve_samplingcb_evalcurve(FCurve *fcu, void *UNUSED(data), float evaltime)
|
|
{
|
|
/* Assume any interference from drivers on the curve is intended... */
|
|
return evaluate_fcurve(fcu, evaltime);
|
|
}
|
|
|
|
/* Main API function for creating a set of sampled curve data, given some callback function
|
|
* used to retrieve the values to store.
|
|
*/
|
|
void fcurve_store_samples(FCurve *fcu, void *data, int start, int end, FcuSampleFunc sample_cb)
|
|
{
|
|
FPoint *fpt, *new_fpt;
|
|
int cfra;
|
|
|
|
/* Sanity checks. */
|
|
/* TODO: make these tests report errors using reports not CLOG's (Joshua Leung 2009) */
|
|
if (ELEM(NULL, fcu, sample_cb)) {
|
|
CLOG_ERROR(&LOG, "No F-Curve with F-Curve Modifiers to Bake");
|
|
return;
|
|
}
|
|
if (start > end) {
|
|
CLOG_ERROR(&LOG, "Error: Frame range for Sampled F-Curve creation is inappropriate");
|
|
return;
|
|
}
|
|
|
|
/* Set up sample data. */
|
|
fpt = new_fpt = MEM_callocN(sizeof(FPoint) * (end - start + 1), "FPoint Samples");
|
|
|
|
/* Use the sampling callback at 1-frame intervals from start to end frames. */
|
|
for (cfra = start; cfra <= end; cfra++, fpt++) {
|
|
fpt->vec[0] = (float)cfra;
|
|
fpt->vec[1] = sample_cb(fcu, data, (float)cfra);
|
|
}
|
|
|
|
/* Free any existing sample/keyframe data on curve. */
|
|
if (fcu->bezt) {
|
|
MEM_freeN(fcu->bezt);
|
|
}
|
|
if (fcu->fpt) {
|
|
MEM_freeN(fcu->fpt);
|
|
}
|
|
|
|
/* Store the samples. */
|
|
fcu->bezt = NULL;
|
|
fcu->fpt = new_fpt;
|
|
fcu->totvert = end - start + 1;
|
|
}
|
|
|
|
static void init_unbaked_bezt_data(BezTriple *bezt)
|
|
{
|
|
bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
|
|
/* Baked FCurve points always use linear interpolation. */
|
|
bezt->ipo = BEZT_IPO_LIN;
|
|
bezt->h1 = bezt->h2 = HD_AUTO_ANIM;
|
|
}
|
|
|
|
/* Convert baked/sampled fcurves into bezt/regular fcurves. */
|
|
void fcurve_samples_to_keyframes(FCurve *fcu, const int start, const int end)
|
|
{
|
|
|
|
/* Sanity checks. */
|
|
/* TODO: make these tests report errors using reports not CLOG's (Joshua Leung 2009). */
|
|
if (fcu == NULL) {
|
|
CLOG_ERROR(&LOG, "No F-Curve with F-Curve Modifiers to Un-Bake");
|
|
return;
|
|
}
|
|
|
|
if (start > end) {
|
|
CLOG_ERROR(&LOG, "Error: Frame range to unbake F-Curve is inappropriate");
|
|
return;
|
|
}
|
|
|
|
if (fcu->fpt == NULL) {
|
|
/* No data to unbake. */
|
|
CLOG_ERROR(&LOG, "Error: Curve containts no baked keyframes");
|
|
return;
|
|
}
|
|
|
|
/* Free any existing sample/keyframe data on the curve. */
|
|
if (fcu->bezt) {
|
|
MEM_freeN(fcu->bezt);
|
|
}
|
|
|
|
BezTriple *bezt;
|
|
FPoint *fpt = fcu->fpt;
|
|
int keyframes_to_insert = end - start;
|
|
int sample_points = fcu->totvert;
|
|
|
|
bezt = fcu->bezt = MEM_callocN(sizeof(*fcu->bezt) * (size_t)keyframes_to_insert, __func__);
|
|
fcu->totvert = keyframes_to_insert;
|
|
|
|
/* Get first sample point to 'copy' as keyframe. */
|
|
for (; sample_points && (fpt->vec[0] < start); fpt++, sample_points--) {
|
|
/* pass */
|
|
}
|
|
|
|
/* Current position in the timeline. */
|
|
int cur_pos = start;
|
|
|
|
/* Add leading dummy flat points if needed. */
|
|
for (; keyframes_to_insert && (fpt->vec[0] > start); cur_pos++, bezt++, keyframes_to_insert--) {
|
|
init_unbaked_bezt_data(bezt);
|
|
bezt->vec[1][0] = (float)cur_pos;
|
|
bezt->vec[1][1] = fpt->vec[1];
|
|
}
|
|
|
|
/* Copy actual sample points. */
|
|
for (; keyframes_to_insert && sample_points;
|
|
cur_pos++, bezt++, keyframes_to_insert--, fpt++, sample_points--) {
|
|
init_unbaked_bezt_data(bezt);
|
|
copy_v2_v2(bezt->vec[1], fpt->vec);
|
|
}
|
|
|
|
/* Add trailing dummy flat points if needed. */
|
|
for (fpt--; keyframes_to_insert; cur_pos++, bezt++, keyframes_to_insert--) {
|
|
init_unbaked_bezt_data(bezt);
|
|
bezt->vec[1][0] = (float)cur_pos;
|
|
bezt->vec[1][1] = fpt->vec[1];
|
|
}
|
|
|
|
MEM_SAFE_FREE(fcu->fpt);
|
|
|
|
/* Not strictly needed since we use linear interpolation, but better be consistent here. */
|
|
calchandles_fcurve(fcu);
|
|
}
|
|
|
|
/* ***************************** F-Curve Sanity ********************************* */
|
|
/* The functions here are used in various parts of Blender, usually after some editing
|
|
* of keyframe data has occurred. They ensure that keyframe data is properly ordered and
|
|
* that the handles are correct.
|
|
*/
|
|
|
|
/* Checks if the F-Curve has a Cycles modifier, and returns the type of the cycle behavior. */
|
|
eFCU_Cycle_Type BKE_fcurve_get_cycle_type(FCurve *fcu)
|
|
{
|
|
FModifier *fcm = fcu->modifiers.first;
|
|
|
|
if (!fcm || fcm->type != FMODIFIER_TYPE_CYCLES) {
|
|
return FCU_CYCLE_NONE;
|
|
}
|
|
|
|
if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED)) {
|
|
return FCU_CYCLE_NONE;
|
|
}
|
|
|
|
if (fcm->flag & (FMODIFIER_FLAG_RANGERESTRICT | FMODIFIER_FLAG_USEINFLUENCE)) {
|
|
return FCU_CYCLE_NONE;
|
|
}
|
|
|
|
FMod_Cycles *data = (FMod_Cycles *)fcm->data;
|
|
|
|
if (data && data->after_cycles == 0 && data->before_cycles == 0) {
|
|
if (data->before_mode == FCM_EXTRAPOLATE_CYCLIC &&
|
|
data->after_mode == FCM_EXTRAPOLATE_CYCLIC) {
|
|
return FCU_CYCLE_PERFECT;
|
|
}
|
|
|
|
if (ELEM(data->before_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET) &&
|
|
ELEM(data->after_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET)) {
|
|
return FCU_CYCLE_OFFSET;
|
|
}
|
|
}
|
|
|
|
return FCU_CYCLE_NONE;
|
|
}
|
|
|
|
/* Checks if the F-Curve has a Cycles modifier with simple settings
|
|
* that warrant transition smoothing. */
|
|
bool BKE_fcurve_is_cyclic(FCurve *fcu)
|
|
{
|
|
return BKE_fcurve_get_cycle_type(fcu) != FCU_CYCLE_NONE;
|
|
}
|
|
|
|
/* Shifts 'in' by the difference in coordinates between 'to' and 'from',
|
|
* using 'out' as the output buffer.
|
|
* When 'to' and 'from' are end points of the loop, this moves the 'in' point one loop cycle.
|
|
*/
|
|
static BezTriple *cycle_offset_triple(
|
|
bool cycle, BezTriple *out, const BezTriple *in, const BezTriple *from, const BezTriple *to)
|
|
{
|
|
if (!cycle) {
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(out, in, sizeof(BezTriple));
|
|
|
|
float delta[3];
|
|
sub_v3_v3v3(delta, to->vec[1], from->vec[1]);
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
add_v3_v3(out->vec[i], delta);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Variant of #calchandles_fcurve() that allows calculating based on a different select flag.
|
|
*
|
|
* \param handle_sel_flag: The flag (bezt.f1/2/3) value to use to determine selection.
|
|
* Usually `SELECT`, but may want to use a different one at times
|
|
* (if caller does not operate on selection).
|
|
*/
|
|
void calchandles_fcurve_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag)
|
|
{
|
|
BezTriple *bezt, *prev, *next;
|
|
int a = fcu->totvert;
|
|
|
|
/* Error checking:
|
|
* - Need at least two points.
|
|
* - Need bezier keys.
|
|
* - Only bezier-interpolation has handles (for now).
|
|
*/
|
|
if (ELEM(NULL, fcu, fcu->bezt) || (a < 2) /*|| ELEM(fcu->ipo, BEZT_IPO_CONST, BEZT_IPO_LIN)*/) {
|
|
return;
|
|
}
|
|
|
|
/* If the first modifier is Cycles, smooth the curve through the cycle. */
|
|
BezTriple *first = &fcu->bezt[0], *last = &fcu->bezt[fcu->totvert - 1];
|
|
BezTriple tmp;
|
|
|
|
bool cycle = BKE_fcurve_is_cyclic(fcu) && BEZT_IS_AUTOH(first) && BEZT_IS_AUTOH(last);
|
|
|
|
/* Get initial pointers. */
|
|
bezt = fcu->bezt;
|
|
prev = cycle_offset_triple(cycle, &tmp, &fcu->bezt[fcu->totvert - 2], last, first);
|
|
next = (bezt + 1);
|
|
|
|
/* Loop over all beztriples, adjusting handles. */
|
|
while (a--) {
|
|
/* Clamp timing of handles to be on either side of beztriple. */
|
|
if (bezt->vec[0][0] > bezt->vec[1][0]) {
|
|
bezt->vec[0][0] = bezt->vec[1][0];
|
|
}
|
|
if (bezt->vec[2][0] < bezt->vec[1][0]) {
|
|
bezt->vec[2][0] = bezt->vec[1][0];
|
|
}
|
|
|
|
/* Calculate auto-handles. */
|
|
BKE_nurb_handle_calc_ex(bezt, prev, next, handle_sel_flag, true, fcu->auto_smoothing);
|
|
|
|
/* For automatic ease in and out. */
|
|
if (BEZT_IS_AUTOH(bezt) && !cycle) {
|
|
/* Only do this on first or last beztriple. */
|
|
if ((a == 0) || (a == fcu->totvert - 1)) {
|
|
/* Set both handles to have same horizontal value as keyframe. */
|
|
if (fcu->extend == FCURVE_EXTRAPOLATE_CONSTANT) {
|
|
bezt->vec[0][1] = bezt->vec[2][1] = bezt->vec[1][1];
|
|
/* Remember that these keyframes are special, they don't need to be adjusted. */
|
|
bezt->f5 = HD_AUTOTYPE_SPECIAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Avoid total smoothing failure on duplicate keyframes (can happen during grab). */
|
|
if (prev && prev->vec[1][0] >= bezt->vec[1][0]) {
|
|
prev->f5 = bezt->f5 = HD_AUTOTYPE_SPECIAL;
|
|
}
|
|
|
|
/* Advance pointers for next iteration. */
|
|
prev = bezt;
|
|
|
|
if (a == 1) {
|
|
next = cycle_offset_triple(cycle, &tmp, &fcu->bezt[1], first, last);
|
|
}
|
|
else {
|
|
next++;
|
|
}
|
|
|
|
bezt++;
|
|
}
|
|
|
|
/* If cyclic extrapolation and Auto Clamp has triggered, ensure it is symmetric. */
|
|
if (cycle && (first->f5 != HD_AUTOTYPE_NORMAL || last->f5 != HD_AUTOTYPE_NORMAL)) {
|
|
first->vec[0][1] = first->vec[2][1] = first->vec[1][1];
|
|
last->vec[0][1] = last->vec[2][1] = last->vec[1][1];
|
|
first->f5 = last->f5 = HD_AUTOTYPE_SPECIAL;
|
|
}
|
|
|
|
/* Do a second pass for auto handle: compute the handle to have 0 acceleration step. */
|
|
if (fcu->auto_smoothing != FCURVE_SMOOTH_NONE) {
|
|
BKE_nurb_handle_smooth_fcurve(fcu->bezt, fcu->totvert, cycle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function recalculates the handles of an F-Curve. Acts based on selection with `SELECT`
|
|
* flag. To use a different flag, use #calchandles_fcurve_ex().
|
|
*
|
|
* If the BezTriples have been rearranged, sort them first before using this.
|
|
*/
|
|
void calchandles_fcurve(FCurve *fcu)
|
|
{
|
|
calchandles_fcurve_ex(fcu, SELECT);
|
|
}
|
|
|
|
/**
|
|
* Update handles, making sure the handle-types are valid (e.g. correctly deduced from an "Auto"
|
|
* type), and recalculating their position vectors.
|
|
* Use when something has changed handle positions.
|
|
*
|
|
* \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`,
|
|
* but may want to use a different one at times (if caller does not operate on
|
|
* selection).
|
|
* \param use_handle: Check selection state of individual handles, otherwise always update both
|
|
* handles if the key is selected.
|
|
*/
|
|
void testhandles_fcurve(FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_handle)
|
|
{
|
|
BezTriple *bezt;
|
|
unsigned int a;
|
|
|
|
/* Only beztriples have handles (bpoints don't though). */
|
|
if (ELEM(NULL, fcu, fcu->bezt)) {
|
|
return;
|
|
}
|
|
|
|
/* Loop over beztriples. */
|
|
for (a = 0, bezt = fcu->bezt; a < fcu->totvert; a++, bezt++) {
|
|
BKE_nurb_bezt_handle_test(bezt, sel_flag, use_handle, false);
|
|
}
|
|
|
|
/* Recalculate handles. */
|
|
calchandles_fcurve_ex(fcu, sel_flag);
|
|
}
|
|
|
|
/* This function sorts BezTriples so that they are arranged in chronological order,
|
|
* as tools working on F-Curves expect that the BezTriples are in order.
|
|
*/
|
|
void sort_time_fcurve(FCurve *fcu)
|
|
{
|
|
if (fcu->bezt == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Keep adjusting order of beztriples until nothing moves (bubble-sort). */
|
|
BezTriple *bezt;
|
|
uint a;
|
|
|
|
bool ok = true;
|
|
while (ok) {
|
|
ok = 0;
|
|
/* Currently, will only be needed when there are beztriples. */
|
|
|
|
/* Loop over ALL points to adjust position in array and recalculate handles. */
|
|
for (a = 0, bezt = fcu->bezt; a < fcu->totvert; a++, bezt++) {
|
|
/* Check if thee's a next beztriple which we could try to swap with current. */
|
|
if (a < (fcu->totvert - 1)) {
|
|
/* Swap if one is after the other (and indicate that order has changed). */
|
|
if (bezt->vec[1][0] > (bezt + 1)->vec[1][0]) {
|
|
SWAP(BezTriple, *bezt, *(bezt + 1));
|
|
ok = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (a = 0, bezt = fcu->bezt; a < fcu->totvert; a++, bezt++) {
|
|
/* If either one of both of the points exceeds crosses over the keyframe time... */
|
|
if ((bezt->vec[0][0] > bezt->vec[1][0]) && (bezt->vec[2][0] < bezt->vec[1][0])) {
|
|
/* Swap handles if they have switched sides for some reason. */
|
|
swap_v2_v2(bezt->vec[0], bezt->vec[2]);
|
|
}
|
|
else {
|
|
/* Clamp handles. */
|
|
CLAMP_MAX(bezt->vec[0][0], bezt->vec[1][0]);
|
|
CLAMP_MIN(bezt->vec[2][0], bezt->vec[1][0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This function tests if any BezTriples are out of order, thus requiring a sort. */
|
|
bool test_time_fcurve(FCurve *fcu)
|
|
{
|
|
unsigned int a;
|
|
|
|
/* Sanity checks. */
|
|
if (fcu == NULL) {
|
|
return false;
|
|
}
|
|
|
|
/* Currently, only need to test beztriples. */
|
|
if (fcu->bezt) {
|
|
BezTriple *bezt;
|
|
|
|
/* Loop through all BezTriples, stopping when one exceeds the one after it. */
|
|
for (a = 0, bezt = fcu->bezt; a < (fcu->totvert - 1); a++, bezt++) {
|
|
if (bezt->vec[1][0] > (bezt + 1)->vec[1][0]) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (fcu->fpt) {
|
|
FPoint *fpt;
|
|
|
|
/* Loop through all FPoints, stopping when one exceeds the one after it. */
|
|
for (a = 0, fpt = fcu->fpt; a < (fcu->totvert - 1); a++, fpt++) {
|
|
if (fpt->vec[0] > (fpt + 1)->vec[0]) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* None need any swapping. */
|
|
return false;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name F-Curve Calculations
|
|
* \{ */
|
|
|
|
/* The length of each handle is not allowed to be more
|
|
* than the horizontal distance between (v1-v4).
|
|
* This is to prevent curve loops.
|
|
*
|
|
* This function is very similar to BKE_curve_correct_bezpart(), but allows a steeper tangent for
|
|
* more snappy animations. This is not desired for other areas in which curves are used, though.
|
|
*/
|
|
void BKE_fcurve_correct_bezpart(const float v1[2], float v2[2], float v3[2], const float v4[2])
|
|
{
|
|
float h1[2], h2[2], len1, len2, len, fac;
|
|
|
|
/* Calculate handle deltas. */
|
|
h1[0] = v1[0] - v2[0];
|
|
h1[1] = v1[1] - v2[1];
|
|
|
|
h2[0] = v4[0] - v3[0];
|
|
h2[1] = v4[1] - v3[1];
|
|
|
|
/* Calculate distances:
|
|
* - len = Span of time between keyframes.
|
|
* - len1 = Length of handle of start key.
|
|
* - len2 = Length of handle of end key.
|
|
*/
|
|
len = v4[0] - v1[0];
|
|
len1 = fabsf(h1[0]);
|
|
len2 = fabsf(h2[0]);
|
|
|
|
/* If the handles have no length, no need to do any corrections. */
|
|
if ((len1 + len2) == 0.0f) {
|
|
return;
|
|
}
|
|
|
|
/* To prevent looping or rewinding, handles cannot
|
|
* exceed the adjacent key-frames time position. */
|
|
if (len1 > len) {
|
|
fac = len / len1;
|
|
v2[0] = (v1[0] - fac * h1[0]);
|
|
v2[1] = (v1[1] - fac * h1[1]);
|
|
}
|
|
|
|
if (len2 > len) {
|
|
fac = len / len2;
|
|
v3[0] = (v4[0] - fac * h2[0]);
|
|
v3[1] = (v4[1] - fac * h2[1]);
|
|
}
|
|
}
|
|
|
|
/** Find roots of cubic equation (c0 x³ + c1 x² + c2 x + c3)
|
|
* \return number of roots in `o`.
|
|
* NOTE: it is up to the caller to allocate enough memory for `o`. */
|
|
static int solve_cubic(double c0, double c1, double c2, double c3, float *o)
|
|
{
|
|
double a, b, c, p, q, d, t, phi;
|
|
int nr = 0;
|
|
|
|
if (c3 != 0.0) {
|
|
a = c2 / c3;
|
|
b = c1 / c3;
|
|
c = c0 / c3;
|
|
a = a / 3;
|
|
|
|
p = b / 3 - a * a;
|
|
q = (2 * a * a * a - a * b + c) / 2;
|
|
d = q * q + p * p * p;
|
|
|
|
if (d > 0.0) {
|
|
t = sqrt(d);
|
|
o[0] = (float)(sqrt3d(-q + t) + sqrt3d(-q - t) - a);
|
|
|
|
if ((o[0] >= (float)SMALL) && (o[0] <= 1.000001f)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (d == 0.0) {
|
|
t = sqrt3d(-q);
|
|
o[0] = (float)(2 * t - a);
|
|
|
|
if ((o[0] >= (float)SMALL) && (o[0] <= 1.000001f)) {
|
|
nr++;
|
|
}
|
|
o[nr] = (float)(-t - a);
|
|
|
|
if ((o[nr] >= (float)SMALL) && (o[nr] <= 1.000001f)) {
|
|
return nr + 1;
|
|
}
|
|
return nr;
|
|
}
|
|
|
|
phi = acos(-q / sqrt(-(p * p * p)));
|
|
t = sqrt(-p);
|
|
p = cos(phi / 3);
|
|
q = sqrt(3 - 3 * p * p);
|
|
o[0] = (float)(2 * t * p - a);
|
|
|
|
if ((o[0] >= (float)SMALL) && (o[0] <= 1.000001f)) {
|
|
nr++;
|
|
}
|
|
o[nr] = (float)(-t * (p + q) - a);
|
|
|
|
if ((o[nr] >= (float)SMALL) && (o[nr] <= 1.000001f)) {
|
|
nr++;
|
|
}
|
|
o[nr] = (float)(-t * (p - q) - a);
|
|
|
|
if ((o[nr] >= (float)SMALL) && (o[nr] <= 1.000001f)) {
|
|
return nr + 1;
|
|
}
|
|
return nr;
|
|
}
|
|
a = c2;
|
|
b = c1;
|
|
c = c0;
|
|
|
|
if (a != 0.0) {
|
|
/* Discriminant */
|
|
p = b * b - 4 * a * c;
|
|
|
|
if (p > 0) {
|
|
p = sqrt(p);
|
|
o[0] = (float)((-b - p) / (2 * a));
|
|
|
|
if ((o[0] >= (float)SMALL) && (o[0] <= 1.000001f)) {
|
|
nr++;
|
|
}
|
|
o[nr] = (float)((-b + p) / (2 * a));
|
|
|
|
if ((o[nr] >= (float)SMALL) && (o[nr] <= 1.000001f)) {
|
|
return nr + 1;
|
|
}
|
|
return nr;
|
|
}
|
|
|
|
if (p == 0) {
|
|
o[0] = (float)(-b / (2 * a));
|
|
if ((o[0] >= (float)SMALL) && (o[0] <= 1.000001f)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (b != 0.0) {
|
|
o[0] = (float)(-c / b);
|
|
|
|
if ((o[0] >= (float)SMALL) && (o[0] <= 1.000001f)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (c == 0.0) {
|
|
o[0] = 0.0;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Find root(s) ('zero') of a Bezier curve. */
|
|
static int findzero(float x, float q0, float q1, float q2, float q3, float *o)
|
|
{
|
|
const double c0 = q0 - x;
|
|
const double c1 = 3.0f * (q1 - q0);
|
|
const double c2 = 3.0f * (q0 - 2.0f * q1 + q2);
|
|
const double c3 = q3 - q0 + 3.0f * (q1 - q2);
|
|
|
|
return solve_cubic(c0, c1, c2, c3, o);
|
|
}
|
|
|
|
static void berekeny(float f1, float f2, float f3, float f4, float *o, int b)
|
|
{
|
|
float t, c0, c1, c2, c3;
|
|
int a;
|
|
|
|
c0 = f1;
|
|
c1 = 3.0f * (f2 - f1);
|
|
c2 = 3.0f * (f1 - 2.0f * f2 + f3);
|
|
c3 = f4 - f1 + 3.0f * (f2 - f3);
|
|
|
|
for (a = 0; a < b; a++) {
|
|
t = o[a];
|
|
o[a] = c0 + t * c1 + t * t * c2 + t * t * t * c3;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adjust Bezier handles of all three given BezTriples, so that `bezt` can be inserted between
|
|
* `prev` and `next` without changing the resulting curve shape.
|
|
*
|
|
* \param r_pdelta: return Y difference between `bezt` and the original curve value at its X
|
|
* position.
|
|
* \return Whether the split was successful.
|
|
*/
|
|
bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt,
|
|
struct BezTriple *prev,
|
|
struct BezTriple *next,
|
|
float *r_pdelta)
|
|
{
|
|
/* The four points that make up this section of the Bezier curve. */
|
|
const float *prev_coords = prev->vec[1];
|
|
float *prev_handle_right = prev->vec[2];
|
|
float *next_handle_left = next->vec[0];
|
|
const float *next_coords = next->vec[1];
|
|
|
|
float *new_handle_left = bezt->vec[0];
|
|
const float *new_coords = bezt->vec[1];
|
|
float *new_handle_right = bezt->vec[2];
|
|
|
|
if (new_coords[0] <= prev_coords[0] || new_coords[0] >= next_coords[0]) {
|
|
/* The new keyframe is outside the (prev_coords, next_coords) range. */
|
|
return false;
|
|
}
|
|
|
|
/* Apply evaluation-time limits and compute the effective curve. */
|
|
BKE_fcurve_correct_bezpart(prev_coords, prev_handle_right, next_handle_left, next_coords);
|
|
float roots[4];
|
|
if (!findzero(new_coords[0],
|
|
prev_coords[0],
|
|
prev_handle_right[0],
|
|
next_handle_left[0],
|
|
next_coords[0],
|
|
roots)) {
|
|
return false;
|
|
}
|
|
|
|
const float t = roots[0]; /* Percentage of the curve at which the split should occur. */
|
|
if (t <= 0.0f || t >= 1.0f) {
|
|
/* The split would occur outside the curve, which isn't possible. */
|
|
return false;
|
|
}
|
|
|
|
/* De Casteljau split, requires three iterations of splitting.
|
|
* See https://pomax.github.io/bezierinfo/#decasteljau */
|
|
float split1[3][2], split2[2][2], split3[2];
|
|
interp_v2_v2v2(split1[0], prev_coords, prev_handle_right, t);
|
|
interp_v2_v2v2(split1[1], prev_handle_right, next_handle_left, t);
|
|
interp_v2_v2v2(split1[2], next_handle_left, next_coords, t);
|
|
interp_v2_v2v2(split2[0], split1[0], split1[1], t);
|
|
interp_v2_v2v2(split2[1], split1[1], split1[2], t);
|
|
interp_v2_v2v2(split3, split2[0], split2[1], t);
|
|
|
|
/* Update the existing handles. */
|
|
copy_v2_v2(prev_handle_right, split1[0]);
|
|
copy_v2_v2(next_handle_left, split1[2]);
|
|
|
|
float diff_coords[2];
|
|
sub_v2_v2v2(diff_coords, new_coords, split3);
|
|
add_v2_v2v2(new_handle_left, split2[0], diff_coords);
|
|
add_v2_v2v2(new_handle_right, split2[1], diff_coords);
|
|
|
|
*r_pdelta = diff_coords[1];
|
|
return true;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name F-Curve Evaluation
|
|
* \{ */
|
|
|
|
static float fcurve_eval_keyframes_extrapolate(
|
|
FCurve *fcu, BezTriple *bezts, float evaltime, int endpoint_offset, int direction_to_neighbor)
|
|
{
|
|
BezTriple *endpoint_bezt = bezts + endpoint_offset; /* The first/last keyframe. */
|
|
BezTriple *neighbor_bezt = endpoint_bezt +
|
|
direction_to_neighbor; /* The second (to last) keyframe. */
|
|
|
|
if (endpoint_bezt->ipo == BEZT_IPO_CONST || fcu->extend == FCURVE_EXTRAPOLATE_CONSTANT ||
|
|
(fcu->flag & FCURVE_DISCRETE_VALUES) != 0) {
|
|
/* Constant (BEZT_IPO_HORIZ) extrapolation or constant interpolation, so just extend the
|
|
* endpoint's value. */
|
|
return endpoint_bezt->vec[1][1];
|
|
}
|
|
|
|
if (endpoint_bezt->ipo == BEZT_IPO_LIN) {
|
|
/* Use the next center point instead of our own handle for linear interpolated extrapolate. */
|
|
if (fcu->totvert == 1) {
|
|
return endpoint_bezt->vec[1][1];
|
|
}
|
|
|
|
float dx = endpoint_bezt->vec[1][0] - evaltime;
|
|
float fac = neighbor_bezt->vec[1][0] - endpoint_bezt->vec[1][0];
|
|
|
|
/* Prevent division by zero. */
|
|
if (fac == 0.0f) {
|
|
return endpoint_bezt->vec[1][1];
|
|
}
|
|
|
|
fac = (neighbor_bezt->vec[1][1] - endpoint_bezt->vec[1][1]) / fac;
|
|
return endpoint_bezt->vec[1][1] - (fac * dx);
|
|
}
|
|
|
|
/* Use the gradient of the second handle (later) of neighbor to calculate the gradient and thus
|
|
* the value of the curve at evaluation time. */
|
|
int handle = direction_to_neighbor > 0 ? 0 : 2;
|
|
float dx = endpoint_bezt->vec[1][0] - evaltime;
|
|
float fac = endpoint_bezt->vec[1][0] - endpoint_bezt->vec[handle][0];
|
|
|
|
/* Prevent division by zero. */
|
|
if (fac == 0.0f) {
|
|
return endpoint_bezt->vec[1][1];
|
|
}
|
|
|
|
fac = (endpoint_bezt->vec[1][1] - endpoint_bezt->vec[handle][1]) / fac;
|
|
return endpoint_bezt->vec[1][1] - (fac * dx);
|
|
}
|
|
|
|
static float fcurve_eval_keyframes_interpolate(FCurve *fcu, BezTriple *bezts, float evaltime)
|
|
{
|
|
const float eps = 1.e-8f;
|
|
BezTriple *bezt, *prevbezt;
|
|
unsigned int a;
|
|
|
|
/* Evaltime occurs somewhere in the middle of the curve. */
|
|
bool exact = false;
|
|
|
|
/* Use binary search to find appropriate keyframes...
|
|
*
|
|
* The threshold here has the following constraints:
|
|
* - 0.001 is too coarse:
|
|
* We get artifacts with 2cm driver movements at 1BU = 1m (see T40332).
|
|
*
|
|
* - 0.00001 is too fine:
|
|
* Weird errors, like selecting the wrong keyframe range (see T39207), occur.
|
|
* This lower bound was established in b888a32eee8147b028464336ad2404d8155c64dd.
|
|
*/
|
|
a = BKE_fcurve_bezt_binarysearch_index_ex(bezts, evaltime, fcu->totvert, 0.0001, &exact);
|
|
bezt = bezts + a;
|
|
|
|
if (exact) {
|
|
/* Index returned must be interpreted differently when it sits on top of an existing keyframe
|
|
* - That keyframe is the start of the segment we need (see action_bug_2.blend in T39207).
|
|
*/
|
|
return bezt->vec[1][1];
|
|
}
|
|
|
|
/* Index returned refers to the keyframe that the eval-time occurs *before*
|
|
* - hence, that keyframe marks the start of the segment we're dealing with.
|
|
*/
|
|
prevbezt = (a > 0) ? (bezt - 1) : bezt;
|
|
|
|
/* Use if the key is directly on the frame, in rare cases this is needed else we get 0.0 instead.
|
|
* XXX: consult T39207 for examples of files where failure of these checks can cause issues. */
|
|
if (fabsf(bezt->vec[1][0] - evaltime) < eps) {
|
|
return bezt->vec[1][1];
|
|
}
|
|
|
|
if (evaltime < prevbezt->vec[1][0] || bezt->vec[1][0] < evaltime) {
|
|
if (G.debug & G_DEBUG) {
|
|
printf(" ERROR: failed eval - p=%f b=%f, t=%f (%f)\n",
|
|
prevbezt->vec[1][0],
|
|
bezt->vec[1][0],
|
|
evaltime,
|
|
fabsf(bezt->vec[1][0] - evaltime));
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
/* Evaltime occurs within the interval defined by these two keyframes. */
|
|
const float begin = prevbezt->vec[1][1];
|
|
const float change = bezt->vec[1][1] - prevbezt->vec[1][1];
|
|
const float duration = bezt->vec[1][0] - prevbezt->vec[1][0];
|
|
const float time = evaltime - prevbezt->vec[1][0];
|
|
const float amplitude = prevbezt->amplitude;
|
|
const float period = prevbezt->period;
|
|
|
|
/* Value depends on interpolation mode. */
|
|
if ((prevbezt->ipo == BEZT_IPO_CONST) || (fcu->flag & FCURVE_DISCRETE_VALUES) ||
|
|
(duration == 0)) {
|
|
/* Constant (evaltime not relevant, so no interpolation needed). */
|
|
return prevbezt->vec[1][1];
|
|
}
|
|
|
|
switch (prevbezt->ipo) {
|
|
/* Interpolation ...................................... */
|
|
case BEZT_IPO_BEZ: {
|
|
float v1[2], v2[2], v3[2], v4[2], opl[32];
|
|
|
|
/* Bezier interpolation. */
|
|
/* (v1, v2) are the first keyframe and its 2nd handle. */
|
|
v1[0] = prevbezt->vec[1][0];
|
|
v1[1] = prevbezt->vec[1][1];
|
|
v2[0] = prevbezt->vec[2][0];
|
|
v2[1] = prevbezt->vec[2][1];
|
|
/* (v3, v4) are the last keyframe's 1st handle + the last keyframe. */
|
|
v3[0] = bezt->vec[0][0];
|
|
v3[1] = bezt->vec[0][1];
|
|
v4[0] = bezt->vec[1][0];
|
|
v4[1] = bezt->vec[1][1];
|
|
|
|
if (fabsf(v1[1] - v4[1]) < FLT_EPSILON && fabsf(v2[1] - v3[1]) < FLT_EPSILON &&
|
|
fabsf(v3[1] - v4[1]) < FLT_EPSILON) {
|
|
/* Optimization: If all the handles are flat/at the same values,
|
|
* the value is simply the shared value (see T40372 -> F91346).
|
|
*/
|
|
return v1[1];
|
|
}
|
|
/* Adjust handles so that they don't overlap (forming a loop). */
|
|
BKE_fcurve_correct_bezpart(v1, v2, v3, v4);
|
|
|
|
/* Try to get a value for this position - if failure, try another set of points. */
|
|
if (!findzero(evaltime, v1[0], v2[0], v3[0], v4[0], opl)) {
|
|
if (G.debug & G_DEBUG) {
|
|
printf(" ERROR: findzero() failed at %f with %f %f %f %f\n",
|
|
evaltime,
|
|
v1[0],
|
|
v2[0],
|
|
v3[0],
|
|
v4[0]);
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
berekeny(v1[1], v2[1], v3[1], v4[1], opl, 1);
|
|
return opl[0];
|
|
}
|
|
case BEZT_IPO_LIN:
|
|
/* Linear - simply linearly interpolate between values of the two keyframes. */
|
|
return BLI_easing_linear_ease(time, begin, change, duration);
|
|
|
|
/* Easing ............................................ */
|
|
case BEZT_IPO_BACK:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_back_ease_in(time, begin, change, duration, prevbezt->back);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_back_ease_out(time, begin, change, duration, prevbezt->back);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_back_ease_in_out(time, begin, change, duration, prevbezt->back);
|
|
|
|
default: /* Default/Auto: same as ease out. */
|
|
return BLI_easing_back_ease_out(time, begin, change, duration, prevbezt->back);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_BOUNCE:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_bounce_ease_in(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_bounce_ease_out(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_bounce_ease_in_out(time, begin, change, duration);
|
|
|
|
default: /* Default/Auto: same as ease out. */
|
|
return BLI_easing_bounce_ease_out(time, begin, change, duration);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_CIRC:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_circ_ease_in(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_circ_ease_out(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_circ_ease_in_out(time, begin, change, duration);
|
|
|
|
default: /* Default/Auto: same as ease in. */
|
|
return BLI_easing_circ_ease_in(time, begin, change, duration);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_CUBIC:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_cubic_ease_in(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_cubic_ease_out(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_cubic_ease_in_out(time, begin, change, duration);
|
|
|
|
default: /* Default/Auto: same as ease in. */
|
|
return BLI_easing_cubic_ease_in(time, begin, change, duration);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_ELASTIC:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_elastic_ease_in(time, begin, change, duration, amplitude, period);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_elastic_ease_out(time, begin, change, duration, amplitude, period);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_elastic_ease_in_out(time, begin, change, duration, amplitude, period);
|
|
|
|
default: /* Default/Auto: same as ease out. */
|
|
return BLI_easing_elastic_ease_out(time, begin, change, duration, amplitude, period);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_EXPO:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_expo_ease_in(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_expo_ease_out(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_expo_ease_in_out(time, begin, change, duration);
|
|
|
|
default: /* Default/Auto: same as ease in. */
|
|
return BLI_easing_expo_ease_in(time, begin, change, duration);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_QUAD:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_quad_ease_in(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_quad_ease_out(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_quad_ease_in_out(time, begin, change, duration);
|
|
|
|
default: /* Default/Auto: same as ease in. */
|
|
return BLI_easing_quad_ease_in(time, begin, change, duration);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_QUART:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_quart_ease_in(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_quart_ease_out(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_quart_ease_in_out(time, begin, change, duration);
|
|
|
|
default: /* Default/Auto: same as ease in. */
|
|
return BLI_easing_quart_ease_in(time, begin, change, duration);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_QUINT:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_quint_ease_in(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_quint_ease_out(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_quint_ease_in_out(time, begin, change, duration);
|
|
|
|
default: /* Default/Auto: same as ease in. */
|
|
return BLI_easing_quint_ease_in(time, begin, change, duration);
|
|
}
|
|
break;
|
|
|
|
case BEZT_IPO_SINE:
|
|
switch (prevbezt->easing) {
|
|
case BEZT_IPO_EASE_IN:
|
|
return BLI_easing_sine_ease_in(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_OUT:
|
|
return BLI_easing_sine_ease_out(time, begin, change, duration);
|
|
case BEZT_IPO_EASE_IN_OUT:
|
|
return BLI_easing_sine_ease_in_out(time, begin, change, duration);
|
|
|
|
default: /* Default/Auto: same as ease in. */
|
|
return BLI_easing_sine_ease_in(time, begin, change, duration);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return prevbezt->vec[1][1];
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
/* Calculate F-Curve value for 'evaltime' using #BezTriple keyframes. */
|
|
static float fcurve_eval_keyframes(FCurve *fcu, BezTriple *bezts, float evaltime)
|
|
{
|
|
if (evaltime <= bezts->vec[1][0]) {
|
|
return fcurve_eval_keyframes_extrapolate(fcu, bezts, evaltime, 0, +1);
|
|
}
|
|
|
|
BezTriple *lastbezt = bezts + fcu->totvert - 1;
|
|
if (lastbezt->vec[1][0] <= evaltime) {
|
|
return fcurve_eval_keyframes_extrapolate(fcu, bezts, evaltime, fcu->totvert - 1, -1);
|
|
}
|
|
|
|
return fcurve_eval_keyframes_interpolate(fcu, bezts, evaltime);
|
|
}
|
|
|
|
/* Calculate F-Curve value for 'evaltime' using #FPoint samples. */
|
|
static float fcurve_eval_samples(FCurve *fcu, FPoint *fpts, float evaltime)
|
|
{
|
|
FPoint *prevfpt, *lastfpt, *fpt;
|
|
float cvalue = 0.0f;
|
|
|
|
/* Get pointers. */
|
|
prevfpt = fpts;
|
|
lastfpt = prevfpt + fcu->totvert - 1;
|
|
|
|
/* Evaluation time at or past endpoints? */
|
|
if (prevfpt->vec[0] >= evaltime) {
|
|
/* Before or on first sample, so just extend value. */
|
|
cvalue = prevfpt->vec[1];
|
|
}
|
|
else if (lastfpt->vec[0] <= evaltime) {
|
|
/* After or on last sample, so just extend value. */
|
|
cvalue = lastfpt->vec[1];
|
|
}
|
|
else {
|
|
float t = fabsf(evaltime - floorf(evaltime));
|
|
|
|
/* Find the one on the right frame (assume that these are spaced on 1-frame intervals). */
|
|
fpt = prevfpt + ((int)evaltime - (int)prevfpt->vec[0]);
|
|
|
|
/* If not exactly on the frame, perform linear interpolation with the next one. */
|
|
if ((t != 0.0f) && (t < 1.0f)) {
|
|
cvalue = interpf(fpt->vec[1], (fpt + 1)->vec[1], 1.0f - t);
|
|
}
|
|
else {
|
|
cvalue = fpt->vec[1];
|
|
}
|
|
}
|
|
|
|
return cvalue;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name F-Curve - Evaluation
|
|
* \{ */
|
|
|
|
/* Evaluate and return the value of the given F-Curve at the specified frame ("evaltime")
|
|
* Note: this is also used for drivers.
|
|
*/
|
|
static float evaluate_fcurve_ex(FCurve *fcu, float evaltime, float cvalue)
|
|
{
|
|
float devaltime;
|
|
|
|
/* Evaluate modifiers which modify time to evaluate the base curve at. */
|
|
FModifiersStackStorage storage;
|
|
storage.modifier_count = BLI_listbase_count(&fcu->modifiers);
|
|
storage.size_per_modifier = evaluate_fmodifiers_storage_size_per_modifier(&fcu->modifiers);
|
|
storage.buffer = alloca(storage.modifier_count * storage.size_per_modifier);
|
|
|
|
devaltime = evaluate_time_fmodifiers(&storage, &fcu->modifiers, fcu, cvalue, evaltime);
|
|
|
|
/* Evaluate curve-data
|
|
* - 'devaltime' instead of 'evaltime', as this is the time that the last time-modifying
|
|
* F-Curve modifier on the stack requested the curve to be evaluated at.
|
|
*/
|
|
if (fcu->bezt) {
|
|
cvalue = fcurve_eval_keyframes(fcu, fcu->bezt, devaltime);
|
|
}
|
|
else if (fcu->fpt) {
|
|
cvalue = fcurve_eval_samples(fcu, fcu->fpt, devaltime);
|
|
}
|
|
|
|
/* Evaluate modifiers. */
|
|
evaluate_value_fmodifiers(&storage, &fcu->modifiers, fcu, &cvalue, devaltime);
|
|
|
|
/* If curve can only have integral values, perform truncation (i.e. drop the decimal part)
|
|
* here so that the curve can be sampled correctly.
|
|
*/
|
|
if (fcu->flag & FCURVE_INT_VALUES) {
|
|
cvalue = floorf(cvalue + 0.5f);
|
|
}
|
|
|
|
return cvalue;
|
|
}
|
|
|
|
float evaluate_fcurve(FCurve *fcu, float evaltime)
|
|
{
|
|
BLI_assert(fcu->driver == NULL);
|
|
|
|
return evaluate_fcurve_ex(fcu, evaltime, 0.0);
|
|
}
|
|
|
|
float evaluate_fcurve_only_curve(FCurve *fcu, float evaltime)
|
|
{
|
|
/* Can be used to evaluate the (keyframed) fcurve only.
|
|
* Also works for driver-fcurves when the driver itself is not relevant.
|
|
* E.g. when inserting a keyframe in a driver fcurve. */
|
|
return evaluate_fcurve_ex(fcu, evaltime, 0.0);
|
|
}
|
|
|
|
float evaluate_fcurve_driver(PathResolvedRNA *anim_rna,
|
|
FCurve *fcu,
|
|
ChannelDriver *driver_orig,
|
|
const AnimationEvalContext *anim_eval_context)
|
|
{
|
|
BLI_assert(fcu->driver != NULL);
|
|
float cvalue = 0.0f;
|
|
float evaltime = anim_eval_context->eval_time;
|
|
|
|
/* If there is a driver (only if this F-Curve is acting as 'driver'),
|
|
* evaluate it to find value to use as "evaltime" since drivers essentially act as alternative
|
|
* input (i.e. in place of 'time') for F-Curves. */
|
|
if (fcu->driver) {
|
|
/* Evaltime now serves as input for the curve. */
|
|
evaltime = evaluate_driver(anim_rna, fcu->driver, driver_orig, anim_eval_context);
|
|
|
|
/* Only do a default 1-1 mapping if it's unlikely that anything else will set a value... */
|
|
if (fcu->totvert == 0) {
|
|
FModifier *fcm;
|
|
bool do_linear = true;
|
|
|
|
/* Out-of-range F-Modifiers will block, as will those which just plain overwrite the values
|
|
* XXX: additive is a bit more dicey; it really depends then if things are in range or not...
|
|
*/
|
|
for (fcm = fcu->modifiers.first; fcm; fcm = fcm->next) {
|
|
/* If there are range-restrictions, we must definitely block T36950. */
|
|
if ((fcm->flag & FMODIFIER_FLAG_RANGERESTRICT) == 0 ||
|
|
((fcm->sfra <= evaltime) && (fcm->efra >= evaltime))) {
|
|
/* Within range: here it probably doesn't matter,
|
|
* though we'd want to check on additive. */
|
|
}
|
|
else {
|
|
/* Outside range: modifier shouldn't contribute to the curve here,
|
|
* though it does in other areas, so neither should the driver! */
|
|
do_linear = false;
|
|
}
|
|
}
|
|
|
|
/* Only copy over results if none of the modifiers disagreed with this. */
|
|
if (do_linear) {
|
|
cvalue = evaltime;
|
|
}
|
|
}
|
|
}
|
|
|
|
return evaluate_fcurve_ex(fcu, evaltime, cvalue);
|
|
}
|
|
|
|
/* Checks if the curve has valid keys, drivers or modifiers that produce an actual curve. */
|
|
bool BKE_fcurve_is_empty(FCurve *fcu)
|
|
{
|
|
return (fcu->totvert == 0) && (fcu->driver == NULL) &&
|
|
!list_has_suitable_fmodifier(&fcu->modifiers, 0, FMI_TYPE_GENERATE_CURVE);
|
|
}
|
|
|
|
/* Calculate the value of the given F-Curve at the given frame, and set its curval. */
|
|
float calculate_fcurve(PathResolvedRNA *anim_rna,
|
|
FCurve *fcu,
|
|
const AnimationEvalContext *anim_eval_context)
|
|
{
|
|
/* Only calculate + set curval (overriding the existing value) if curve has
|
|
* any data which warrants this...
|
|
*/
|
|
if (BKE_fcurve_is_empty(fcu)) {
|
|
return 0.0f;
|
|
}
|
|
|
|
/* Calculate and set curval (evaluates driver too if necessary). */
|
|
float curval;
|
|
if (fcu->driver) {
|
|
curval = evaluate_fcurve_driver(anim_rna, fcu, fcu->driver, anim_eval_context);
|
|
}
|
|
else {
|
|
curval = evaluate_fcurve(fcu, anim_eval_context->eval_time);
|
|
}
|
|
fcu->curval = curval; /* Debug display only, not thread safe! */
|
|
return curval;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name F-Curve - .blend file API
|
|
* \{ */
|
|
|
|
void BKE_fmodifiers_blend_write(BlendWriter *writer, ListBase *fmodifiers)
|
|
{
|
|
/* Write all modifiers first (for faster reloading) */
|
|
BLO_write_struct_list(writer, FModifier, fmodifiers);
|
|
|
|
/* Modifiers */
|
|
LISTBASE_FOREACH (FModifier *, fcm, fmodifiers) {
|
|
const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(fcm);
|
|
|
|
/* Write the specific data */
|
|
if (fmi && fcm->data) {
|
|
/* firstly, just write the plain fmi->data struct */
|
|
BLO_write_struct_by_name(writer, fmi->structName, fcm->data);
|
|
|
|
/* do any modifier specific stuff */
|
|
switch (fcm->type) {
|
|
case FMODIFIER_TYPE_GENERATOR: {
|
|
FMod_Generator *data = fcm->data;
|
|
|
|
/* write coefficients array */
|
|
if (data->coefficients) {
|
|
BLO_write_float_array(writer, data->arraysize, data->coefficients);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case FMODIFIER_TYPE_ENVELOPE: {
|
|
FMod_Envelope *data = fcm->data;
|
|
|
|
/* write envelope data */
|
|
if (data->data) {
|
|
BLO_write_struct_array(writer, FCM_EnvelopeData, data->totvert, data->data);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case FMODIFIER_TYPE_PYTHON: {
|
|
FMod_Python *data = fcm->data;
|
|
|
|
/* Write ID Properties -- and copy this comment EXACTLY for easy finding
|
|
* of library blocks that implement this.*/
|
|
IDP_BlendWrite(writer, data->prop);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_fmodifiers_blend_read_data(BlendDataReader *reader, ListBase *fmodifiers, FCurve *curve)
|
|
{
|
|
LISTBASE_FOREACH (FModifier *, fcm, fmodifiers) {
|
|
/* relink general data */
|
|
BLO_read_data_address(reader, &fcm->data);
|
|
fcm->curve = curve;
|
|
|
|
/* do relinking of data for specific types */
|
|
switch (fcm->type) {
|
|
case FMODIFIER_TYPE_GENERATOR: {
|
|
FMod_Generator *data = (FMod_Generator *)fcm->data;
|
|
BLO_read_float_array(reader, data->arraysize, &data->coefficients);
|
|
break;
|
|
}
|
|
case FMODIFIER_TYPE_ENVELOPE: {
|
|
FMod_Envelope *data = (FMod_Envelope *)fcm->data;
|
|
|
|
BLO_read_data_address(reader, &data->data);
|
|
|
|
break;
|
|
}
|
|
case FMODIFIER_TYPE_PYTHON: {
|
|
FMod_Python *data = (FMod_Python *)fcm->data;
|
|
|
|
BLO_read_data_address(reader, &data->prop);
|
|
IDP_BlendDataRead(reader, &data->prop);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_fmodifiers_blend_read_lib(BlendLibReader *reader, ID *id, ListBase *fmodifiers)
|
|
{
|
|
LISTBASE_FOREACH (FModifier *, fcm, fmodifiers) {
|
|
/* data for specific modifiers */
|
|
switch (fcm->type) {
|
|
case FMODIFIER_TYPE_PYTHON: {
|
|
FMod_Python *data = (FMod_Python *)fcm->data;
|
|
BLO_read_id_address(reader, id->lib, &data->script);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_fmodifiers_blend_read_expand(BlendExpander *expander, ListBase *fmodifiers)
|
|
{
|
|
LISTBASE_FOREACH (FModifier *, fcm, fmodifiers) {
|
|
/* library data for specific F-Modifier types */
|
|
switch (fcm->type) {
|
|
case FMODIFIER_TYPE_PYTHON: {
|
|
FMod_Python *data = (FMod_Python *)fcm->data;
|
|
BLO_expand(expander, data->script);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_fcurve_blend_write(BlendWriter *writer, ListBase *fcurves)
|
|
{
|
|
BLO_write_struct_list(writer, FCurve, fcurves);
|
|
LISTBASE_FOREACH (FCurve *, fcu, fcurves) {
|
|
/* curve data */
|
|
if (fcu->bezt) {
|
|
BLO_write_struct_array(writer, BezTriple, fcu->totvert, fcu->bezt);
|
|
}
|
|
if (fcu->fpt) {
|
|
BLO_write_struct_array(writer, FPoint, fcu->totvert, fcu->fpt);
|
|
}
|
|
|
|
if (fcu->rna_path) {
|
|
BLO_write_string(writer, fcu->rna_path);
|
|
}
|
|
|
|
/* driver data */
|
|
if (fcu->driver) {
|
|
ChannelDriver *driver = fcu->driver;
|
|
|
|
BLO_write_struct(writer, ChannelDriver, driver);
|
|
|
|
/* variables */
|
|
BLO_write_struct_list(writer, DriverVar, &driver->variables);
|
|
LISTBASE_FOREACH (DriverVar *, dvar, &driver->variables) {
|
|
DRIVER_TARGETS_USED_LOOPER_BEGIN (dvar) {
|
|
if (dtar->rna_path) {
|
|
BLO_write_string(writer, dtar->rna_path);
|
|
}
|
|
}
|
|
DRIVER_TARGETS_LOOPER_END;
|
|
}
|
|
}
|
|
|
|
/* write F-Modifiers */
|
|
BKE_fmodifiers_blend_write(writer, &fcu->modifiers);
|
|
}
|
|
}
|
|
|
|
void BKE_fcurve_blend_read_data(BlendDataReader *reader, ListBase *fcurves)
|
|
{
|
|
/* link F-Curve data to F-Curve again (non ID-libs) */
|
|
LISTBASE_FOREACH (FCurve *, fcu, fcurves) {
|
|
/* curve data */
|
|
BLO_read_data_address(reader, &fcu->bezt);
|
|
BLO_read_data_address(reader, &fcu->fpt);
|
|
|
|
/* rna path */
|
|
BLO_read_data_address(reader, &fcu->rna_path);
|
|
|
|
/* group */
|
|
BLO_read_data_address(reader, &fcu->grp);
|
|
|
|
/* clear disabled flag - allows disabled drivers to be tried again (T32155),
|
|
* but also means that another method for "reviving disabled F-Curves" exists
|
|
*/
|
|
fcu->flag &= ~FCURVE_DISABLED;
|
|
|
|
/* driver */
|
|
BLO_read_data_address(reader, &fcu->driver);
|
|
if (fcu->driver) {
|
|
ChannelDriver *driver = fcu->driver;
|
|
|
|
/* Compiled expression data will need to be regenerated
|
|
* (old pointer may still be set here). */
|
|
driver->expr_comp = NULL;
|
|
driver->expr_simple = NULL;
|
|
|
|
/* Give the driver a fresh chance - the operating environment may be different now
|
|
* (addons, etc. may be different) so the driver namespace may be sane now T32155. */
|
|
driver->flag &= ~DRIVER_FLAG_INVALID;
|
|
|
|
/* relink variables, targets and their paths */
|
|
BLO_read_list(reader, &driver->variables);
|
|
LISTBASE_FOREACH (DriverVar *, dvar, &driver->variables) {
|
|
DRIVER_TARGETS_LOOPER_BEGIN (dvar) {
|
|
/* only relink the targets being used */
|
|
if (tarIndex < dvar->num_targets) {
|
|
BLO_read_data_address(reader, &dtar->rna_path);
|
|
}
|
|
else {
|
|
dtar->rna_path = NULL;
|
|
}
|
|
}
|
|
DRIVER_TARGETS_LOOPER_END;
|
|
}
|
|
}
|
|
|
|
/* modifiers */
|
|
BLO_read_list(reader, &fcu->modifiers);
|
|
BKE_fmodifiers_blend_read_data(reader, &fcu->modifiers, fcu);
|
|
}
|
|
}
|
|
|
|
void BKE_fcurve_blend_read_lib(BlendLibReader *reader, ID *id, ListBase *fcurves)
|
|
{
|
|
if (fcurves == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* relink ID-block references... */
|
|
LISTBASE_FOREACH (FCurve *, fcu, fcurves) {
|
|
/* driver data */
|
|
if (fcu->driver) {
|
|
ChannelDriver *driver = fcu->driver;
|
|
LISTBASE_FOREACH (DriverVar *, dvar, &driver->variables) {
|
|
DRIVER_TARGETS_LOOPER_BEGIN (dvar) {
|
|
/* only relink if still used */
|
|
if (tarIndex < dvar->num_targets) {
|
|
BLO_read_id_address(reader, id->lib, &dtar->id);
|
|
}
|
|
else {
|
|
dtar->id = NULL;
|
|
}
|
|
}
|
|
DRIVER_TARGETS_LOOPER_END;
|
|
}
|
|
}
|
|
|
|
/* modifiers */
|
|
BKE_fmodifiers_blend_read_lib(reader, id, &fcu->modifiers);
|
|
}
|
|
}
|
|
|
|
void BKE_fcurve_blend_read_expand(BlendExpander *expander, ListBase *fcurves)
|
|
{
|
|
LISTBASE_FOREACH (FCurve *, fcu, fcurves) {
|
|
/* Driver targets if there is a driver */
|
|
if (fcu->driver) {
|
|
ChannelDriver *driver = fcu->driver;
|
|
|
|
LISTBASE_FOREACH (DriverVar *, dvar, &driver->variables) {
|
|
DRIVER_TARGETS_LOOPER_BEGIN (dvar) {
|
|
// TODO: only expand those that are going to get used?
|
|
BLO_expand(expander, dtar->id);
|
|
}
|
|
DRIVER_TARGETS_LOOPER_END;
|
|
}
|
|
}
|
|
|
|
/* F-Curve Modifiers */
|
|
BKE_fmodifiers_blend_read_expand(expander, &fcu->modifiers);
|
|
}
|
|
}
|
|
|
|
/** \} */
|