Compare commits
4 Commits
blender-pr
...
smooth-fcu
Author | SHA1 | Date | |
---|---|---|---|
eea32ffa4d | |||
644a5ed7ef | |||
7a765e7afa | |||
0d814a2e49 |
@@ -202,10 +202,12 @@ void BKE_nurb_bpoint_calc_normal(struct Nurb *nu, struct BPoint *bp, float r_nor
|
||||
void BKE_nurb_bpoint_calc_plane(struct Nurb *nu, struct BPoint *bp, float r_plane[3]);
|
||||
|
||||
void BKE_nurb_handle_calc(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next,
|
||||
const bool is_fcurve);
|
||||
const bool is_fcurve, const char smoothing);
|
||||
void BKE_nurb_handle_calc_simple(struct Nurb *nu, struct BezTriple *bezt);
|
||||
void BKE_nurb_handle_calc_simple_auto(struct Nurb *nu, struct BezTriple *bezt);
|
||||
|
||||
void BKE_nurb_handle_smooth_fcurve(struct BezTriple *bezt, int total, bool cyclic);
|
||||
|
||||
void BKE_nurb_handles_calc(struct Nurb *nu);
|
||||
void BKE_nurb_handles_autocalc(struct Nurb *nu, int flag);
|
||||
void BKE_nurb_bezt_handle_test(struct BezTriple *bezt, const bool use_handle);
|
||||
|
@@ -188,7 +188,7 @@ const FModifierTypeInfo *get_fmodifier_typeinfo(const int type);
|
||||
|
||||
/* ---------------------- */
|
||||
|
||||
struct FModifier *add_fmodifier(ListBase *modifiers, int type);
|
||||
struct FModifier *add_fmodifier(ListBase *modifiers, int type, struct FCurve *owner_fcu);
|
||||
struct FModifier *copy_fmodifier(const struct FModifier *src);
|
||||
void copy_fmodifiers(ListBase *dst, const ListBase *src);
|
||||
bool remove_fmodifier(ListBase *modifiers, struct FModifier *fcm);
|
||||
|
@@ -41,6 +41,7 @@
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_ghash.h"
|
||||
|
||||
#include "DNA_anim_types.h"
|
||||
#include "DNA_curve_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
|
||||
@@ -3134,7 +3135,7 @@ void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render)
|
||||
|
||||
static void calchandleNurb_intern(
|
||||
BezTriple *bezt, const BezTriple *prev, const BezTriple *next,
|
||||
bool is_fcurve, bool skip_align)
|
||||
bool is_fcurve, bool skip_align, char fcurve_smoothing)
|
||||
{
|
||||
/* defines to avoid confusion */
|
||||
#define p2_h1 ((p2) - 3)
|
||||
@@ -3148,6 +3149,9 @@ static void calchandleNurb_intern(
|
||||
float len_ratio;
|
||||
const float eps = 1e-5;
|
||||
|
||||
/* assume normal handle until we check */
|
||||
bezt->f5 = HD_AUTOTYPE_NORMAL;
|
||||
|
||||
if (bezt->h1 == 0 && bezt->h2 == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -3199,7 +3203,13 @@ static void calchandleNurb_intern(
|
||||
tvec[2] = dvec_b[2] / len_b + dvec_a[2] / len_a;
|
||||
|
||||
if (is_fcurve) {
|
||||
len = tvec[0];
|
||||
if (fcurve_smoothing != FCURVE_SMOOTH_NONE) {
|
||||
/* force the handlers transition to be 1/3 */
|
||||
len = 6.0f/2.5614f;
|
||||
}
|
||||
else {
|
||||
len = tvec[0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
len = len_v3(tvec);
|
||||
@@ -3210,10 +3220,12 @@ static void calchandleNurb_intern(
|
||||
/* only for fcurves */
|
||||
bool leftviolate = false, rightviolate = false;
|
||||
|
||||
if (len_a > 5.0f * len_b)
|
||||
len_a = 5.0f * len_b;
|
||||
if (len_b > 5.0f * len_a)
|
||||
len_b = 5.0f * len_a;
|
||||
if (!is_fcurve || fcurve_smoothing == FCURVE_SMOOTH_NONE) {
|
||||
if (len_a > 5.0f * len_b)
|
||||
len_a = 5.0f * len_b;
|
||||
if (len_b > 5.0f * len_a)
|
||||
len_b = 5.0f * len_a;
|
||||
}
|
||||
|
||||
if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM)) {
|
||||
len_a /= len;
|
||||
@@ -3224,6 +3236,7 @@ static void calchandleNurb_intern(
|
||||
float ydiff2 = next->vec[1][1] - bezt->vec[1][1];
|
||||
if ((ydiff1 <= 0.0f && ydiff2 <= 0.0f) || (ydiff1 >= 0.0f && ydiff2 >= 0.0f)) {
|
||||
bezt->vec[0][1] = bezt->vec[1][1];
|
||||
bezt->f5 = HD_AUTOTYPE_SPECIAL;
|
||||
}
|
||||
else { /* handles should not be beyond y coord of two others */
|
||||
if (ydiff1 <= 0.0f) {
|
||||
@@ -3250,6 +3263,7 @@ static void calchandleNurb_intern(
|
||||
float ydiff2 = next->vec[1][1] - bezt->vec[1][1];
|
||||
if ( (ydiff1 <= 0.0f && ydiff2 <= 0.0f) || (ydiff1 >= 0.0f && ydiff2 >= 0.0f) ) {
|
||||
bezt->vec[2][1] = bezt->vec[1][1];
|
||||
bezt->f5 = HD_AUTOTYPE_SPECIAL;
|
||||
}
|
||||
else { /* handles should not be beyond y coord of two others */
|
||||
if (ydiff1 <= 0.0f) {
|
||||
@@ -3397,7 +3411,7 @@ static void calchandlesNurb_intern(Nurb *nu, bool skip_align)
|
||||
next = bezt + 1;
|
||||
|
||||
while (a--) {
|
||||
calchandleNurb_intern(bezt, prev, next, 0, skip_align);
|
||||
calchandleNurb_intern(bezt, prev, next, 0, skip_align, 0);
|
||||
prev = bezt;
|
||||
if (a == 1) {
|
||||
if (nu->flagu & CU_NURB_CYCLIC)
|
||||
@@ -3412,9 +3426,519 @@ static void calchandlesNurb_intern(Nurb *nu, bool skip_align)
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_nurb_handle_calc(BezTriple *bezt, BezTriple *prev, BezTriple *next, const bool is_fcurve)
|
||||
static void *allocate_arrays(int count, float ***floats, char ***chars, const char *name)
|
||||
{
|
||||
calchandleNurb_intern(bezt, prev, next, is_fcurve, false);
|
||||
int num_floats = 0, num_chars = 0;
|
||||
|
||||
while (floats && floats[num_floats]) {
|
||||
num_floats++;
|
||||
}
|
||||
|
||||
while (chars && chars[num_chars]) {
|
||||
num_chars++;
|
||||
}
|
||||
|
||||
void *buffer = (float*)MEM_mallocN(count * (sizeof(float)*num_floats + num_chars), name);
|
||||
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
float *fptr = buffer;
|
||||
|
||||
for (int i = 0; i < num_floats; i++, fptr += count)
|
||||
*floats[i] = fptr;
|
||||
|
||||
char *cptr = (char*)fptr;
|
||||
|
||||
for (int i = 0; i < num_chars; i++, cptr += count)
|
||||
*chars[i] = cptr;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/* computes in which direction to change h[i] to satisfy conditions better */
|
||||
static float bezier_relax_direction(float *a, float *b, float *c, float *d, float *h, int i, int count)
|
||||
{
|
||||
/* current deviation between sides of the equation */
|
||||
float state = a[i] * h[(i+count-1)%count] + b[i] * h[i] + c[i] * h[(i+1)%count] - d[i];
|
||||
|
||||
/* only the sign is meaningful */
|
||||
return -state * b[i];
|
||||
}
|
||||
|
||||
static void bezier_lock_unknown(float *a, float *b, float *c, float *d, int i, float value)
|
||||
{
|
||||
a[i] = c[i] = 0.0f;
|
||||
b[i] = 1.0f;
|
||||
d[i] = value;
|
||||
}
|
||||
|
||||
static bool tridiagonal_solve_with_limits(float *a, float *b, float *c, float *d, float *h, float *hmin, float *hmax, int solve_count)
|
||||
{
|
||||
float *a0, *b0, *c0, *d0;
|
||||
float **arrays[] = { &a0, &b0, &c0, &d0, NULL };
|
||||
char *is_locked, *num_unlocks;
|
||||
char **flagarrays[] = { &is_locked, &num_unlocks, NULL };
|
||||
|
||||
void *tmps = allocate_arrays(solve_count, arrays, flagarrays, "tridiagonal_solve_with_limits");
|
||||
if (!tmps)
|
||||
return false;
|
||||
|
||||
memcpy(a0, a, sizeof(float)*solve_count);
|
||||
memcpy(b0, b, sizeof(float)*solve_count);
|
||||
memcpy(c0, c, sizeof(float)*solve_count);
|
||||
memcpy(d0, d, sizeof(float)*solve_count);
|
||||
|
||||
memset(is_locked, 0, solve_count);
|
||||
memset(num_unlocks, 0, solve_count);
|
||||
|
||||
bool overshoot, unlocked;
|
||||
|
||||
do
|
||||
{
|
||||
if (!BLI_tridiagonal_solve_cyclic(a, b, c, d, h, solve_count)) {
|
||||
MEM_freeN(tmps);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* first check if any handles overshoot the limits, and lock them */
|
||||
bool all = false, locked = false;
|
||||
|
||||
overshoot = unlocked = false;
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < solve_count; i++) {
|
||||
if (h[i] >= hmin[i] && h[i] <= hmax[i])
|
||||
continue;
|
||||
|
||||
overshoot = true;
|
||||
|
||||
float target = h[i] > hmax[i] ? hmax[i] : hmin[i];
|
||||
|
||||
/* heuristically only lock handles that go in the right direction if there are such ones */
|
||||
if (target != 0.0f || all) {
|
||||
/* mark item locked */
|
||||
is_locked[i] = 1;
|
||||
|
||||
bezier_lock_unknown(a, b, c, d, i, target);
|
||||
locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
all = true;
|
||||
}
|
||||
while (overshoot && !locked);
|
||||
|
||||
/* if no handles overshot and were locked, see if it may be a good idea to unlock some handles */
|
||||
if (!locked) {
|
||||
for (int i = 0; i < solve_count; i++) {
|
||||
// to definitely avoid infinite loops limit this to 2 times
|
||||
if (!is_locked[i] || num_unlocks[i] >= 2)
|
||||
continue;
|
||||
|
||||
/* if the handle wants to move in allowable direction, release it */
|
||||
float relax = bezier_relax_direction(a0, b0, c0, d0, h, i, solve_count);
|
||||
|
||||
if ((relax > 0 && h[i] < hmax[i]) || (relax < 0 && h[i] > hmin[i])) {
|
||||
/* restore equation coefficients */
|
||||
a[i] = a0[i]; b[i] = b0[i]; c[i] = c0[i]; d[i] = d0[i];
|
||||
|
||||
is_locked[i] = 0;
|
||||
num_unlocks[i]++;
|
||||
unlocked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (overshoot || unlocked);
|
||||
|
||||
MEM_freeN(tmps);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function computes the handles of a series of auto bezier points
|
||||
* on the basis of 'no acceleration discontinuities' at the points.
|
||||
* The first and last bezier points are considered 'fixed' (their handles are not touched)
|
||||
* The result is the smoothest possible trajectory going through intemediate points.
|
||||
* The difficulty is that the handles depends on their neighbours.
|
||||
*
|
||||
* The exact solution is found by solving a tridiagonal matrix equation formed
|
||||
* by the continuity and boundary conditions. Although theoretically handle position
|
||||
* is affected by all other points of the curve segment, in practice the influence
|
||||
* decreases exponentially with distance.
|
||||
*
|
||||
* Note: this algorithm assumes that the handle horizontal size if always 1/3 of the
|
||||
* of the interval to the next point. This rule ensures linear interpolation of time.
|
||||
*
|
||||
* ^ height (co 1)
|
||||
* | yN
|
||||
* | yN-1 |
|
||||
* | y2 | |
|
||||
* | y1 | | |
|
||||
* | y0 | | | |
|
||||
* | | | | | |
|
||||
* | | | | | |
|
||||
* | | | | | |
|
||||
* |-------t1---------t2--------- ~ --------tN-------------------> time (co 0)
|
||||
*
|
||||
*
|
||||
* Mathematical basis:
|
||||
*
|
||||
* 1. Handle lengths on either side of each point are connected by a factor
|
||||
* ensuring continuity of the first derivative:
|
||||
*
|
||||
* l[i] = t[i+1]/t[i]
|
||||
*
|
||||
* 2. The tridiagonal system is formed by the following equation, which is derived
|
||||
* by differentiating the bezier curve and specifies second derivative continuity
|
||||
* at every point:
|
||||
*
|
||||
* l[i]^2 * h[i-1] + (2*l[i]+2) * h[i] + 1/l[i+1] * h[i+1] = (y[i]-y[i-1])*l[i]^2 + y[i+1]-y[i]
|
||||
*
|
||||
* 3. If this point is adjacent to a manually set handle with X size not equal to 1/3
|
||||
* of the horizontal interval, this equation becomes slightly more complex:
|
||||
*
|
||||
* l[i]^2 * h[i-1] + (3*(1-R[i-1])*l[i] + 3*(1-L[i+1])) * h[i] + 1/l[i+1] * h[i+1] = (y[i]-y[i-1])*l[i]^2 + y[i+1]-y[i]
|
||||
*
|
||||
* The difference between equations amounts to this, and it's obvious that when R[i-1]
|
||||
* and L[i+1] are both 1/3, it becomes zero:
|
||||
*
|
||||
* ( (1-3*R[i-1])*l[i] + (1-3*L[i+1]) ) * h[i]
|
||||
*
|
||||
* 4. The equations for zero acceleration border conditions are basically the above
|
||||
* equation with parts omitted, so the handle size correction also applies.
|
||||
*/
|
||||
|
||||
|
||||
static void bezier_eq_continuous(float *a, float *b, float *c, float *d, float *dy, float *l, int i)
|
||||
{
|
||||
a[i] = l[i]*l[i];
|
||||
b[i] = 2.0f*(l[i] + 1);
|
||||
c[i] = 1.0f/l[i+1];
|
||||
d[i] = dy[i]*l[i]*l[i] + dy[i+1];
|
||||
}
|
||||
|
||||
static void bezier_eq_noaccel_right(float *a, float *b, float *c, float *d, float *dy, float *l, int i)
|
||||
{
|
||||
a[i] = 0.0f;
|
||||
b[i] = 2.0f;
|
||||
c[i] = 1.0f/l[i+1];
|
||||
d[i] = dy[i+1];
|
||||
}
|
||||
|
||||
static void bezier_eq_noaccel_left(float *a, float *b, float *c, float *d, float *dy, float *l, int i)
|
||||
{
|
||||
a[i] = l[i]*l[i];
|
||||
b[i] = 2.0f*l[i];
|
||||
c[i] = 0.0f;
|
||||
d[i] = dy[i]*l[i]*l[i];
|
||||
}
|
||||
|
||||
/* auto clamp prevents its own point going the wrong way, and adjacent handles overshooting */
|
||||
static void bezier_clamp(float *hmax, float *hmin, int i, float dy, bool no_reverse, bool no_overshoot)
|
||||
{
|
||||
if (dy > 0) {
|
||||
if (no_overshoot)
|
||||
hmax[i] = min_ff(hmax[i], dy);
|
||||
if (no_reverse)
|
||||
hmin[i] = 0.0f;
|
||||
}
|
||||
else if (dy < 0) {
|
||||
if (no_reverse)
|
||||
hmax[i] = 0.0f;
|
||||
if (no_overshoot)
|
||||
hmin[i] = max_ff(hmin[i], dy);
|
||||
}
|
||||
else if (no_reverse || no_overshoot) {
|
||||
hmax[i] = hmin[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/* write changes to a bezier handle */
|
||||
static void bezier_output_handle_inner(BezTriple *bezt, bool right, float newval[3], bool endpoint)
|
||||
{
|
||||
float tmp[3];
|
||||
|
||||
int idx = right ? 2 : 0;
|
||||
char hr = right ? bezt->h2 : bezt->h1;
|
||||
char hm = right ? bezt->h1 : bezt->h2;
|
||||
|
||||
/* only assign Auto/Vector handles */
|
||||
if (!ELEM(hr, HD_AUTO, HD_AUTO_ANIM, HD_VECT))
|
||||
return;
|
||||
|
||||
copy_v3_v3(bezt->vec[idx], newval);
|
||||
|
||||
/* fix up the Align handle if any */
|
||||
if (ELEM(hm, HD_ALIGN, HD_ALIGN_DOUBLESIDE)) {
|
||||
float hlen = len_v3v3(bezt->vec[1], bezt->vec[2-idx]);
|
||||
float h2len = len_v3v3(bezt->vec[1], bezt->vec[idx]);
|
||||
|
||||
sub_v3_v3v3(tmp, bezt->vec[1], bezt->vec[idx]);
|
||||
madd_v3_v3v3fl(bezt->vec[2-idx], bezt->vec[1], tmp, hlen/h2len);
|
||||
}
|
||||
/* at end points of the curve, mirror handle to the other side */
|
||||
else if (endpoint && ELEM(hm, HD_AUTO, HD_AUTO_ANIM, HD_VECT)) {
|
||||
sub_v3_v3v3(tmp, bezt->vec[1], bezt->vec[idx]);
|
||||
add_v3_v3v3(bezt->vec[2-idx], bezt->vec[1], tmp);
|
||||
}
|
||||
}
|
||||
|
||||
static void bezier_output_handle(BezTriple *bezt, bool right, float dy, bool endpoint)
|
||||
{
|
||||
float tmp[3];
|
||||
|
||||
copy_v3_v3(tmp, bezt->vec[right ? 2 : 0]);
|
||||
|
||||
tmp[1] = bezt->vec[1][1] + dy;
|
||||
|
||||
bezier_output_handle_inner(bezt, right, tmp, endpoint);
|
||||
}
|
||||
|
||||
static bool bezier_check_solve_end_handle(BezTriple *bezt, char htype, bool end)
|
||||
{
|
||||
return (htype == HD_VECT) || (end && ELEM(htype, HD_AUTO, HD_AUTO_ANIM) && bezt->f5 == HD_AUTOTYPE_NORMAL);
|
||||
}
|
||||
|
||||
static float bezier_calc_handle_adj(float hsize[2], float dx)
|
||||
{
|
||||
/* if handles intersect in x direction, they are scaled to fit */
|
||||
float fac = dx/(hsize[0] + dx/3.0f);
|
||||
if (fac < 1.0f)
|
||||
mul_v2_fl(hsize, fac);
|
||||
|
||||
return 1.0f - 3.0f*hsize[0]/dx;
|
||||
}
|
||||
|
||||
static void bezier_handle_calc_smooth_fcurve(BezTriple *bezt, int total, int start, int count, bool cycle)
|
||||
{
|
||||
float *dx, *dy, *l, *a, *b, *c, *d, *h, *hmax, *hmin;
|
||||
float **arrays[] = { &dx, &dy, &l, &a, &b, &c, &d, &h, &hmax, &hmin, NULL };
|
||||
|
||||
int solve_count = count;
|
||||
|
||||
/* verify index ranges */
|
||||
|
||||
if (count < 2)
|
||||
return;
|
||||
|
||||
BLI_assert(start < total-1 && count <= total);
|
||||
BLI_assert(start + count <= total || cycle);
|
||||
|
||||
bool full_cycle = (start == 0 && count == total && cycle);
|
||||
|
||||
BezTriple *bezt_first = &bezt[start];
|
||||
BezTriple *bezt_last = &bezt[(start+count > total) ? start+count-total : start+count-1];
|
||||
|
||||
bool solve_first = bezier_check_solve_end_handle(bezt_first, bezt_first->h2, start==0);
|
||||
bool solve_last = bezier_check_solve_end_handle(bezt_last, bezt_last->h1, start+count==total);
|
||||
|
||||
if (count == 2 && !full_cycle && solve_first == solve_last)
|
||||
return;
|
||||
|
||||
/* allocate all */
|
||||
|
||||
void *tmp_buffer = allocate_arrays(count, arrays, NULL, "bezier_calc_smooth_tmp");
|
||||
if (!tmp_buffer)
|
||||
return;
|
||||
|
||||
/* point locations */
|
||||
|
||||
dx[0] = dy[0] = NAN_FLT;
|
||||
|
||||
for (int i = 1, j = start+1; i < count; i++, j++) {
|
||||
dx[i] = bezt[j].vec[1][0] - bezt[j-1].vec[1][0];
|
||||
dy[i] = bezt[j].vec[1][1] - bezt[j-1].vec[1][1];
|
||||
|
||||
/* when cyclic, jump from last point to first */
|
||||
if (cycle && j == total-1)
|
||||
j = 0;
|
||||
}
|
||||
|
||||
/* ratio of x intervals */
|
||||
|
||||
l[0] = l[count-1] = 1.0f;
|
||||
|
||||
for (int i = 1; i < count-1; i++)
|
||||
l[i] = dx[i+1] / dx[i];
|
||||
|
||||
/* compute handle clamp ranges */
|
||||
|
||||
bool clamped_prev = false, clamped_cur = ELEM(HD_AUTO_ANIM, bezt_first->h1, bezt_first->h2);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
hmax[i] = FLT_MAX;
|
||||
hmin[i] = -FLT_MAX;
|
||||
}
|
||||
|
||||
for (int i = 1, j = start+1; i < count; i++, j++) {
|
||||
clamped_prev = clamped_cur;
|
||||
clamped_cur = ELEM(HD_AUTO_ANIM, bezt[j].h1, bezt[j].h2);
|
||||
|
||||
if (cycle && j == total-1)
|
||||
{
|
||||
j = 0;
|
||||
clamped_cur = clamped_cur || ELEM(HD_AUTO_ANIM, bezt[j].h1, bezt[j].h2);
|
||||
}
|
||||
|
||||
bezier_clamp(hmax, hmin, i-1, dy[i], clamped_prev, clamped_prev);
|
||||
bezier_clamp(hmax, hmin, i, dy[i] * l[i], clamped_cur, clamped_cur);
|
||||
}
|
||||
|
||||
/* full cycle merges first and last points into continuous loop */
|
||||
|
||||
float first_handle_adj = 0.0f, last_handle_adj = 0.0f;
|
||||
|
||||
if (full_cycle) {
|
||||
/* reduce the number of uknowns by one */
|
||||
int i = solve_count = count-1;
|
||||
|
||||
dx[0] = dx[i];
|
||||
dy[0] = dy[i];
|
||||
|
||||
l[0] = l[i] = dx[1] / dx[0];
|
||||
|
||||
hmin[0] = max_ff(hmin[0], hmin[i]);
|
||||
hmax[0] = min_ff(hmax[0], hmax[i]);
|
||||
|
||||
solve_first = solve_last = true;
|
||||
|
||||
bezier_eq_continuous(a, b, c, d, dy, l, 0);
|
||||
}
|
||||
else {
|
||||
float tmp[2];
|
||||
|
||||
/* boundary condition: fixed handles or zero curvature */
|
||||
if (!solve_first) {
|
||||
sub_v2_v2v2(tmp, bezt_first->vec[2], bezt_first->vec[1]);
|
||||
first_handle_adj = bezier_calc_handle_adj(tmp, dx[1]);
|
||||
|
||||
bezier_lock_unknown(a, b, c, d, 0, tmp[1]);
|
||||
}
|
||||
else
|
||||
bezier_eq_noaccel_right(a, b, c, d, dy, l, 0);
|
||||
|
||||
if (!solve_last) {
|
||||
sub_v2_v2v2(tmp, bezt_last->vec[1], bezt_last->vec[0]);
|
||||
last_handle_adj = bezier_calc_handle_adj(tmp, dx[count-1]);
|
||||
|
||||
bezier_lock_unknown(a, b, c, d, count-1, tmp[1]);
|
||||
}
|
||||
else
|
||||
bezier_eq_noaccel_left(a, b, c, d, dy, l, count-1);
|
||||
}
|
||||
|
||||
/* main tridiagonal system of equations */
|
||||
|
||||
for (int i = 1; i < count-1; i++) {
|
||||
bezier_eq_continuous(a, b, c, d, dy, l, i);
|
||||
}
|
||||
|
||||
/* apply correction for user-defined handles with nonstandard x positions */
|
||||
|
||||
if (!full_cycle) {
|
||||
if (count > 2 || solve_last)
|
||||
b[1] += l[1]*first_handle_adj;
|
||||
|
||||
if (count > 2 || solve_first)
|
||||
b[count-2] += last_handle_adj;
|
||||
}
|
||||
|
||||
/* solve and output results */
|
||||
|
||||
if (tridiagonal_solve_with_limits(a, b, c, d, h, hmin, hmax, solve_count)) {
|
||||
if (full_cycle)
|
||||
h[count-1] = h[0];
|
||||
|
||||
for (int i = 1, j = start+1; i < count-1; i++, j++) {
|
||||
bool end = (j == total-1);
|
||||
|
||||
bezier_output_handle(&bezt[j], false, - h[i] / l[i], end);
|
||||
|
||||
if (end)
|
||||
j = 0;
|
||||
|
||||
bezier_output_handle(&bezt[j], true, h[i], end);
|
||||
}
|
||||
|
||||
if (solve_first)
|
||||
bezier_output_handle(bezt_first, true, h[0], start == 0);
|
||||
|
||||
if (solve_last)
|
||||
bezier_output_handle(bezt_last, false, - h[count-1] / l[count-1], start+count == total);
|
||||
}
|
||||
|
||||
/* free all */
|
||||
|
||||
MEM_freeN(tmp_buffer);
|
||||
}
|
||||
|
||||
static bool is_auto_point(BezTriple *bezt)
|
||||
{
|
||||
return ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM);
|
||||
}
|
||||
|
||||
static bool is_free_auto_point(BezTriple *bezt)
|
||||
{
|
||||
return is_auto_point(bezt) && bezt->f5 == HD_AUTOTYPE_NORMAL;
|
||||
}
|
||||
|
||||
static void bezier_handle_smooth_curve(void (*smooth_fn)(BezTriple*,int,int,int,bool), bool (*check_fn)(BezTriple*), BezTriple *bezt, int total, bool cycle)
|
||||
{
|
||||
/* ignore cyclic extrapolation if end points are locked */
|
||||
cycle = cycle && check_fn(&bezt[0]) && check_fn(&bezt[total-1]);
|
||||
|
||||
/* if cyclic, try to find a sequence break point */
|
||||
int search_base = 0;
|
||||
|
||||
if (cycle) {
|
||||
for (int i = 1; i < total-1; i++) {
|
||||
if (!check_fn(&bezt[i])) {
|
||||
search_base = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* all points of the curve are freely changeable auto handles - solve as full cycle */
|
||||
if (search_base == 0) {
|
||||
smooth_fn(bezt, total, 0, total, cycle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find continuous subsequences of free auto handles and smooth them, starting at
|
||||
* search_base. In cyclic mode these subsequences can span the cycle boundary. */
|
||||
int start = search_base, count = 1;
|
||||
|
||||
for (int i = 1, j = start+1; i < total; i++, j++) {
|
||||
/* in cyclic mode: jump from last to first point when necessary */
|
||||
if (j == total-1 && cycle)
|
||||
j = 0;
|
||||
|
||||
/* non auto handle closes the list (we come here at least for the last handle, see above) */
|
||||
if (!check_fn(&bezt[j])) {
|
||||
smooth_fn(bezt, total, start, count+1, cycle);
|
||||
start = j;
|
||||
count = 1;
|
||||
}
|
||||
else
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
smooth_fn(bezt, total, start, count, cycle);
|
||||
}
|
||||
|
||||
void BKE_nurb_handle_smooth_fcurve(BezTriple *bezt, int total, bool cycle)
|
||||
{
|
||||
bezier_handle_smooth_curve(bezier_handle_calc_smooth_fcurve, is_free_auto_point, bezt, total, cycle);
|
||||
}
|
||||
|
||||
void BKE_nurb_handle_calc(BezTriple *bezt, BezTriple *prev, BezTriple *next, const bool is_fcurve, const char smoothing)
|
||||
{
|
||||
calchandleNurb_intern(bezt, prev, next, is_fcurve, false, smoothing);
|
||||
}
|
||||
|
||||
void BKE_nurb_handles_calc(Nurb *nu) /* first, if needed, set handle flags */
|
||||
@@ -3454,7 +3978,7 @@ void BKE_nurb_handle_calc_simple(Nurb *nu, BezTriple *bezt)
|
||||
if (nu->pntsu > 1) {
|
||||
BezTriple *prev = BKE_nurb_bezt_get_prev(nu, bezt);
|
||||
BezTriple *next = BKE_nurb_bezt_get_next(nu, bezt);
|
||||
BKE_nurb_handle_calc(bezt, prev, next, 0);
|
||||
BKE_nurb_handle_calc(bezt, prev, next, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -882,6 +882,44 @@ void fcurve_store_samples(FCurve *fcu, void *data, int start, int end, FcuSample
|
||||
* that the handles are correctly
|
||||
*/
|
||||
|
||||
/* Checks if the F-Curve has a Cycles modifier with simple settings that warrant transition smoothing */
|
||||
static bool detect_cycle_modifier(FCurve *fcu)
|
||||
{
|
||||
FModifier *fcm = fcu->modifiers.first;
|
||||
|
||||
if (!fcm || fcm->type != FMODIFIER_TYPE_CYCLES)
|
||||
return false;
|
||||
|
||||
if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED))
|
||||
return false;
|
||||
|
||||
if (fcm->flag & (FMODIFIER_FLAG_RANGERESTRICT | FMODIFIER_FLAG_USEINFLUENCE))
|
||||
return false;
|
||||
|
||||
FMod_Cycles *data = (FMod_Cycles*)fcm->data;
|
||||
|
||||
return data && data->after_cycles == 0 && data->before_cycles == 0 &&
|
||||
ELEM(data->before_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET) &&
|
||||
ELEM(data->after_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET);
|
||||
}
|
||||
|
||||
/* If cyclic, set out by shifting in by the difference in position between from and to. */
|
||||
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;
|
||||
}
|
||||
|
||||
/* This function recalculates the handles of an F-Curve
|
||||
* If the BezTriples have been rearranged, sort them first before using this.
|
||||
*/
|
||||
@@ -897,10 +935,16 @@ void calchandles_fcurve(FCurve *fcu)
|
||||
*/
|
||||
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 = detect_cycle_modifier(fcu) && BEZT_IS_AUTOH(first) && BEZT_IS_AUTOH(last);
|
||||
|
||||
/* get initial pointers */
|
||||
bezt = fcu->bezt;
|
||||
prev = NULL;
|
||||
prev = cycle_offset_triple(cycle, &tmp, &fcu->bezt[fcu->totvert-2], last, first);
|
||||
next = (bezt + 1);
|
||||
|
||||
/* loop over all beztriples, adjusting handles */
|
||||
@@ -910,25 +954,45 @@ void calchandles_fcurve(FCurve *fcu)
|
||||
if (bezt->vec[2][0] < bezt->vec[1][0]) bezt->vec[2][0] = bezt->vec[1][0];
|
||||
|
||||
/* calculate auto-handles */
|
||||
BKE_nurb_handle_calc(bezt, prev, next, true);
|
||||
BKE_nurb_handle_calc(bezt, prev, next, true, fcu->auto_smoothing);
|
||||
|
||||
/* for automatic ease in and out */
|
||||
if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM)) {
|
||||
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 = NULL;
|
||||
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 accelaration step */
|
||||
if (fcu->auto_smoothing != FCURVE_SMOOTH_NONE) {
|
||||
BKE_nurb_handle_smooth_fcurve(fcu->bezt, fcu->totvert, cycle);
|
||||
}
|
||||
}
|
||||
|
||||
void testhandles_fcurve(FCurve *fcu, const bool use_handle)
|
||||
|
@@ -1077,7 +1077,7 @@ const FModifierTypeInfo *fmodifier_get_typeinfo(const FModifier *fcm)
|
||||
/* API --------------------------- */
|
||||
|
||||
/* Add a new F-Curve Modifier to the given F-Curve of a certain type */
|
||||
FModifier *add_fmodifier(ListBase *modifiers, int type)
|
||||
FModifier *add_fmodifier(ListBase *modifiers, int type, FCurve *owner_fcu)
|
||||
{
|
||||
const FModifierTypeInfo *fmi = get_fmodifier_typeinfo(type);
|
||||
FModifier *fcm;
|
||||
@@ -1098,6 +1098,7 @@ FModifier *add_fmodifier(ListBase *modifiers, int type)
|
||||
fcm = MEM_callocN(sizeof(FModifier), "F-Curve Modifier");
|
||||
fcm->type = type;
|
||||
fcm->flag = FMODIFIER_FLAG_EXPANDED;
|
||||
fcm->curve = owner_fcu;
|
||||
fcm->influence = 1.0f;
|
||||
BLI_addtail(modifiers, fcm);
|
||||
|
||||
@@ -1129,6 +1130,7 @@ FModifier *copy_fmodifier(const FModifier *src)
|
||||
/* copy the base data, clearing the links */
|
||||
dst = MEM_dupallocN(src);
|
||||
dst->next = dst->prev = NULL;
|
||||
dst->curve = NULL;
|
||||
|
||||
/* make a new copy of the F-Modifier's data */
|
||||
dst->data = MEM_dupallocN(src->data);
|
||||
@@ -1157,6 +1159,7 @@ void copy_fmodifiers(ListBase *dst, const ListBase *src)
|
||||
|
||||
/* make a new copy of the F-Modifier's data */
|
||||
fcm->data = MEM_dupallocN(fcm->data);
|
||||
fcm->curve = NULL;
|
||||
|
||||
/* only do specific constraints if required */
|
||||
if (fmi && fmi->copy_data)
|
||||
|
@@ -1192,7 +1192,7 @@ static void icu_to_fcurves(ID *id, ListBase *groups, ListBase *list, IpoCurve *i
|
||||
/* Add a new FModifier (Cyclic) instead of setting extend value
|
||||
* as that's the new equivalent of that option.
|
||||
*/
|
||||
FModifier *fcm = add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES);
|
||||
FModifier *fcm = add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, fcu);
|
||||
FMod_Cycles *data = (FMod_Cycles *)fcm->data;
|
||||
|
||||
/* if 'offset' one is in use, set appropriate settings */
|
||||
|
@@ -1204,14 +1204,14 @@ static void mask_calc_point_handle(MaskSplinePoint *point, MaskSplinePoint *poin
|
||||
|
||||
#if 1
|
||||
if (bezt_prev || bezt_next) {
|
||||
BKE_nurb_handle_calc(bezt, bezt_prev, bezt_next, 0);
|
||||
BKE_nurb_handle_calc(bezt, bezt_prev, bezt_next, 0, 0);
|
||||
}
|
||||
#else
|
||||
if (handle_type == HD_VECT) {
|
||||
BKE_nurb_handle_calc(bezt, bezt_prev, bezt_next, 0);
|
||||
BKE_nurb_handle_calc(bezt, bezt_prev, bezt_next, 0, 0);
|
||||
}
|
||||
else if (handle_type == HD_AUTO) {
|
||||
BKE_nurb_handle_calc(bezt, bezt_prev, bezt_next, 0);
|
||||
BKE_nurb_handle_calc(bezt, bezt_prev, bezt_next, 0, 0);
|
||||
}
|
||||
else if (handle_type == HD_ALIGN || handle_type == HD_ALIGN_DOUBLESIDE) {
|
||||
float v1[3], v2[3];
|
||||
|
@@ -1387,6 +1387,7 @@ void BKE_nlastrip_validate_fcurves(NlaStrip *strip)
|
||||
|
||||
/* set default flags */
|
||||
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
|
||||
fcu->auto_smoothing = FCURVE_SMOOTH_CONT_ACCEL;
|
||||
|
||||
/* store path - make copy, and store that */
|
||||
fcu->rna_path = BLI_strdupn("influence", 9);
|
||||
@@ -1408,6 +1409,7 @@ void BKE_nlastrip_validate_fcurves(NlaStrip *strip)
|
||||
|
||||
/* set default flags */
|
||||
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
|
||||
fcu->auto_smoothing = FCURVE_SMOOTH_CONT_ACCEL;
|
||||
|
||||
/* store path - make copy, and store that */
|
||||
fcu->rna_path = BLI_strdupn("strip_time", 10);
|
||||
|
@@ -48,6 +48,11 @@ bool BLI_eigen_solve_selfadjoint_m3(const float m3[3][3], float r_eigen_values[3
|
||||
|
||||
void BLI_svd_m3(const float m3[3][3], float r_U[3][3], float r_S[], float r_V[3][3]);
|
||||
|
||||
/***************************** Simple Solvers ************************************/
|
||||
|
||||
bool BLI_tridiagonal_solve(const float *a, const float *b, const float *c, const float *d, float *x, const int count);
|
||||
bool BLI_tridiagonal_solve_cyclic(const float *a, const float *b, const float *c, const float *d, float *x, const int count);
|
||||
|
||||
/**************************** Inline Definitions ******************************/
|
||||
#if 0 /* None so far. */
|
||||
# if BLI_MATH_DO_INLINE
|
||||
|
@@ -72,3 +72,115 @@ void BLI_svd_m3(const float m3[3][3], float r_U[3][3], float r_S[3], float r_V[3
|
||||
{
|
||||
EIG_svd_square_matrix(3, (const float *)m3, (float *)r_U, (float *)r_S, (float *)r_V);
|
||||
}
|
||||
|
||||
/***************************** Simple Solvers ************************************/
|
||||
|
||||
/**
|
||||
* \brief Solve a tridiagonal system of equations:
|
||||
*
|
||||
* a[i] * x[i-1] + b[i] * x[i] + c[i] * x[i+1] = d[i]
|
||||
*
|
||||
* Ignores a[0] and c[count-1]. Uses Thomas algorithm, e.g. see wiki.
|
||||
*
|
||||
* \param x output vector, may be shared with any of the input ones
|
||||
* \return true if success
|
||||
*/
|
||||
bool BLI_tridiagonal_solve(const float *a, const float *b, const float *c, const float *d, float *x, const int count)
|
||||
{
|
||||
if (count < 0)
|
||||
return false;
|
||||
|
||||
size_t bytes = sizeof(float)*(unsigned)count;
|
||||
float *c1 = (float *)MEM_mallocN(bytes*2, "tridiagonal_c1d1");
|
||||
float *d1 = c1 + count;
|
||||
|
||||
if (!c1)
|
||||
return false;
|
||||
|
||||
int i;
|
||||
double c_prev, d_prev, x_prev;
|
||||
|
||||
/* forward pass */
|
||||
|
||||
c_prev = ((double)c[0]) / b[0];
|
||||
d_prev = ((double)d[0]) / b[0];
|
||||
|
||||
c1[0] = ((float)c_prev);
|
||||
d1[0] = ((float)d_prev);
|
||||
|
||||
for (i = 1; i < count; i++) {
|
||||
double denum = b[i] - a[i]*c_prev;
|
||||
|
||||
c_prev = c[i] / denum;
|
||||
d_prev = (d[i] - a[i]*d_prev) / denum;
|
||||
|
||||
c1[i] = ((float)c_prev);
|
||||
d1[i] = ((float)d_prev);
|
||||
}
|
||||
|
||||
/* back pass */
|
||||
|
||||
x_prev = d_prev;
|
||||
x[--i] = ((float)x_prev);
|
||||
|
||||
while (--i >= 0) {
|
||||
x_prev = d1[i] - c1[i] * x_prev;
|
||||
x[i] = ((float)x_prev);
|
||||
}
|
||||
|
||||
MEM_freeN(c1);
|
||||
|
||||
return isfinite(x_prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Solve a possibly cyclic tridiagonal system using the Sherman-Morrison formula.
|
||||
*
|
||||
* \param x output vector, may be shared with any of the input ones
|
||||
* \return true if success
|
||||
*/
|
||||
bool BLI_tridiagonal_solve_cyclic(const float *a, const float *b, const float *c, const float *d, float *x, const int count)
|
||||
{
|
||||
if (count < 1)
|
||||
return false;
|
||||
|
||||
float a0 = a[0], cN = c[count-1];
|
||||
|
||||
if (a0 == 0.0f && cN == 0.0f) {
|
||||
return BLI_tridiagonal_solve(a, b, c, d, x, count);
|
||||
}
|
||||
|
||||
size_t bytes = sizeof(float)*(unsigned)count;
|
||||
float *tmp = (float*)MEM_mallocN(bytes*2, "tridiagonal_ex");
|
||||
float *b2 = tmp + count;
|
||||
|
||||
if (!tmp)
|
||||
return false;
|
||||
|
||||
/* prepare the noncyclic system; relies on tridiagonal_solve ignoring values */
|
||||
memcpy(b2, b, bytes);
|
||||
b2[0] -= a0;
|
||||
b2[count-1] -= cN;
|
||||
|
||||
memset(tmp, 0, bytes);
|
||||
tmp[0] = a0;
|
||||
tmp[count-1] = cN;
|
||||
|
||||
/* solve for partial solution and adjustment vector */
|
||||
bool success =
|
||||
BLI_tridiagonal_solve(a, b2, c, tmp, tmp, count) &&
|
||||
BLI_tridiagonal_solve(a, b2, c, d, x, count);
|
||||
|
||||
/* apply adjustment */
|
||||
if (success) {
|
||||
float coeff = (x[0] + x[count-1]) / (1.0f + tmp[0] + tmp[count-1]);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
x[i] -= coeff * tmp[i];
|
||||
}
|
||||
|
||||
MEM_freeN(tmp);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@@ -2389,11 +2389,13 @@ static void lib_link_constraint_channels(FileData *fd, ID *id, ListBase *chanbas
|
||||
|
||||
/* Data Linking ----------------------------- */
|
||||
|
||||
static void lib_link_fmodifiers(FileData *fd, ID *id, ListBase *list)
|
||||
static void lib_link_fmodifiers(FileData *fd, ID *id, ListBase *list, FCurve *curve)
|
||||
{
|
||||
FModifier *fcm;
|
||||
|
||||
for (fcm = list->first; fcm; fcm = fcm->next) {
|
||||
fcm->curve = curve;
|
||||
|
||||
/* data for specific modifiers */
|
||||
switch (fcm->type) {
|
||||
case FMODIFIER_TYPE_PYTHON:
|
||||
@@ -2435,19 +2437,20 @@ static void lib_link_fcurves(FileData *fd, ID *id, ListBase *list)
|
||||
}
|
||||
|
||||
/* modifiers */
|
||||
lib_link_fmodifiers(fd, id, &fcu->modifiers);
|
||||
lib_link_fmodifiers(fd, id, &fcu->modifiers, fcu);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* NOTE: this assumes that link_list has already been called on the list */
|
||||
static void direct_link_fmodifiers(FileData *fd, ListBase *list)
|
||||
static void direct_link_fmodifiers(FileData *fd, ListBase *list, FCurve *curve)
|
||||
{
|
||||
FModifier *fcm;
|
||||
|
||||
for (fcm = list->first; fcm; fcm = fcm->next) {
|
||||
/* relink general data */
|
||||
fcm->data = newdataadr(fd, fcm->data);
|
||||
fcm->curve = curve;
|
||||
|
||||
/* do relinking of data for specific types */
|
||||
switch (fcm->type) {
|
||||
@@ -2506,6 +2509,12 @@ static void direct_link_fcurves(FileData *fd, ListBase *list)
|
||||
*/
|
||||
fcu->flag &= ~FCURVE_DISABLED;
|
||||
|
||||
/* TEMPORARY HACK */
|
||||
if (fcu->flag & (1<<13)) {
|
||||
fcu->flag &= ~(1<<13);
|
||||
fcu->auto_smoothing = FCURVE_SMOOTH_CONT_ACCEL;
|
||||
}
|
||||
|
||||
/* driver */
|
||||
fcu->driver= newdataadr(fd, fcu->driver);
|
||||
if (fcu->driver) {
|
||||
@@ -2537,7 +2546,7 @@ static void direct_link_fcurves(FileData *fd, ListBase *list)
|
||||
|
||||
/* modifiers */
|
||||
link_list(fd, &fcu->modifiers);
|
||||
direct_link_fmodifiers(fd, &fcu->modifiers);
|
||||
direct_link_fmodifiers(fd, &fcu->modifiers, fcu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2642,7 +2651,7 @@ static void direct_link_nladata_strips(FileData *fd, ListBase *list)
|
||||
|
||||
/* strip's F-Modifiers */
|
||||
link_list(fd, &strip->modifiers);
|
||||
direct_link_fmodifiers(fd, &strip->modifiers);
|
||||
direct_link_fmodifiers(fd, &strip->modifiers, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -101,6 +101,7 @@ FCurve *verify_driver_fcurve(ID *id, const char rna_path[], const int array_inde
|
||||
fcu = MEM_callocN(sizeof(FCurve), "FCurve");
|
||||
|
||||
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
|
||||
fcu->auto_smoothing = FCURVE_SMOOTH_CONT_ACCEL;
|
||||
|
||||
/* store path - make copy, and store that */
|
||||
fcu->rna_path = BLI_strdup(rna_path);
|
||||
@@ -122,7 +123,7 @@ FCurve *verify_driver_fcurve(ID *id, const char rna_path[], const int array_inde
|
||||
* Create FModifier so that old scripts won't break
|
||||
* for now before 2.7 series -- (September 4, 2013)
|
||||
*/
|
||||
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR);
|
||||
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR, fcu);
|
||||
}
|
||||
else {
|
||||
/* add 2 keyframes so that user has something to work with
|
||||
|
@@ -90,7 +90,9 @@ static void delete_fmodifier_cb(bContext *C, void *fmods_v, void *fcm_v)
|
||||
{
|
||||
ListBase *modifiers = (ListBase *)fmods_v;
|
||||
FModifier *fcm = (FModifier *)fcm_v;
|
||||
|
||||
|
||||
FCurve *update_fcu = fcm->type == FMODIFIER_TYPE_CYCLES ? fcm->curve : NULL;
|
||||
|
||||
/* remove the given F-Modifier from the active modifier-stack */
|
||||
remove_fmodifier(modifiers, fcm);
|
||||
|
||||
@@ -99,6 +101,9 @@ static void delete_fmodifier_cb(bContext *C, void *fmods_v, void *fcm_v)
|
||||
/* send notifiers */
|
||||
// XXX for now, this is the only way to get updates in all the right places... but would be nice to have a special one in this case
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
|
||||
|
||||
if (update_fcu)
|
||||
calchandles_fcurve(update_fcu);
|
||||
}
|
||||
|
||||
/* --------------- */
|
||||
@@ -736,7 +741,7 @@ bool ANIM_fmodifiers_copy_to_buf(ListBase *modifiers, bool active)
|
||||
/* 'Paste' the F-Modifier(s) from the buffer to the specified list
|
||||
* - replace: free all the existing modifiers to leave only the pasted ones
|
||||
*/
|
||||
bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace)
|
||||
bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace, FCurve *curve)
|
||||
{
|
||||
FModifier *fcm;
|
||||
bool ok = false;
|
||||
@@ -753,6 +758,8 @@ bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace)
|
||||
for (fcm = fmodifier_copypaste_buf.first; fcm; fcm = fcm->next) {
|
||||
/* make a copy of it */
|
||||
FModifier *fcmN = copy_fmodifier(fcm);
|
||||
|
||||
fcmN->curve = curve;
|
||||
|
||||
/* make sure the new one isn't active, otherwise the list may get several actives */
|
||||
fcmN->flag &= ~FMODIFIER_FLAG_ACTIVE;
|
||||
|
@@ -186,6 +186,7 @@ FCurve *verify_fcurve(bAction *act, const char group[], PointerRNA *ptr,
|
||||
fcu = MEM_callocN(sizeof(FCurve), "FCurve");
|
||||
|
||||
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
|
||||
fcu->auto_smoothing = FCURVE_SMOOTH_CONT_ACCEL;
|
||||
if (BLI_listbase_is_empty(&act->curves))
|
||||
fcu->flag |= FCURVE_ACTIVE; /* first one added active */
|
||||
|
||||
|
@@ -592,7 +592,7 @@ static void calc_keyHandles(ListBase *nurb, float *key)
|
||||
if (nextp) key_to_bezt(nextfp, nextp, &next);
|
||||
if (prevp) key_to_bezt(prevfp, prevp, &prev);
|
||||
|
||||
BKE_nurb_handle_calc(&cur, prevp ? &prev : NULL, nextp ? &next : NULL, 0);
|
||||
BKE_nurb_handle_calc(&cur, prevp ? &prev : NULL, nextp ? &next : NULL, 0, 0);
|
||||
bezt_to_key(&cur, fp);
|
||||
|
||||
prevp = bezt;
|
||||
|
@@ -563,7 +563,7 @@ bool ANIM_fmodifiers_copy_to_buf(ListBase *modifiers, bool active);
|
||||
/* 'Paste' the F-Modifier(s) from the buffer to the specified list
|
||||
* - replace: free all the existing modifiers to leave only the pasted ones
|
||||
*/
|
||||
bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace);
|
||||
bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace, struct FCurve *curve);
|
||||
|
||||
/* ************************************************* */
|
||||
/* ASSORTED TOOLS */
|
||||
|
@@ -360,6 +360,9 @@ bool UI_context_copy_to_selected_list(
|
||||
else if (RNA_struct_is_a(ptr->type, &RNA_Sequence)) {
|
||||
*r_lb = CTX_data_collection_get(C, "selected_editable_sequences");
|
||||
}
|
||||
else if (RNA_struct_is_a(ptr->type, &RNA_FCurve)) {
|
||||
*r_lb = CTX_data_collection_get(C, "selected_editable_fcurves");
|
||||
}
|
||||
else if (RNA_struct_is_a(ptr->type, &RNA_Node) ||
|
||||
RNA_struct_is_a(ptr->type, &RNA_NodeSocket))
|
||||
{
|
||||
|
@@ -1025,7 +1025,7 @@ static int followpath_path_animate_exec(bContext *C, wmOperator *op)
|
||||
* and define basic slope of this curve based on the properties
|
||||
*/
|
||||
if (!fcu->bezt && !fcu->fpt && !fcu->modifiers.first) {
|
||||
FModifier *fcm = add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR);
|
||||
FModifier *fcm = add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR, fcu);
|
||||
FMod_Generator *gen = fcm->data;
|
||||
|
||||
/* Assume that we have the following equation:
|
||||
|
@@ -637,7 +637,7 @@ bool ED_object_parent_set(ReportList *reports, Main *bmain, Scene *scene, Object
|
||||
|
||||
/* setup dummy 'generator' modifier here to get 1-1 correspondence still working */
|
||||
if (!fcu->bezt && !fcu->fpt && !fcu->modifiers.first)
|
||||
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR);
|
||||
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR, fcu);
|
||||
}
|
||||
|
||||
/* fall back on regular parenting now (for follow only) */
|
||||
|
@@ -54,6 +54,7 @@
|
||||
|
||||
#include "ED_armature.h"
|
||||
#include "ED_gpencil.h"
|
||||
#include "ED_anim_api.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
#include "UI_interface.h"
|
||||
@@ -87,7 +88,7 @@ const char *screen_context_dir[] = {
|
||||
"visible_gpencil_layers", "editable_gpencil_layers", "editable_gpencil_strokes",
|
||||
"active_gpencil_layer", "active_gpencil_frame", "active_gpencil_palette",
|
||||
"active_gpencil_palettecolor", "active_gpencil_brush",
|
||||
"active_operator",
|
||||
"active_operator", "selected_editable_fcurves",
|
||||
NULL};
|
||||
|
||||
int ed_screen_context(const bContext *C, const char *member, bContextDataResult *result)
|
||||
@@ -608,6 +609,30 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (CTX_data_equals(member, "selected_editable_fcurves"))
|
||||
{
|
||||
bAnimContext ac;
|
||||
|
||||
if (ANIM_animdata_get_context(C, &ac) && ELEM(ac.spacetype, SPACE_ACTION, SPACE_IPO)) {
|
||||
bAnimListElem *ale;
|
||||
ListBase anim_data = {NULL, NULL};
|
||||
|
||||
int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS | ANIMFILTER_SEL) |
|
||||
(ac.spacetype == SPACE_IPO ? ANIMFILTER_CURVE_VISIBLE : ANIMFILTER_LIST_VISIBLE);
|
||||
|
||||
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
||||
|
||||
for (ale = anim_data.first; ale; ale = ale->next) {
|
||||
if (ale->type == ANIMTYPE_FCURVE)
|
||||
CTX_data_list_add(result, ale->id, &RNA_FCurve, ale->data);
|
||||
}
|
||||
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
|
||||
CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
@@ -1146,7 +1146,7 @@ static void setexpo_action_keys(bAnimContext *ac, short mode)
|
||||
/* only add if one doesn't exist */
|
||||
if (list_has_suitable_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, -1) == 0) {
|
||||
/* TODO: add some more preset versions which set different extrapolation options? */
|
||||
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES);
|
||||
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, fcu);
|
||||
}
|
||||
}
|
||||
else if (mode == CLEAR_CYCLIC_EXPO) {
|
||||
|
@@ -208,7 +208,12 @@ static void graph_panel_properties(const bContext *C, Panel *pa)
|
||||
sub = uiLayoutRow(row, true);
|
||||
uiLayoutSetEnabled(sub, (fcu->color_mode == FCURVE_COLOR_CUSTOM));
|
||||
uiItemR(sub, &fcu_ptr, "color", 0, "", ICON_NONE);
|
||||
|
||||
|
||||
/* smoothing setting */
|
||||
col = uiLayoutColumn(layout, true);
|
||||
uiItemL(col, IFACE_("Auto Handle Smoothing:"), ICON_NONE);
|
||||
uiItemR(col, &fcu_ptr, "auto_smoothing", 0, "", ICON_NONE);
|
||||
|
||||
MEM_freeN(ale);
|
||||
}
|
||||
|
||||
|
@@ -1504,7 +1504,7 @@ static void setexpo_graph_keys(bAnimContext *ac, short mode)
|
||||
/* only add if one doesn't exist */
|
||||
if (list_has_suitable_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, -1) == 0) {
|
||||
// TODO: add some more preset versions which set different extrapolation options?
|
||||
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES);
|
||||
add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, fcu);
|
||||
}
|
||||
}
|
||||
else if (mode == CLEAR_CYCLIC_EXPO) {
|
||||
@@ -1520,7 +1520,7 @@ static void setexpo_graph_keys(bAnimContext *ac, short mode)
|
||||
}
|
||||
}
|
||||
|
||||
ale->update |= ANIM_UPDATE_DEPS;
|
||||
ale->update |= ANIM_UPDATE_DEFAULT;
|
||||
}
|
||||
|
||||
ANIM_animdata_update(ac, &anim_data);
|
||||
@@ -2446,7 +2446,7 @@ static int graph_fmodifier_add_exec(bContext *C, wmOperator *op)
|
||||
FModifier *fcm;
|
||||
|
||||
/* add F-Modifier of specified type to active F-Curve, and make it the active one */
|
||||
fcm = add_fmodifier(&fcu->modifiers, type);
|
||||
fcm = add_fmodifier(&fcu->modifiers, type, fcu);
|
||||
if (fcm) {
|
||||
set_active_fmodifier(&fcu->modifiers, fcm);
|
||||
}
|
||||
@@ -2455,7 +2455,7 @@ static int graph_fmodifier_add_exec(bContext *C, wmOperator *op)
|
||||
break;
|
||||
}
|
||||
|
||||
ale->update |= ANIM_UPDATE_DEPS;
|
||||
ale->update |= ANIM_UPDATE_DEPS | ANIM_UPDATE_HANDLES;
|
||||
}
|
||||
|
||||
ANIM_animdata_update(&ac, &anim_data);
|
||||
@@ -2582,10 +2582,10 @@ static int graph_fmodifier_paste_exec(bContext *C, wmOperator *op)
|
||||
FCurve *fcu = (FCurve *)ale->data;
|
||||
int tot;
|
||||
|
||||
tot = ANIM_fmodifiers_paste_from_buf(&fcu->modifiers, replace);
|
||||
tot = ANIM_fmodifiers_paste_from_buf(&fcu->modifiers, replace, fcu);
|
||||
|
||||
if (tot) {
|
||||
ale->update |= ANIM_UPDATE_DEPS;
|
||||
ale->update |= ANIM_UPDATE_DEPS | ANIM_UPDATE_HANDLES;
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
|
@@ -2316,7 +2316,7 @@ static int nla_fmodifier_add_exec(bContext *C, wmOperator *op)
|
||||
continue;
|
||||
|
||||
/* add F-Modifier of specified type to selected, and make it the active one */
|
||||
fcm = add_fmodifier(&strip->modifiers, type);
|
||||
fcm = add_fmodifier(&strip->modifiers, type, NULL);
|
||||
|
||||
if (fcm) {
|
||||
set_active_fmodifier(&strip->modifiers, fcm);
|
||||
@@ -2470,8 +2470,8 @@ static int nla_fmodifier_paste_exec(bContext *C, wmOperator *op)
|
||||
}
|
||||
|
||||
/* paste FModifiers from buffer */
|
||||
ok += ANIM_fmodifiers_paste_from_buf(&strip->modifiers, replace);
|
||||
ale->update |= ANIM_UPDATE_DEPS;
|
||||
ok += ANIM_fmodifiers_paste_from_buf(&strip->modifiers, replace, NULL);
|
||||
ale->update |= ANIM_UPDATE_DEPS | ANIM_UPDATE_HANDLES;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -52,6 +52,7 @@ extern "C" {
|
||||
typedef struct FModifier {
|
||||
struct FModifier *next, *prev;
|
||||
|
||||
struct FCurve *curve; /* containing curve, only used for updates to CYCLES */
|
||||
void *data; /* pointer to modifier data */
|
||||
|
||||
char name[64]; /* user-defined description for the modifier - MAX_ID_NAME-2 */
|
||||
@@ -489,7 +490,10 @@ typedef struct FCurve {
|
||||
float curval; /* value stored from last time curve was evaluated (not threadsafe, debug display only!) */
|
||||
short flag; /* user-editable settings for this curve */
|
||||
short extend; /* value-extending mode for this curve (does not cover */
|
||||
char auto_smoothing; /* auto-handle smoothing mode */
|
||||
|
||||
char pad[7];
|
||||
|
||||
/* RNA - data link */
|
||||
int array_index; /* if applicable, the index of the RNA-array item to get */
|
||||
char *rna_path; /* RNA-path to resolve data-access */
|
||||
@@ -544,6 +548,12 @@ typedef enum eFCurve_Coloring {
|
||||
FCURVE_COLOR_CUSTOM = 2, /* custom color */
|
||||
} eFCurve_Coloring;
|
||||
|
||||
/* curve smoothing modes */
|
||||
typedef enum eFCurve_Smoothing {
|
||||
FCURVE_SMOOTH_NONE = 0, /* legacy mode: auto handles only consider adjacent points */
|
||||
FCURVE_SMOOTH_CONT_ACCEL = 1, /* maintain continuity of the acceleration */
|
||||
} eFCurve_Smoothing;
|
||||
|
||||
/* ************************************************ */
|
||||
/* 'Action' Datatypes */
|
||||
|
||||
|
@@ -124,7 +124,8 @@ typedef struct BezTriple {
|
||||
float back; /* BEZT_IPO_BACK */
|
||||
float amplitude, period; /* BEZT_IPO_ELASTIC */
|
||||
|
||||
char pad[4];
|
||||
char f5; /* f5: used for auto handle to distinguish between normal handle and exception (extrema) */
|
||||
char pad[3];
|
||||
} BezTriple;
|
||||
|
||||
/* note; alfa location in struct is abused by Key system */
|
||||
@@ -389,6 +390,12 @@ typedef enum eBezTriple_Handle {
|
||||
HD_ALIGN_DOUBLESIDE = 5, /* align handles, displayed both of them. used for masks */
|
||||
} eBezTriple_Handle;
|
||||
|
||||
/* f5 (beztriple) */
|
||||
typedef enum eBezTriple_Auto_Type {
|
||||
HD_AUTOTYPE_NORMAL = 0,
|
||||
HD_AUTOTYPE_SPECIAL = 1
|
||||
} eBezTriple_Auto_Type;
|
||||
|
||||
/* interpolation modes (used only for BezTriple->ipo) */
|
||||
typedef enum eBezTriple_Interpolation {
|
||||
/* traditional interpolation */
|
||||
@@ -436,6 +443,8 @@ typedef enum eBezTriple_KeyframeType {
|
||||
#define BEZT_SEL_ALL(bezt) { (bezt)->f1 |= SELECT; (bezt)->f2 |= SELECT; (bezt)->f3 |= SELECT; } ((void)0)
|
||||
#define BEZT_DESEL_ALL(bezt) { (bezt)->f1 &= ~SELECT; (bezt)->f2 &= ~SELECT; (bezt)->f3 &= ~SELECT; } ((void)0)
|
||||
|
||||
#define BEZT_IS_AUTOH(bezt) (ELEM((bezt)->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM((bezt)->h2, HD_AUTO, HD_AUTO_ANIM))
|
||||
|
||||
/* *************** CHARINFO **************** */
|
||||
|
||||
/* CharInfo.flag */
|
||||
|
@@ -495,7 +495,10 @@ static void rna_FCurve_active_modifier_set(PointerRNA *ptr, PointerRNA value)
|
||||
|
||||
static FModifier *rna_FCurve_modifiers_new(FCurve *fcu, int type)
|
||||
{
|
||||
return add_fmodifier(&fcu->modifiers, type);
|
||||
FModifier *fcm = add_fmodifier(&fcu->modifiers, type, fcu);
|
||||
if (type == FMODIFIER_TYPE_CYCLES)
|
||||
calchandles_fcurve(fcu);
|
||||
return fcm;
|
||||
}
|
||||
|
||||
static void rna_FCurve_modifiers_remove(FCurve *fcu, ReportList *reports, PointerRNA *fcm_ptr)
|
||||
@@ -506,8 +509,13 @@ static void rna_FCurve_modifiers_remove(FCurve *fcu, ReportList *reports, Pointe
|
||||
return;
|
||||
}
|
||||
|
||||
bool update = (fcm->type == FMODIFIER_TYPE_CYCLES);
|
||||
|
||||
remove_fmodifier(&fcu->modifiers, fcm);
|
||||
RNA_POINTER_INVALIDATE(fcm_ptr);
|
||||
|
||||
if (update)
|
||||
calchandles_fcurve(fcu);
|
||||
}
|
||||
|
||||
static void rna_FModifier_active_set(PointerRNA *ptr, int UNUSED(value))
|
||||
@@ -586,11 +594,15 @@ static void rna_FModifier_blending_range(PointerRNA *ptr, float *min, float *max
|
||||
static void rna_FModifier_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
|
||||
{
|
||||
ID *id = ptr->id.data;
|
||||
FModifier *fcm = (FModifier *)ptr->data;
|
||||
AnimData *adt = BKE_animdata_from_id(id);
|
||||
DAG_id_tag_update(id, (GS(id->name) == ID_OB) ? OB_RECALC_OB : OB_RECALC_DATA);
|
||||
if (adt != NULL) {
|
||||
adt->recalc |= ADT_RECALC_ANIM;
|
||||
}
|
||||
if (fcm->curve && fcm->type == FMODIFIER_TYPE_CYCLES) {
|
||||
calchandles_fcurve(fcm->curve);
|
||||
}
|
||||
}
|
||||
|
||||
static void rna_FModifier_verify_data_update(Main *bmain, Scene *scene, PointerRNA *ptr)
|
||||
@@ -1891,6 +1903,11 @@ static void rna_def_fcurve(BlenderRNA *brna)
|
||||
"Use custom hand-picked color for F-Curve"},
|
||||
{0, NULL, 0, NULL, NULL}
|
||||
};
|
||||
static EnumPropertyItem prop_mode_smoothing_items[] = {
|
||||
{FCURVE_SMOOTH_NONE, "NONE", 0, "None", "Auto handles only take adjacent keys into account (legacy mode)"},
|
||||
{FCURVE_SMOOTH_CONT_ACCEL, "CONT_ACCEL", 0, "Continuous Acceleration", "Auto handles are placed to avoid jumps in acceleration"},
|
||||
{0, NULL, 0, NULL, NULL}
|
||||
};
|
||||
|
||||
srna = RNA_def_struct(brna, "FCurve", NULL);
|
||||
RNA_def_struct_ui_text(srna, "F-Curve", "F-Curve defining values of a period of time");
|
||||
@@ -1964,6 +1981,11 @@ static void rna_def_fcurve(BlenderRNA *brna)
|
||||
RNA_def_property_ui_text(prop, "Hide", "F-Curve and its keyframes are hidden in the Graph Editor graphs");
|
||||
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_GRAPH, NULL);
|
||||
|
||||
prop = RNA_def_property(srna, "auto_smoothing", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, prop_mode_smoothing_items);
|
||||
RNA_def_property_ui_text(prop, "Auto Handle Smoothing", "Algorithm used to compute automatic handles");
|
||||
RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FCurve_update_data");
|
||||
|
||||
/* State Info (for Debugging) */
|
||||
prop = RNA_def_property(srna, "is_valid", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", FCURVE_DISABLED);
|
||||
|
Reference in New Issue
Block a user