You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
12 KiB
386 lines
12 KiB
/****************************************************************************
|
|
Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd.
|
|
|
|
https://www.cocos.com/
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#pragma once
|
|
|
|
namespace cc {
|
|
namespace geometry {
|
|
|
|
constexpr auto LOOK_FORWARD = 3;
|
|
|
|
/**
|
|
* @en
|
|
* A key frame in the curve.
|
|
* @zh
|
|
* 曲线中的一个关键帧。
|
|
*/
|
|
struct Keyframe {
|
|
/**
|
|
* @en Current frame time.
|
|
* @zh 当前帧时间。
|
|
*/
|
|
float time = 0;
|
|
|
|
/**
|
|
* @en Current frame value.
|
|
* @zh 当前帧的值。
|
|
*/
|
|
float value = 0;
|
|
|
|
/**
|
|
* @en In tangent value.
|
|
* @zh 左切线。
|
|
*/
|
|
float inTangent = 0;
|
|
|
|
/**
|
|
* @en Out tangent value.
|
|
* @zh 右切线。
|
|
*/
|
|
float outTangent = 0;
|
|
};
|
|
|
|
float evalOptCurve(float t, const ccstd::vector<float> &coefs);
|
|
|
|
struct OptimizedKey {
|
|
float index;
|
|
float time;
|
|
float endTime;
|
|
ccstd::vector<float> coefficient;
|
|
OptimizedKey() {
|
|
index = -1;
|
|
time = 0;
|
|
endTime = 0;
|
|
coefficient.resize(4);
|
|
}
|
|
static float evaluate(float t);
|
|
};
|
|
|
|
/**
|
|
* @en
|
|
* Describe a curve in which three times Hermite interpolation is used for each adjacent key frame.
|
|
* @zh
|
|
* 描述一条曲线,其中每个相邻关键帧采用三次hermite插值计算。
|
|
*/
|
|
class AnimationCurve {
|
|
// @serializable
|
|
private:
|
|
// _curve ! : RealCurve;
|
|
|
|
public:
|
|
static ccstd::vector<KeyFrame> defaultKF = [ {
|
|
time : 0,
|
|
value : 1,
|
|
inTangent : 0,
|
|
outTangent : 0,
|
|
},
|
|
{
|
|
time : 1,
|
|
value : 1,
|
|
inTangent : 0,
|
|
outTangent : 0,
|
|
} ];
|
|
|
|
/**
|
|
* For internal usage only.
|
|
* @internal
|
|
*/
|
|
get _internalCurve() {
|
|
return this._curve;
|
|
}
|
|
|
|
/**
|
|
* @en
|
|
* The key frame of the curve.
|
|
* @zh
|
|
* 曲线的关键帧。
|
|
*/
|
|
auto getKeyFrames() {
|
|
return Array.from(this._curve.keyframes()).map(([ time, value ]) = > {
|
|
const legacyKeyframe = ccnew Keyframe();
|
|
legacyKeyframe.time = time;
|
|
legacyKeyframe.value = value.value;
|
|
legacyKeyframe.inTangent = value.leftTangent;
|
|
legacyKeyframe.outTangent = value.rightTangent;
|
|
return legacyKeyframe;
|
|
});
|
|
}
|
|
|
|
void setKeyFrames(value) {
|
|
this._curve.assignSorted(value.map((legacyCurve) = > [
|
|
legacyCurve.time,
|
|
{
|
|
interpolationMode : RealInterpolationMode.CUBIC,
|
|
value : legacyCurve.value,
|
|
leftTangent : legacyCurve.inTangent,
|
|
rightTangent : legacyCurve.outTangent,
|
|
},
|
|
]));
|
|
}
|
|
|
|
/**
|
|
* @en
|
|
* Loop mode [[WrapMode]] when the sampling time exceeds the left end.
|
|
* @zh
|
|
* 当采样时间超出左端时采用的循环模式[[WrapMode]]。
|
|
*/
|
|
auto getPreWrapMode() {
|
|
return toLegacyWrapMode(this._curve.preExtrapolation);
|
|
}
|
|
|
|
void sPreWrapMode(value) {
|
|
this._curve.preExtrapolation = fromLegacyWrapMode(value);
|
|
}
|
|
|
|
/**
|
|
* @en
|
|
* Cycle mode [[WrapMode]] when the sampling time exceeds the right end.
|
|
* @zh
|
|
* 当采样时间超出右端时采用的循环模式[[WrapMode]]。
|
|
*/
|
|
get postWrapMode() {
|
|
return toLegacyWrapMode(this._curve.postExtrapolation);
|
|
}
|
|
|
|
set postWrapMode(value) {
|
|
this._curve.postExtrapolation = fromLegacyWrapMode(value);
|
|
}
|
|
|
|
private
|
|
cachedKey : OptimizedKey;
|
|
|
|
/**
|
|
* 构造函数。
|
|
* @param keyFrames 关键帧。
|
|
*/
|
|
constructor(keyFrames
|
|
: Keyframe[] | null | RealCurve = null) {
|
|
if (keyFrames instanceof RealCurve) {
|
|
this._curve = keyFrames;
|
|
} else {
|
|
const curve = ccnew RealCurve();
|
|
this._curve = curve;
|
|
curve.preExtrapolation = ExtrapolationMode.LOOP;
|
|
curve.postExtrapolation = ExtrapolationMode.CLAMP;
|
|
if (!keyFrames) {
|
|
curve.assignSorted([
|
|
[ 0.0, {interpolationMode : RealInterpolationMode.CUBIC, value : 1.0} ],
|
|
[ 1.0, {interpolationMode : RealInterpolationMode.CUBIC, value : 1.0} ],
|
|
]);
|
|
} else {
|
|
curve.assignSorted(keyFrames.map((legacyKeyframe) = > [ legacyKeyframe.time, {
|
|
interpolationMode : RealInterpolationMode.CUBIC,
|
|
value : legacyKeyframe.value,
|
|
leftTangent : legacyKeyframe.inTangent,
|
|
rightTangent : legacyKeyframe.outTangent,
|
|
} ]));
|
|
}
|
|
}
|
|
this.cachedKey = ccnew OptimizedKey();
|
|
}
|
|
|
|
/**
|
|
* @en
|
|
* Add a keyframe.
|
|
* @zh
|
|
* 添加一个关键帧。
|
|
* @param keyFrame 关键帧。
|
|
*/
|
|
public
|
|
addKey(keyFrame
|
|
: Keyframe | null) {
|
|
if (!keyFrame) {
|
|
this._curve.clear();
|
|
} else {
|
|
this._curve.addKeyFrame(keyFrame.time, {
|
|
interpolationMode : RealInterpolationMode.CUBIC,
|
|
value : keyFrame.value,
|
|
leftTangent : keyFrame.inTangent,
|
|
rightTangent : keyFrame.outTangent,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @ignore
|
|
* @param time
|
|
*/
|
|
public
|
|
evaluate_slow(time
|
|
: number) {
|
|
return this._curve.evaluate(time);
|
|
}
|
|
|
|
/**
|
|
* @en
|
|
* Calculate the curve interpolation at a given point in time.
|
|
* @zh
|
|
* 计算给定时间点的曲线插值。
|
|
* @param time 时间。
|
|
*/
|
|
public
|
|
evaluate(time
|
|
: number) {
|
|
const {cachedKey, _curve : curve} = this;
|
|
const nKeyframes = curve.keyFramesCount;
|
|
const lastKeyframeIndex = nKeyframes - 1;
|
|
let wrappedTime = time;
|
|
const extrapolationMode = time < 0 ? curve.preExtrapolation : curve.postExtrapolation;
|
|
const startTime = curve.getKeyframeTime(0);
|
|
const endTime = curve.getKeyframeTime(lastKeyframeIndex);
|
|
switch (extrapolationMode) {
|
|
case ExtrapolationMode.LOOP:
|
|
wrappedTime = repeat(time - startTime, endTime - startTime) + startTime;
|
|
break;
|
|
case ExtrapolationMode.PING_PONG:
|
|
wrappedTime = pingPong(time - startTime, endTime - startTime) + startTime;
|
|
break;
|
|
case ExtrapolationMode.CLAMP:
|
|
default:
|
|
wrappedTime = clamp(time, startTime, endTime);
|
|
break;
|
|
}
|
|
if (wrappedTime >= cachedKey.time && wrappedTime < cachedKey.endTime) {
|
|
return cachedKey.evaluate(wrappedTime);
|
|
}
|
|
const leftIndex = this.findIndex(cachedKey, wrappedTime);
|
|
const rightIndex = Math.min(leftIndex + 1, lastKeyframeIndex);
|
|
this.calcOptimizedKey(cachedKey, leftIndex, rightIndex);
|
|
return cachedKey.evaluate(wrappedTime);
|
|
}
|
|
|
|
/**
|
|
* @ignore
|
|
* @param optKey
|
|
* @param leftIndex
|
|
* @param rightIndex
|
|
*/
|
|
public
|
|
calcOptimizedKey(optKey
|
|
: OptimizedKey, leftIndex
|
|
: number, rightIndex
|
|
: number) {
|
|
const lhsTime = this._curve.getKeyframeTime(leftIndex);
|
|
const rhsTime = this._curve.getKeyframeTime(rightIndex);
|
|
const {value : lhsValue, leftTangent : lhsOutTangent} = this._curve.getKeyframeValue(leftIndex);
|
|
const {value : rhsValue, rightTangent : rhsInTangent} = this._curve.getKeyframeValue(rightIndex);
|
|
optKey.index = leftIndex;
|
|
optKey.time = lhsTime;
|
|
optKey.endTime = rhsTime;
|
|
|
|
const dx = rhsTime - lhsTime;
|
|
const dy = rhsValue - lhsValue;
|
|
const length = 1 / (dx * dx);
|
|
const d1 = lhsOutTangent * dx;
|
|
const d2 = rhsInTangent * dx;
|
|
|
|
optKey.coefficient[0] = (d1 + d2 - dy - dy) * length / dx;
|
|
optKey.coefficient[1] = (dy + dy + dy - d1 - d1 - d2) * length;
|
|
optKey.coefficient[2] = lhsOutTangent;
|
|
optKey.coefficient[3] = lhsValue;
|
|
}
|
|
|
|
/**
|
|
* @ignore
|
|
* @param optKey
|
|
* @param t
|
|
*/
|
|
private
|
|
findIndex(optKey
|
|
: OptimizedKey, t
|
|
: number) {
|
|
const {_curve : curve} = this;
|
|
const nKeyframes = curve.keyFramesCount;
|
|
const cachedIndex = optKey.index;
|
|
if (cachedIndex != = -1) {
|
|
const cachedTime = curve.getKeyframeTime(cachedIndex);
|
|
if (t > cachedTime) {
|
|
for (let i = 0; i < LOOK_FORWARD; i++) {
|
|
const currIndex = cachedIndex + i;
|
|
if (currIndex + 1 < nKeyframes && curve.getKeyframeTime(currIndex + 1) > t) {
|
|
return currIndex;
|
|
}
|
|
}
|
|
} else {
|
|
for (let i = 0; i < LOOK_FORWARD; i++) {
|
|
const currIndex = cachedIndex - i;
|
|
if (currIndex >= 0 && curve.getKeyframeTime(currIndex - 1) <= t) {
|
|
return currIndex - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let left = 0;
|
|
let right = nKeyframes;
|
|
let mid;
|
|
while (right - left > 1) {
|
|
mid = Math.floor((left + right) / 2);
|
|
if (curve.getKeyframeTime(mid) >= t) {
|
|
right = mid;
|
|
} else {
|
|
left = mid;
|
|
}
|
|
}
|
|
return left;
|
|
}
|
|
}
|
|
|
|
function
|
|
fromLegacyWrapMode(legacyWrapMode
|
|
: WrapModeMask) : ExtrapolationMode {
|
|
switch (legacyWrapMode) {
|
|
default:
|
|
case WrapModeMask.Default:
|
|
case WrapModeMask.Normal:
|
|
case WrapModeMask.Clamp: return ExtrapolationMode.CLAMP;
|
|
case WrapModeMask.PingPong: return ExtrapolationMode.PING_PONG;
|
|
case WrapModeMask.Loop: return ExtrapolationMode.LOOP;
|
|
}
|
|
}
|
|
|
|
function toLegacyWrapMode(extrapolationMode
|
|
: ExtrapolationMode) : WrapModeMask {
|
|
switch (extrapolationMode) {
|
|
default:
|
|
case ExtrapolationMode.LINEAR:
|
|
case ExtrapolationMode.CLAMP: return WrapModeMask.Clamp;
|
|
case ExtrapolationMode.PING_PONG: return WrapModeMask.PingPong;
|
|
case ExtrapolationMode.LOOP: return WrapModeMask.Loop;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Same as but more effective than `new LegacyCurve()._internalCurve`.
|
|
*/
|
|
export function constructLegacyCurveAndConvert() {
|
|
const curve = ccnew RealCurve();
|
|
curve.assignSorted([
|
|
[ 0.0, {interpolationMode : RealInterpolationMode.CUBIC, value : 1.0} ],
|
|
[ 1.0, {interpolationMode : RealInterpolationMode.CUBIC, value : 1.0} ],
|
|
]);
|
|
return curve;
|
|
}
|
|
|
|
} // namespace geometry
|
|
} // namespace cc
|