diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index eb01bc31c2..92c784a816 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -36,15 +36,22 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, const QString& id, AnimNode::Triggers& triggersOut) { + const float EPSILON = 0.0001f; float frame = currentFrame; const float clampedStartFrame = std::min(startFrame, endFrame); - if (fabsf(clampedStartFrame - endFrame) < 1.0f) { + if (fabsf(clampedStartFrame - endFrame) <= 1.0f) { + // An animation of a single frame should not send loop or done triggers. frame = endFrame; - } else if (timeScale > 0.0f) { + } else if (timeScale > EPSILON && dt > EPSILON) { // accumulate time, keeping track of loops and end of animation events. const float FRAMES_PER_SECOND = 30.0f; float framesRemaining = (dt * timeScale) * FRAMES_PER_SECOND; - while (framesRemaining > 0.0f) { + + // prevent huge dt or timeScales values from causing many trigger events. + uint32_t triggerCount = 0; + const uint32_t MAX_TRIGGER_COUNT = 3; + + while (framesRemaining > EPSILON && triggerCount < MAX_TRIGGER_COUNT) { float framesTillEnd = endFrame - frame; // when looping, add one frame between start and end. if (loopFlag) { @@ -62,6 +69,7 @@ float accumulateTime(float startFrame, float endFrame, float timeScale, float cu frame = endFrame; framesRemaining = 0.0f; } + triggerCount++; } else { frame += framesRemaining; framesRemaining = 0.0f; diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 130b692fb0..8e67bda2d3 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -257,6 +257,29 @@ void AnimTests::testAccumulateTime() { endFrame = 15.0f; timeScale = 2.0f; testAccumulateTimeWithParameters(startFrame, endFrame, timeScale); + + startFrame = 0.0f; + endFrame = 1.0f; + timeScale = 1.0f; + float dt = 1.0f; + QString id = "testNode"; + AnimNode::Triggers triggers; + float loopFlag = true; + float resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); + // a one frame looping animation should NOT trigger onLoop events + QVERIFY(triggers.empty()); + + const uint32_t MAX_TRIGGER_COUNT = 3; + + startFrame = 0.0f; + endFrame = 1.1f; + timeScale = 10.0f; + dt = 10.0f; + triggers.clear(); + loopFlag = true; + resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); + // a short animation with a large dt & a large timescale, should only create a MAXIMUM of 3 loop events. + QVERIFY(triggers.size() <= MAX_TRIGGER_COUNT); } void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFrame, float timeScale) const {