tdf#136097: make physics animation effects handle skips and rewinds

Making box2d world only stay populated when there's an animation
effect going on. This makes it possible to get rid of big chunk
of sequential update implementation.

Change-Id: Iaf50d4871b2355035495618354f0ae09abe94164
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/101304
Tested-by: Jenkins
Reviewed-by: Thorsten Behrens <Thorsten.Behrens@CIB.de>
This commit is contained in:
Sarper Akdemir
2020-08-26 14:47:52 +02:00
committed by Thorsten Behrens
parent f070bea92f
commit d29c27af4a
3 changed files with 83 additions and 124 deletions
+13 -11
View File
@@ -406,23 +406,18 @@ namespace slideshow::internal
mpShape = rShape;
mpAttrLayer = rAttrLayer;
if( !(mpBox2DWorld->isInitialized()) )
mpBox2DWorld->initiateWorld(maPageSize);
if( !(mpBox2DWorld->shapesInitialized()) )
mpBox2DWorld->initateAllShapesAsStaticBodies( mpShapeManager );
ENSURE_OR_THROW( rShape,
"PhysicsAnimation::start(): Invalid shape" );
ENSURE_OR_THROW( rAttrLayer,
"PhysicsAnimation::start(): Invalid attribute layer" );
mpBox2DBody = mpBox2DWorld->makeShapeDynamic( rShape->getXShape(), maStartVelocity, mfDensity, mfBounciness );
if( !mbAnimationStarted )
{
mbAnimationStarted = true;
mpBox2DWorld->alertPhysicsAnimationStart(maPageSize, mpShapeManager);
mpBox2DBody = mpBox2DWorld->makeShapeDynamic( mpShape->getXShape(), maStartVelocity, mfDensity, mfBounciness );
if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) )
mpShapeManager->enterAnimationMode( mpShape );
}
@@ -441,15 +436,22 @@ namespace slideshow::internal
{
mbAnimationStarted = false;
// Animation have ended for this body, make it static
box2d::utils::makeBodyStatic( mpBox2DBody );
if( !(mnFlags & AnimationFactory::FLAG_NO_SPRITE) )
mpShapeManager->leaveAnimationMode( mpShape );
if( mpShape->isContentChanged() )
mpShapeManager->notifyShapeUpdate( mpShape );
mpBox2DWorld->alertPhysicsAnimationEnd(mpShape);
// if this was the only physics animation effect going on
// all box2d bodies were destroyed on alertPhysicsAnimationEnd
// except the one owned by the animation.
// Try to destroy the remaining body - if it is unique
// (it being unique means all physics animation effects have ended
// since otherwise mpBox2DWorld would own a copy of the shared_ptr )
mpBox2DBody.reset();
}
}
// NumberAnimation interface
+64 -100
View File
@@ -11,6 +11,7 @@
#include <Box2D/Box2D.h>
#include <shapemanager.hxx>
#include <attributableshape.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygontriangulator.hxx>
@@ -208,9 +209,9 @@ box2DWorld::box2DWorld(const ::basegfx::B2DVector& rSlideSize)
, mfScaleFactor(calculateScaleFactor(rSlideSize))
, mbShapesInitialized(false)
, mbHasWorldStepper(false)
, mnPhysicsAnimationCounter(0)
, mpXShapeToBodyMap()
, maShapeParallelUpdateQueue()
, maShapeSequentialUpdate()
{
}
@@ -326,75 +327,41 @@ void box2DWorld::setShapeCollision(
void box2DWorld::processUpdateQueue(const double fPassedTime)
{
if (maShapeSequentialUpdate.empty())
while (!maShapeParallelUpdateQueue.empty())
{
while (!maShapeParallelUpdateQueue.empty())
{
Box2DDynamicUpdateInformation& aQueueElement = maShapeParallelUpdateQueue.front();
Box2DDynamicUpdateInformation& aQueueElement = maShapeParallelUpdateQueue.front();
if (aQueueElement.mnDelayForSteps > 0)
{
// it was queued as a delayed action, skip it, don't pop
aQueueElement.mnDelayForSteps--;
}
else
{
switch (aQueueElement.meUpdateType)
{
default:
case BOX2D_UPDATE_POSITION:
setShapePositionByLinearVelocity(aQueueElement.mxShape,
aQueueElement.maPosition, fPassedTime);
break;
case BOX2D_UPDATE_ANGLE:
setShapeAngleByAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngle,
fPassedTime);
break;
case BOX2D_UPDATE_SIZE:
break;
case BOX2D_UPDATE_VISIBILITY:
setShapeCollision(aQueueElement.mxShape, aQueueElement.mbVisibility);
break;
case BOX2D_UPDATE_LINEAR_VELOCITY:
setShapeLinearVelocity(aQueueElement.mxShape, aQueueElement.maVelocity);
break;
case BOX2D_UPDATE_ANGULAR_VELOCITY:
setShapeAngularVelocity(aQueueElement.mxShape,
aQueueElement.mfAngularVelocity);
}
maShapeParallelUpdateQueue.pop();
}
if (aQueueElement.mnDelayForSteps > 0)
{
// it was queued as a delayed action, skip it, don't pop
aQueueElement.mnDelayForSteps--;
}
}
else
{
// clear the Parallel Update Queue since the updates in it
// are not relevant now - if there's any
maShapeParallelUpdateQueue = {};
for (auto& aIt : maShapeSequentialUpdate)
else
{
const css::uno::Reference<css::drawing::XShape>& xShape = aIt.first.first;
const box2DNonsimulatedShapeUpdateType eUpdateType = aIt.first.second;
const Box2DStaticUpdateInformation& rUpdateInformation = aIt.second;
switch (eUpdateType)
switch (aQueueElement.meUpdateType)
{
default:
case BOX2D_UPDATE_POSITION:
setShapePosition(xShape, rUpdateInformation.maPosition);
setShapePositionByLinearVelocity(aQueueElement.mxShape,
aQueueElement.maPosition, fPassedTime);
break;
case BOX2D_UPDATE_ANGLE:
setShapeAngle(xShape, rUpdateInformation.mfAngle);
setShapeAngleByAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngle,
fPassedTime);
break;
case BOX2D_UPDATE_SIZE:
break;
case BOX2D_UPDATE_VISIBILITY:
setShapeCollision(xShape, rUpdateInformation.mbVisibility);
setShapeCollision(aQueueElement.mxShape, aQueueElement.mbVisibility);
break;
case BOX2D_UPDATE_LINEAR_VELOCITY:
setShapeLinearVelocity(aQueueElement.mxShape, aQueueElement.maVelocity);
break;
case BOX2D_UPDATE_ANGULAR_VELOCITY:
setShapeAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngularVelocity);
}
maShapeParallelUpdateQueue.pop();
}
// After applying all required updates empty map
maShapeSequentialUpdate.clear();
}
}
@@ -514,50 +481,20 @@ void box2DWorld::queueShapeAnimationUpdate(
const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer,
const slideshow::internal::AttributeType eAttrType)
{
if (mbHasWorldStepper) // if there's a physics animation going on
switch (eAttrType)
{
switch (eAttrType)
{
case slideshow::internal::AttributeType::Visibility:
queueShapeVisibilityUpdate(xShape, pAttrLayer->getVisibility());
return;
case slideshow::internal::AttributeType::Rotate:
queueDynamicRotationUpdate(xShape, pAttrLayer->getRotationAngle());
return;
case slideshow::internal::AttributeType::PosX:
case slideshow::internal::AttributeType::PosY:
queueDynamicPositionUpdate(xShape,
{ pAttrLayer->getPosX(), pAttrLayer->getPosY() });
return;
default:
return;
}
}
else
{
Box2DStaticUpdateInformation aStaticUpdateInformation;
switch (eAttrType)
{
case slideshow::internal::AttributeType::Visibility:
aStaticUpdateInformation.mbVisibility = pAttrLayer->getVisibility();
maShapeSequentialUpdate[std::make_pair(xShape, BOX2D_UPDATE_VISIBILITY)]
= aStaticUpdateInformation;
return;
case slideshow::internal::AttributeType::Rotate:
aStaticUpdateInformation.mfAngle = pAttrLayer->getRotationAngle();
maShapeSequentialUpdate[std::make_pair(xShape, BOX2D_UPDATE_ANGLE)]
= aStaticUpdateInformation;
return;
case slideshow::internal::AttributeType::PosX:
case slideshow::internal::AttributeType::PosY:
aStaticUpdateInformation.maPosition
= basegfx::B2DPoint(pAttrLayer->getPosX(), pAttrLayer->getPosY());
maShapeSequentialUpdate[std::make_pair(xShape, BOX2D_UPDATE_POSITION)]
= aStaticUpdateInformation;
return;
default:
return;
}
case slideshow::internal::AttributeType::Visibility:
queueShapeVisibilityUpdate(xShape, pAttrLayer->getVisibility());
return;
case slideshow::internal::AttributeType::Rotate:
queueDynamicRotationUpdate(xShape, pAttrLayer->getRotationAngle());
return;
case slideshow::internal::AttributeType::PosX:
case slideshow::internal::AttributeType::PosY:
queueDynamicPositionUpdate(xShape, { pAttrLayer->getPosX(), pAttrLayer->getPosY() });
return;
default:
return;
}
}
@@ -579,11 +516,30 @@ void box2DWorld::queueShapeAnimationEndUpdate(
}
}
void box2DWorld::alertAnimationEndForShape(const slideshow::internal::ShapeSharedPtr& pShape)
void box2DWorld::alertPhysicsAnimationEnd(const slideshow::internal::ShapeSharedPtr& pShape)
{
Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(pShape->getXShape())->second;
makeBodyStatic(pBox2DBody);
pBox2DBody->setRestitution(fDefaultStaticBodyBounciness);
if (--mnPhysicsAnimationCounter == 0)
{
maShapeParallelUpdateQueue = {};
mbShapesInitialized = false;
mpXShapeToBodyMap.clear();
}
}
void box2DWorld::alertPhysicsAnimationStart(
const ::basegfx::B2DVector& rSlideSize,
const slideshow::internal::ShapeManagerSharedPtr& pShapeManager)
{
if (!mpBox2DWorld)
initiateWorld(rSlideSize);
if (!mbShapesInitialized)
initateAllShapesAsStaticBodies(pShapeManager);
mnPhysicsAnimationCounter++;
}
void box2DWorld::step(const float fTimeStep, const int nVelocityIterations,
@@ -669,6 +625,14 @@ Box2DBodySharedPtr box2DWorld::createStaticBody(const slideshow::internal::Shape
aBodyDef.type = b2_staticBody;
aBodyDef.position = convertB2DPointToBox2DVec2(aShapeBounds.getCenter(), mfScaleFactor);
slideshow::internal::ShapeAttributeLayerSharedPtr pShapeAttributeLayer
= static_cast<slideshow::internal::AttributableShape*>(rShape.get())
->getTopmostAttributeLayer();
if (pShapeAttributeLayer && pShapeAttributeLayer->isRotationAngleValid())
{
aBodyDef.angle = ::basegfx::deg2rad(-pShapeAttributeLayer->getRotationAngle());
}
std::shared_ptr<b2Body> pBody(mpBox2DWorld->CreateBody(&aBodyDef), [](b2Body* pB2Body) {
pB2Body->GetWorld()->DestroyBody(pB2Body);
});
+6 -13
View File
@@ -65,13 +65,6 @@ struct Box2DDynamicUpdateInformation
int mnDelayForSteps = 0;
};
union Box2DStaticUpdateInformation {
::basegfx::B2DPoint maPosition;
bool mbVisibility;
double mfAngle;
Box2DStaticUpdateInformation() {}
};
/** Class that manages the Box2D World
This class is used when there's a simulated animation going on,
@@ -89,16 +82,12 @@ private:
/// Holds whether or not there is a Physics Animation node that
/// is stepping the Box2D World
bool mbHasWorldStepper;
int mnPhysicsAnimationCounter;
std::unordered_map<css::uno::Reference<css::drawing::XShape>, Box2DBodySharedPtr>
mpXShapeToBodyMap;
/// Holds any information needed to keep LO animations and Box2D world in sync
/// if they are going in parallel
std::queue<Box2DDynamicUpdateInformation> maShapeParallelUpdateQueue;
/// Holds necessary information to update a shape's body that was altered by an
/// animation effect while there was no Physics Animation going in parallel
std::map<std::pair<css::uno::Reference<css::drawing::XShape>, box2DNonsimulatedShapeUpdateType>,
Box2DStaticUpdateInformation>
maShapeSequentialUpdate;
/// Creates a static frame in Box2D world that corresponds to the slide borders
void createStaticFrameAroundSlide(const ::basegfx::B2DVector& rSlideSize);
@@ -270,7 +259,11 @@ public:
void queueShapeAnimationEndUpdate(const css::uno::Reference<css::drawing::XShape>& xShape,
const slideshow::internal::AttributeType eAttrType);
void alertAnimationEndForShape(const slideshow::internal::ShapeSharedPtr& pShape);
void alertPhysicsAnimationEnd(const slideshow::internal::ShapeSharedPtr& pShape);
void
alertPhysicsAnimationStart(const ::basegfx::B2DVector& rSlideSize,
const slideshow::internal::ShapeManagerSharedPtr& pShapeManager);
};
/// Class that manages a single box2D Body