/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Scale factor is based on whatever is the larger // value between slide width and height if (fWidth > fHeight) return BOX2D_SLIDE_SIZE_IN_METERS / fWidth; else return BOX2D_SLIDE_SIZE_IN_METERS / fHeight;
}
b2BodyType getBox2DInternalBodyType(const box2DBodyType eType)
{ switch (eType)
{ default: case BOX2D_STATIC_BODY: return b2_staticBody; case BOX2D_KINEMATIC_BODY: return b2_kinematicBody; case BOX2D_DYNAMIC_BODY: return b2_dynamicBody;
}
}
box2DBodyType getBox2DLOBodyType(const b2BodyType eType)
{ switch (eType)
{ default: case b2_staticBody: return BOX2D_STATIC_BODY; case b2_kinematicBody: return BOX2D_KINEMATIC_BODY; case b2_dynamicBody: return BOX2D_DYNAMIC_BODY;
}
}
if (bValidPointDistance)
{ // create a fixture that represents the triangle
aPolygonShape.Set(aTriangleVertices, 3);
aFixture.shape = &aPolygonShape;
aFixture.density = fDefaultStaticDensity;
aFixture.friction = fDefaultStaticFriction;
aFixture.restitution = fDefaultStaticBodyBounciness;
aBody->CreateFixture(&aFixture);
}
}
}
// expects rPolygon to have coordinates relative to it's center void addEdgeShapeToBody(const basegfx::B2DPolygon& rPolygon, b2Body* aBody, constdouble fScaleFactor)
{ // make sure there's no bezier curves on the polygon
assert(!rPolygon.areControlPointsUsed());
basegfx::B2DPolygon aPolygon = basegfx::utils::removeNeutralPoints(rPolygon);
// value that somewhat defines half width of the quadrilateral // that will be representing edge segment in the box2d world constfloat fHalfWidth = 0.1f; bool bHasPreviousQuadrilateralEdge = false;
b2Vec2 aQuadrilateralVertices[4];
basegfx::B2DPoint aPointA;
basegfx::B2DPoint aPointB; if (nIndex != 0)
{ // get two adjacent points to create an edge out of
aPointA = aPolygon.getB2DPoint(nIndex - 1);
aPointB = aPolygon.getB2DPoint(nIndex);
} elseif (aPolygon.isClosed())
{ // start by connecting the last point to the first one
aPointA = aPolygon.getB2DPoint(aPolygon.count() - 1);
aPointB = aPolygon.getB2DPoint(nIndex);
} else// the polygon isn't closed, won't connect last and first points
{ continue;
}
// create a vector that represents the direction of the edge // and make it a unit vector
b2Vec2 aEdgeUnitVec(convertB2DPointToBox2DVec2(aPointB, fScaleFactor)
- convertB2DPointToBox2DVec2(aPointA, fScaleFactor));
aEdgeUnitVec.Normalize();
// create a unit vector that represents Normal of the edge
b2Vec2 aEdgeNormal(-aEdgeUnitVec.y, aEdgeUnitVec.x);
// if there was an edge previously created it should just connect // using it's ending points so that there are no empty spots // between edge segments, if not use wherever aPointA is at if (!bHasPreviousQuadrilateralEdge)
{ // the point is translated along the edge normal both directions by // fHalfWidth to create a quadrilateral edge
aQuadrilateralVertices[0]
= convertB2DPointToBox2DVec2(aPointA, fScaleFactor) + fHalfWidth * aEdgeNormal;
aQuadrilateralVertices[1]
= convertB2DPointToBox2DVec2(aPointA, fScaleFactor) + -fHalfWidth * aEdgeNormal;
bHasPreviousQuadrilateralEdge = true;
}
aQuadrilateralVertices[2]
= convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + fHalfWidth * aEdgeNormal;
aQuadrilateralVertices[3]
= convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + -fHalfWidth * aEdgeNormal;
// check whether the edge would have degenerately close points bool bValidPointDistance
= b2DistanceSquared(aQuadrilateralVertices[0], aQuadrilateralVertices[2]) > 0.003f;
if (bValidPointDistance)
{ // create a quadrilateral shaped fixture to represent the edge
aPolygonShape.Set(aQuadrilateralVertices, 4);
aFixture.shape = &aPolygonShape;
aFixture.density = fDefaultStaticDensity;
aFixture.friction = fDefaultStaticFriction;
aFixture.restitution = fDefaultStaticBodyBounciness;
aBody->CreateFixture(&aFixture);
// prepare the quadrilateral edge for next connection
aQuadrilateralVertices[0] = aQuadrilateralVertices[2];
aQuadrilateralVertices[1] = aQuadrilateralVertices[3];
}
}
}
void box2DWorld::setShapePositionByLinearVelocity( const css::uno::Reference<css::drawing::XShape>& xShape, const basegfx::B2DPoint& rOutPos, constdouble fPassedTime)
{ if (fPassedTime > 0) // this only makes sense if there was an advance in time
{ constauto iter = mpXShapeToBodyMap.find(xShape);
assert(iter != mpXShapeToBodyMap.end());
Box2DBodySharedPtr pBox2DBody = iter->second;
pBox2DBody->setPositionByLinearVelocity(rOutPos, fPassedTime);
}
}
void box2DWorld::setShapeAngleByAngularVelocity( const css::uno::Reference<css::drawing::XShape>& xShape, constdouble fAngle, constdouble fPassedTime)
{ if (fPassedTime > 0) // this only makes sense if there was an advance in time
{ constauto iter = mpXShapeToBodyMap.find(xShape);
assert(iter != mpXShapeToBodyMap.end());
Box2DBodySharedPtr pBox2DBody = iter->second;
pBox2DBody->setAngleByAngularVelocity(fAngle, fPassedTime);
}
}
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_CHANGE:
setShapePositionByLinearVelocity(aQueueElement.mxShape,
aQueueElement.maPosition, fPassedTime); break; case BOX2D_UPDATE_POSITION:
setShapePosition(aQueueElement.mxShape, aQueueElement.maPosition); 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();
}
}
}
// iterate over the shapes in the current slide and flag them if they belong to a group // will flag the only ones that are belong to a group since std::unordered_map operator[] // defaults the value to false if the key doesn't have a corresponding value for (auto aIt = aXShapeToShapeMap.begin(); aIt != aXShapeToShapeMap.end(); aIt++)
{
slideshow::internal::ShapeSharedPtr pShape = aIt->second; if (pShape->isForeground())
{
SdrObject* pTemp = SdrObject::getSdrObjectFromXShape(pShape->getXShape()); if (pTemp && pTemp->IsGroupObject())
{ // if it is a group object iterate over its children and flag them
SdrObjList* aObjList = pTemp->GetSubList();
// iterate over shapes in the current slide for (auto aIt = aXShapeToShapeMap.begin(); aIt != aXShapeToShapeMap.end(); aIt++)
{
slideshow::internal::ShapeSharedPtr pShape = aIt->second; // only create static bodies for the shapes that do not belong to a group // groups themselves will have one body that represents the whole shape // collection if (pShape->isForeground() && !aXShapeBelongsToAGroup[pShape->getXShape()])
{
Box2DBodySharedPtr pBox2DBody = createStaticBody(pShape);
mpXShapeToBodyMap.insert(std::make_pair(pShape->getXShape(), pBox2DBody)); if (!pShape->isVisible())
{ // if the shape isn't visible, queue an update for it
queueShapeVisibilityUpdate(pShape->getXShape(), false);
}
}
}
}
void box2DWorld::queueShapePathAnimationUpdate( const css::uno::Reference<css::drawing::XShape>& xShape, const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer, constbool bIsFirstUpdate)
{ // Workaround for PathAnimations since they do not have their own AttributeType // - using PosX makes it register a DynamicPositionUpdate -
queueShapeAnimationUpdate(xShape, pAttrLayer, slideshow::internal::AttributeType::PosX,
bIsFirstUpdate);
}
void box2DWorld::queueShapeAnimationUpdate( const css::uno::Reference<css::drawing::XShape>& xShape, const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer, const slideshow::internal::AttributeType eAttrType, constbool bIsFirstUpdate)
{ 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: if (bIsFirstUpdate) // if it is the first update shape should _teleport_ to the position
queueShapePositionUpdate(xShape, { pAttrLayer->getPosX(), pAttrLayer->getPosY() }); else
queueDynamicPositionUpdate(xShape,
{ pAttrLayer->getPosX(), pAttrLayer->getPosY() }); return; default: return;
}
}
void box2DWorld::queueShapeAnimationEndUpdate( const css::uno::Reference<css::drawing::XShape>& xShape, const slideshow::internal::AttributeType eAttrType)
{ switch (eAttrType)
{ // end updates that change the velocity are delayed for a step // since we do not want them to override the last position/angle case slideshow::internal::AttributeType::Rotate:
queueAngularVelocityUpdate(xShape, 0.0, 1); return; case slideshow::internal::AttributeType::PosX: case slideshow::internal::AttributeType::PosY:
queueLinearVelocityUpdate(xShape, { 0, 0 }, 1); return; default: return;
}
}
void box2DWorld::alertPhysicsAnimationEnd(const slideshow::internal::ShapeSharedPtr& pShape)
{ constauto iter = mpXShapeToBodyMap.find(pShape->getXShape());
assert(iter != mpXShapeToBodyMap.end());
Box2DBodySharedPtr pBox2DBody = iter->second; // since the animation ended make the body static
makeBodyStatic(pBox2DBody);
pBox2DBody->setRestitution(fDefaultStaticBodyBounciness); if (--mnPhysicsAnimationCounter == 0)
{ // if there are no more physics animation effects going on clean up
maShapeParallelUpdateQueue = {};
mbShapesInitialized = false; // clearing the map will make the box2d bodies get // destroyed if there's nothing else that owns them
mpXShapeToBodyMap.clear();
} else
{ // the physics animation that will take over the lock after this one // shouldn't step the world for an update cycle - since it was already // stepped.
mbAlreadyStepped = true;
}
}
// attention fTimeStep should not vary. constfloat fTimeStep = 1.0f / 100.0f; constint nVelocityIterations = 6; constint nPositionIterations = 2;
unsignedint nStepAmount = static_cast<unsignedint>(std::round(fPassedTime / fTimeStep)); // find the actual time that will be stepped through so // that the updates can be processed using that value double fTimeSteppedThrough = fTimeStep * nStepAmount;
// do the updates required to simulate other animation effects going in parallel
processUpdateQueue(fTimeSteppedThrough);
if (!mbAlreadyStepped)
{ for (unsignedint nStepCounter = 0; nStepCounter < nStepAmount; nStepCounter++)
{
mpBox2DWorld->Step(fTimeStep, nVelocityIterations, nPositionIterations);
}
} else
{ // just got the step lock from another physics animation // so skipping stepping the world for an update cycle
mbAlreadyStepped = false;
}
slideshow::internal::ShapeAttributeLayerSharedPtr pShapeAttributeLayer
= static_cast<slideshow::internal::AttributableShape*>(rShape.get())
->getTopmostAttributeLayer(); if (pShapeAttributeLayer && pShapeAttributeLayer->isRotationAngleValid())
{ // if the shape's rotation value was altered by another animation effect set it.
aBodyDef.angle = ::basegfx::deg2rad(-pShapeAttributeLayer->getRotationAngle());
}
// create a shared pointer with a destructor so that the body will be properly destroyed
std::shared_ptr<b2Body> pBody(mpBox2DWorld->CreateBody(&aBodyDef), [](b2Body* pB2Body) {
pB2Body->GetWorld()->DestroyBody(pB2Body);
});
basegfx::B2DPolyPolygon aPolyPolygon; // workaround: // TakeXorPoly() doesn't return beziers for CustomShapes and we want the beziers // so that we can decide the complexity of the polygons generated from them if (aShapeType == "com.sun.star.drawing.CustomShape")
{
aPolyPolygon = static_cast<SdrObjCustomShape*>(pSdrObject)->GetLineGeometry(true);
} else
{
aPolyPolygon = pSdrObject->TakeXorPoly();
}
// make beziers into polygons, using a high degree angle as fAngleBound in // adaptiveSubdivideByAngle reduces complexity of the resulting polygon shapes
aPolyPolygon = aPolyPolygon.areControlPointsUsed()
? basegfx::utils::adaptiveSubdivideByAngle(aPolyPolygon, 20)
: aPolyPolygon;
aPolyPolygon.removeDoublePoints();
// make polygon coordinates relative to the center of the shape instead of top left of the slide // since box2d shapes are expressed this way
aPolyPolygon
= basegfx::utils::distort(aPolyPolygon, aPolyPolygon.getB2DRange(),
{ -aShapeBounds.getWidth() / 2, -aShapeBounds.getHeight() / 2 },
{ aShapeBounds.getWidth() / 2, -aShapeBounds.getHeight() / 2 },
{ -aShapeBounds.getWidth() / 2, aShapeBounds.getHeight() / 2 },
{ aShapeBounds.getWidth() / 2, aShapeBounds.getHeight() / 2 });
if (pSdrObject->IsClosedObj() && !pSdrObject->IsEdgeObj() && pSdrObject->HasFillStyle())
{
basegfx::triangulator::B2DTriangleVector aTriangleVector; // iterate over the polygons of the shape and create representations for them for (constauto& rPolygon : std::as_const(aPolyPolygon))
{ // if the polygon is closed it will be represented by triangles if (rPolygon.isClosed())
{
basegfx::triangulator::B2DTriangleVector aTempTriangleVector(
basegfx::triangulator::triangulate(rPolygon));
aTriangleVector.insert(aTriangleVector.end(), aTempTriangleVector.begin(),
aTempTriangleVector.end());
} else// otherwise it will be an edge representation (example: smile line of the smiley shape)
{
addEdgeShapeToBody(rPolygon, pBody.get(), mfScaleFactor);
}
}
addTriangleVectorToBody(aTriangleVector, pBody.get(), mfScaleFactor);
} else
{
addEdgeShapeToBody(aPolyPolygon, pBody.get(), mfScaleFactor);
}
void box2DBody::setPositionByLinearVelocity(const basegfx::B2DPoint& rDesiredPos, constdouble fPassedTime)
{ // kinematic bodies are not affected by other bodies, but unlike static ones can still have velocity if (mpBox2DBody->GetType() != b2_kinematicBody)
mpBox2DBody->SetType(b2_kinematicBody);
::basegfx::B2DPoint aCurrentPos = getPosition(); // calculate the velocity needed to reach the rDesiredPos in the given time frame
::basegfx::B2DVector aVelocity = (rDesiredPos - aCurrentPos) / fPassedTime;
setLinearVelocity(aVelocity);
}
void box2DBody::setAngleByAngularVelocity(constdouble fDesiredAngle, constdouble fPassedTime)
{ // kinematic bodies are not affected by other bodies, but unlike static ones can still have velocity if (mpBox2DBody->GetType() != b2_kinematicBody)
mpBox2DBody->SetType(b2_kinematicBody);
double fDeltaAngle = fDesiredAngle - getAngle();
// temporary hack for repeating animation effects while (fDeltaAngle > 180
|| fDeltaAngle < -180) // if it is bigger than 180 opposite rotation is actually closer
fDeltaAngle += fDeltaAngle > 0 ? -360 : +360;
void box2DBody::setCollision(constbool bCanCollide)
{ // collision have to be set for each fixture of the body individually for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture;
pFixture = pFixture->GetNext())
{
b2Filter aFilter = pFixture->GetFilterData(); // 0xFFFF means collides with everything // 0x0000 means collides with nothing
aFilter.maskBits = bCanCollide ? 0xFFFF : 0x0000;
pFixture->SetFilterData(aFilter);
}
}
void box2DBody::setDensityAndRestitution(constdouble fDensity, constdouble fRestitution)
{ // density and restitution have to be set for each fixture of the body individually for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture;
pFixture = pFixture->GetNext())
{
pFixture->SetDensity(static_cast<float>(fDensity));
pFixture->SetRestitution(static_cast<float>(fRestitution));
} // without resetting the massdata of the body, density change won't take effect
mpBox2DBody->ResetMassData();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.