|
|
@@ -7,13 +7,26 @@
|
|
|
namespace BansheeEngine
|
|
|
{
|
|
|
AnimationClipInfo::AnimationClipInfo()
|
|
|
- :layerIdx(0), curveVersion(0), stateIdx(0)
|
|
|
+ : fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), layerIdx(0), curveVersion(0), stateIdx(0)
|
|
|
{ }
|
|
|
|
|
|
AnimationClipInfo::AnimationClipInfo(const HAnimationClip& clip)
|
|
|
- :clip(clip), curveVersion(0), layerIdx(0), stateIdx(0)
|
|
|
+ : fadeDirection(0.0f), fadeTime(0.0f), fadeLength(0.0f), clip(clip), curveVersion(0), layerIdx(0), stateIdx(0)
|
|
|
{ }
|
|
|
|
|
|
+ BlendSequentialInfo::BlendSequentialInfo(UINT32 numClips)
|
|
|
+ : clips(nullptr), numClips(numClips)
|
|
|
+ {
|
|
|
+ if (numClips > 0)
|
|
|
+ bs_newN<BlendSequentialClipInfo>(numClips);
|
|
|
+ }
|
|
|
+
|
|
|
+ BlendSequentialInfo::~BlendSequentialInfo()
|
|
|
+ {
|
|
|
+ if(clips != nullptr)
|
|
|
+ bs_deleteN(clips, numClips);
|
|
|
+ }
|
|
|
+
|
|
|
AnimationProxy::AnimationProxy(UINT64 id)
|
|
|
:id(id), layers(nullptr), numLayers(0), genericCurveOutputs(nullptr)
|
|
|
{ }
|
|
|
@@ -67,10 +80,16 @@ namespace BansheeEngine
|
|
|
FrameVector<AnimationStateLayer> tempLayers;
|
|
|
for (auto& clipInfo : clipInfos)
|
|
|
{
|
|
|
+ UINT32 layer = clipInfo.state.layer;
|
|
|
+ if (layer == (UINT32)-1)
|
|
|
+ layer = 0;
|
|
|
+ else
|
|
|
+ layer += 1;
|
|
|
+
|
|
|
auto iterFind = std::find_if(tempLayers.begin(), tempLayers.end(),
|
|
|
[&](auto& x)
|
|
|
{
|
|
|
- return x.index == clipInfo.state.layer;
|
|
|
+ return x.index == layer;
|
|
|
});
|
|
|
|
|
|
if (iterFind == tempLayers.end())
|
|
|
@@ -78,7 +97,7 @@ namespace BansheeEngine
|
|
|
tempLayers.push_back(AnimationStateLayer());
|
|
|
AnimationStateLayer& newLayer = tempLayers.back();
|
|
|
|
|
|
- newLayer.index = clipInfo.state.layer;
|
|
|
+ newLayer.index = layer;
|
|
|
newLayer.additive = clipInfo.clip.isLoaded() && clipInfo.clip->isAdditive();
|
|
|
}
|
|
|
}
|
|
|
@@ -167,10 +186,27 @@ namespace BansheeEngine
|
|
|
|
|
|
new (&states[curStateIdx]) AnimationState();
|
|
|
AnimationState& state = states[curStateIdx];
|
|
|
- state.weight = clipInfo.state.weight;
|
|
|
state.loop = clipInfo.state.wrapMode == AnimWrapMode::Loop;
|
|
|
state.time = clipInfo.state.time;
|
|
|
|
|
|
+ // Calculate weight if fading is active
|
|
|
+ float weight = clipInfo.state.weight;
|
|
|
+
|
|
|
+ //// Assumes time is clamped to [0, fadeLength] and fadeLength != 0
|
|
|
+ if(clipInfo.fadeDirection < 0.0f)
|
|
|
+ {
|
|
|
+ float t = clipInfo.fadeTime / clipInfo.fadeLength;
|
|
|
+ weight *= (1.0f - t);
|
|
|
+ }
|
|
|
+ else if(clipInfo.fadeDirection > 0.0f)
|
|
|
+ {
|
|
|
+ float t = clipInfo.fadeTime / clipInfo.fadeLength;
|
|
|
+ weight *= t;
|
|
|
+ }
|
|
|
+
|
|
|
+ state.weight = weight;
|
|
|
+
|
|
|
+ // Set up individual curves and their caches
|
|
|
bool isClipValid = clipInfo.clip.isLoaded();
|
|
|
if(isClipValid)
|
|
|
state.curves = clipInfo.clip->getCurves();
|
|
|
@@ -198,6 +234,7 @@ namespace BansheeEngine
|
|
|
if(isClipValid)
|
|
|
clipInfo.curveVersion = clipInfo.clip->getVersion();
|
|
|
|
|
|
+ // Set up bone mapping
|
|
|
if (skeleton != nullptr)
|
|
|
{
|
|
|
state.boneToCurveMapping = &boneMappings[curStateIdx * numBones];
|
|
|
@@ -285,77 +322,175 @@ namespace BansheeEngine
|
|
|
mDefaultSpeed = speed;
|
|
|
|
|
|
for (auto& clipInfo : mClipInfos)
|
|
|
- clipInfo.state.speed = speed;
|
|
|
+ {
|
|
|
+ // Special case: Ignore non-moving ones
|
|
|
+ if(clipInfo.state.speed != 0.0f)
|
|
|
+ clipInfo.state.speed = speed;
|
|
|
+ }
|
|
|
|
|
|
mDirty |= AnimDirtyStateFlag::Value;
|
|
|
}
|
|
|
|
|
|
- void Animation::play(const HAnimationClip& clip, UINT32 layer, AnimPlayMode playMode)
|
|
|
+ void Animation::play(const HAnimationClip& clip)
|
|
|
{
|
|
|
- if (playMode == AnimPlayMode::StopAll)
|
|
|
- mClipInfos.clear();
|
|
|
- else
|
|
|
+ AnimationClipInfo* clipInfo = addClip(clip, (UINT32)-1);
|
|
|
+ if(clipInfo != nullptr)
|
|
|
{
|
|
|
- bs_frame_mark();
|
|
|
- {
|
|
|
- FrameVector<AnimationClipInfo> newClips;
|
|
|
- for (auto& clipInfo : mClipInfos)
|
|
|
- {
|
|
|
- if (clipInfo.state.layer != layer && clipInfo.clip != clip)
|
|
|
- newClips.push_back(clipInfo);
|
|
|
- }
|
|
|
+ clipInfo->state.time = 0.0f;
|
|
|
+ clipInfo->state.speed = mDefaultSpeed;
|
|
|
+ clipInfo->state.weight = 1.0f;
|
|
|
+ clipInfo->state.wrapMode = mDefaultWrapMode;
|
|
|
|
|
|
- mClipInfos.resize(newClips.size());
|
|
|
- memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
|
|
|
- }
|
|
|
- bs_frame_clear();
|
|
|
+ mDirty |= AnimDirtyStateFlag::Value;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (clip != nullptr)
|
|
|
+ void Animation::blendAdditive(const HAnimationClip& clip, float weight, float fadeLength, UINT32 layer)
|
|
|
+ {
|
|
|
+ if(clip != nullptr && !clip->isAdditive())
|
|
|
{
|
|
|
- checkAdditiveLayer(layer);
|
|
|
+ LOGWRN("blendAdditive() called with a clip that doesn't contain additive animation. Ignoring.");
|
|
|
|
|
|
- mClipInfos.push_back(AnimationClipInfo(clip));
|
|
|
- AnimationClipInfo& newClipInfo = mClipInfos.back();
|
|
|
+ // Stop any clips on this layer, even if invalid
|
|
|
+ HAnimationClip nullClip;
|
|
|
+ addClip(nullClip, layer);
|
|
|
|
|
|
- newClipInfo.state.layer = layer;
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
+ AnimationClipInfo* clipInfo = addClip(clip, layer);
|
|
|
+ if (clipInfo != nullptr)
|
|
|
+ {
|
|
|
+ clipInfo->state.time = 0.0f;
|
|
|
+ clipInfo->state.speed = mDefaultSpeed;
|
|
|
+ clipInfo->state.weight = weight;
|
|
|
+ clipInfo->state.wrapMode = mDefaultWrapMode;
|
|
|
+
|
|
|
+ if(fadeLength > 0.0f)
|
|
|
+ {
|
|
|
+ clipInfo->fadeDirection = 1.0f;
|
|
|
+ clipInfo->fadeTime = 0.0f;
|
|
|
+ clipInfo->fadeLength = fadeLength;
|
|
|
+ }
|
|
|
+
|
|
|
+ mDirty |= AnimDirtyStateFlag::Value;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- void Animation::blend(const HAnimationClip& clip, float weight, float fadeLength, UINT32 layer)
|
|
|
+ void Animation::blend1D(const Blend1DInfo& info, float t)
|
|
|
{
|
|
|
- if (clip != nullptr)
|
|
|
+ AnimationClipInfo* leftClipInfo = addClip(info.leftClip, (UINT32)-1, true);
|
|
|
+ if (leftClipInfo != nullptr)
|
|
|
{
|
|
|
- checkAdditiveLayer(layer);
|
|
|
+ leftClipInfo->state.time = 0.0f;
|
|
|
+ leftClipInfo->state.speed = 0.0f;
|
|
|
+ leftClipInfo->state.weight = 1.0f - t;
|
|
|
+ leftClipInfo->state.wrapMode = AnimWrapMode::Clamp;
|
|
|
+ }
|
|
|
|
|
|
- mClipInfos.push_back(AnimationClipInfo(clip));
|
|
|
- AnimationClipInfo& newClipInfo = mClipInfos.back();
|
|
|
+ AnimationClipInfo* rightClipInfo = addClip(info.rightClip, (UINT32)-1, false);
|
|
|
+ if(rightClipInfo != nullptr)
|
|
|
+ {
|
|
|
+ rightClipInfo->state.time = 0.0f;
|
|
|
+ rightClipInfo->state.speed = 0.0f;
|
|
|
+ rightClipInfo->state.weight = t;
|
|
|
+ rightClipInfo->state.wrapMode = AnimWrapMode::Clamp;
|
|
|
+ }
|
|
|
+
|
|
|
+ mDirty |= AnimDirtyStateFlag::Value;
|
|
|
+ }
|
|
|
|
|
|
- newClipInfo.state.layer = layer;
|
|
|
+ void Animation::blend2D(const Blend2DInfo& info, const Vector2& t)
|
|
|
+ {
|
|
|
+ AnimationClipInfo* topLeftClipInfo = addClip(info.topLeftClip, (UINT32)-1, true);
|
|
|
+ if (topLeftClipInfo != nullptr)
|
|
|
+ {
|
|
|
+ topLeftClipInfo->state.time = 0.0f;
|
|
|
+ topLeftClipInfo->state.speed = 0.0f;
|
|
|
+ topLeftClipInfo->state.weight = (1.0f - t.x) * (1.0f - t.y);
|
|
|
+ topLeftClipInfo->state.wrapMode = AnimWrapMode::Clamp;
|
|
|
}
|
|
|
|
|
|
- // TODO
|
|
|
+ AnimationClipInfo* topRightClipInfo = addClip(info.topRightClip, (UINT32)-1, false);
|
|
|
+ if (topRightClipInfo != nullptr)
|
|
|
+ {
|
|
|
+ topRightClipInfo->state.time = 0.0f;
|
|
|
+ topRightClipInfo->state.speed = 0.0f;
|
|
|
+ topRightClipInfo->state.weight = t.x * (1.0f - t.y);
|
|
|
+ topRightClipInfo->state.wrapMode = AnimWrapMode::Clamp;
|
|
|
+ }
|
|
|
|
|
|
- mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
+ AnimationClipInfo* botLeftClipInfo = addClip(info.botLeftClip, (UINT32)-1, false);
|
|
|
+ if (botLeftClipInfo != nullptr)
|
|
|
+ {
|
|
|
+ botLeftClipInfo->state.time = 0.0f;
|
|
|
+ botLeftClipInfo->state.speed = 0.0f;
|
|
|
+ botLeftClipInfo->state.weight = (1.0f - t.x) * t.y;
|
|
|
+ botLeftClipInfo->state.wrapMode = AnimWrapMode::Clamp;
|
|
|
+ }
|
|
|
+
|
|
|
+ AnimationClipInfo* botRightClipInfo = addClip(info.botRightClip, (UINT32)-1, false);
|
|
|
+ if (botRightClipInfo != nullptr)
|
|
|
+ {
|
|
|
+ botRightClipInfo->state.time = 0.0f;
|
|
|
+ botRightClipInfo->state.speed = 0.0f;
|
|
|
+ botRightClipInfo->state.weight = t.x * t.y;
|
|
|
+ botRightClipInfo->state.wrapMode = AnimWrapMode::Clamp;
|
|
|
+ }
|
|
|
+
|
|
|
+ mDirty |= AnimDirtyStateFlag::Value;
|
|
|
}
|
|
|
|
|
|
- void Animation::crossFade(const HAnimationClip& clip, float fadeLength, UINT32 layer, AnimPlayMode playMode)
|
|
|
+ void Animation::crossFade(const HAnimationClip& clip, float fadeLength)
|
|
|
{
|
|
|
- if (clip != nullptr)
|
|
|
+ bool isFading = fadeLength > 0.0f;
|
|
|
+ if(!isFading)
|
|
|
+ {
|
|
|
+ play(clip);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ AnimationClipInfo* clipInfo = addClip(clip, (UINT32)-1, false);
|
|
|
+ if (clipInfo != nullptr)
|
|
|
{
|
|
|
- checkAdditiveLayer(layer);
|
|
|
+ clipInfo->state.time = 0.0f;
|
|
|
+ clipInfo->state.speed = mDefaultSpeed;
|
|
|
+ clipInfo->state.weight = 1.0f;
|
|
|
+ clipInfo->state.wrapMode = mDefaultWrapMode;
|
|
|
|
|
|
- mClipInfos.push_back(AnimationClipInfo(clip));
|
|
|
- AnimationClipInfo& newClipInfo = mClipInfos.back();
|
|
|
+ // Set up fade lengths
|
|
|
+ clipInfo->fadeDirection = 1.0f;
|
|
|
+ clipInfo->fadeTime = 0.0f;
|
|
|
+ clipInfo->fadeLength = fadeLength;
|
|
|
|
|
|
- newClipInfo.state.layer = layer;
|
|
|
+ for (auto& entry : mClipInfos)
|
|
|
+ {
|
|
|
+ if (entry.state.layer == (UINT32)-1 && entry.clip != clip)
|
|
|
+ {
|
|
|
+ // If other clips are already cross-fading, we need to persist their current weight before starting
|
|
|
+ // a new crossfade. We do that by adjusting the fade times.
|
|
|
+ if(clipInfo->fadeDirection != 0 && clipInfo->fadeTime < clipInfo->fadeLength)
|
|
|
+ {
|
|
|
+ float t = clipInfo->fadeTime / clipInfo->fadeLength;
|
|
|
+ if (clipInfo->fadeDirection < 0.0f)
|
|
|
+ t = (1.0f - t);
|
|
|
+
|
|
|
+ clipInfo->state.weight *= t;
|
|
|
+ }
|
|
|
+
|
|
|
+ clipInfo->fadeDirection = -1.0f;
|
|
|
+ clipInfo->fadeTime = 0.0f;
|
|
|
+ clipInfo->fadeLength = fadeLength;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ mDirty |= AnimDirtyStateFlag::Value;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
+ void Animation::blendSequential(const BlendSequentialInfo& info)
|
|
|
+ {
|
|
|
// TODO
|
|
|
-
|
|
|
- mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
}
|
|
|
|
|
|
void Animation::stop(UINT32 layer)
|
|
|
@@ -367,14 +502,14 @@ namespace BansheeEngine
|
|
|
{
|
|
|
if (clipInfo.state.layer != layer)
|
|
|
newClips.push_back(clipInfo);
|
|
|
+ else
|
|
|
+ mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
}
|
|
|
|
|
|
mClipInfos.resize(newClips.size());
|
|
|
memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
|
|
|
}
|
|
|
bs_frame_clear();
|
|
|
-
|
|
|
- mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
}
|
|
|
|
|
|
void Animation::stopAll()
|
|
|
@@ -383,6 +518,59 @@ namespace BansheeEngine
|
|
|
mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
}
|
|
|
|
|
|
+ AnimationClipInfo* Animation::addClip(const HAnimationClip& clip, UINT32 layer, bool stopExisting)
|
|
|
+ {
|
|
|
+ AnimationClipInfo* output = nullptr;
|
|
|
+ bool hasExisting = false;
|
|
|
+
|
|
|
+ // Search for existing
|
|
|
+ for (auto& clipInfo : mClipInfos)
|
|
|
+ {
|
|
|
+ if (clipInfo.state.layer == layer)
|
|
|
+ {
|
|
|
+ if (clipInfo.clip == clip)
|
|
|
+ output = &clipInfo;
|
|
|
+ else if (stopExisting)
|
|
|
+ hasExisting = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Doesn't exist or found extra animations, rebuild
|
|
|
+ if (output == nullptr || hasExisting)
|
|
|
+ {
|
|
|
+ bs_frame_mark();
|
|
|
+ {
|
|
|
+ FrameVector<AnimationClipInfo> newClips;
|
|
|
+ for (auto& clipInfo : mClipInfos)
|
|
|
+ {
|
|
|
+ if (!stopExisting || clipInfo.state.layer != layer || clipInfo.clip == clip)
|
|
|
+ newClips.push_back(clipInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (output == nullptr && clip != nullptr)
|
|
|
+ newClips.push_back(AnimationClipInfo());
|
|
|
+
|
|
|
+ mClipInfos.resize(newClips.size());
|
|
|
+ memcpy(mClipInfos.data(), newClips.data(), sizeof(AnimationClipInfo) * newClips.size());
|
|
|
+
|
|
|
+ mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
+ }
|
|
|
+ bs_frame_clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ // If new clip was added, get its address
|
|
|
+ if (output == nullptr && clip != nullptr)
|
|
|
+ {
|
|
|
+ AnimationClipInfo& newInfo = mClipInfos.back();
|
|
|
+ newInfo.clip = clip;
|
|
|
+ newInfo.layerIdx = layer;
|
|
|
+
|
|
|
+ output = &newInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ return output;
|
|
|
+ }
|
|
|
+
|
|
|
bool Animation::isPlaying() const
|
|
|
{
|
|
|
for(auto& clipInfo : mClipInfos)
|
|
|
@@ -413,50 +601,13 @@ namespace BansheeEngine
|
|
|
|
|
|
void Animation::setState(const HAnimationClip& clip, AnimationClipState state)
|
|
|
{
|
|
|
- if (clip == nullptr)
|
|
|
- return;
|
|
|
-
|
|
|
- checkAdditiveLayer(state.layer);
|
|
|
-
|
|
|
- for (auto& clipInfo : mClipInfos)
|
|
|
- {
|
|
|
- if (clipInfo.clip == clip)
|
|
|
- {
|
|
|
- if (clipInfo.state.layer != state.layer)
|
|
|
- mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
-
|
|
|
- clipInfo.state = state;
|
|
|
- mDirty |= AnimDirtyStateFlag::Value;
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // No existing clip found, add new one
|
|
|
- mClipInfos.push_back(AnimationClipInfo(clip));
|
|
|
-
|
|
|
- AnimationClipInfo& newClipInfo = mClipInfos.back();
|
|
|
- newClipInfo.state = state;
|
|
|
+ AnimationClipInfo* clipInfo = addClip(clip, state.layer, false);
|
|
|
|
|
|
- mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
- }
|
|
|
-
|
|
|
- void Animation::checkAdditiveLayer(UINT32 layerIdx)
|
|
|
- {
|
|
|
- for (auto& clipInfo : mClipInfos)
|
|
|
- {
|
|
|
- if (!clipInfo.clip.isLoaded())
|
|
|
- continue;
|
|
|
+ if (clipInfo == nullptr)
|
|
|
+ return;
|
|
|
|
|
|
- if (clipInfo.state.layer == layerIdx)
|
|
|
- {
|
|
|
- if(!clipInfo.clip->isAdditive())
|
|
|
- {
|
|
|
- LOGWRN("Adding an additive clip to a layer, but other clips in the same layer are not additive. " \
|
|
|
- "This will most likely result in an incorrect animation.");
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ clipInfo->state = state;
|
|
|
+ mDirty |= AnimDirtyStateFlag::Value;
|
|
|
}
|
|
|
|
|
|
SPtr<Animation> Animation::create()
|
|
|
@@ -472,13 +623,17 @@ namespace BansheeEngine
|
|
|
|
|
|
void Animation::updateAnimProxy(float timeDelta)
|
|
|
{
|
|
|
- // Check if any of the clip curves are dirty and advance time
|
|
|
+ // Check if any of the clip curves are dirty and advance time, perform fading
|
|
|
for (auto& clipInfo : mClipInfos)
|
|
|
{
|
|
|
- clipInfo.state.time += timeDelta * clipInfo.state.speed;
|
|
|
+ float scaledTimeDelta = timeDelta * clipInfo.state.speed;
|
|
|
+ clipInfo.state.time += scaledTimeDelta;
|
|
|
|
|
|
if (clipInfo.clip.isLoaded() && clipInfo.curveVersion != clipInfo.clip->getVersion())
|
|
|
mDirty |= AnimDirtyStateFlag::Layout;
|
|
|
+
|
|
|
+ float fadeTime = clipInfo.fadeTime + scaledTimeDelta;
|
|
|
+ clipInfo.fadeTime = Math::clamp(fadeTime, 0.0f, clipInfo.fadeLength);
|
|
|
}
|
|
|
|
|
|
if((UINT32)mDirty == 0) // Clean
|