tdf#130857 qt weld: Support mail merge "Server Auth" dialog
[LibreOffice.git] / slideshow / source / engine / box2dtools.cxx
blob5d99aef07e3e6f145cd4f58306f50328071d17bb
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <box2dtools.hxx>
11 #include <config_box2d.h>
12 #include BOX2D_HEADER
14 #include <shapemanager.hxx>
15 #include <attributableshape.hxx>
16 #include <basegfx/polygon/b2dpolypolygontools.hxx>
17 #include <basegfx/polygon/b2dpolygontools.hxx>
18 #include <basegfx/polygon/b2dpolygontriangulator.hxx>
20 #include <svx/svdobj.hxx>
21 #include <svx/svdoashp.hxx>
22 #include <svx/svdpage.hxx>
24 #include <svx/unoapi.hxx>
25 #include <utility>
27 #define BOX2D_SLIDE_SIZE_IN_METERS 100.00f
28 constexpr double fDefaultStaticBodyBounciness(0.1);
30 namespace box2d::utils
32 namespace
34 double calculateScaleFactor(const ::basegfx::B2DVector& rSlideSize)
36 double fWidth = rSlideSize.getX();
37 double fHeight = rSlideSize.getY();
39 // Scale factor is based on whatever is the larger
40 // value between slide width and height
41 if (fWidth > fHeight)
42 return BOX2D_SLIDE_SIZE_IN_METERS / fWidth;
43 else
44 return BOX2D_SLIDE_SIZE_IN_METERS / fHeight;
47 b2BodyType getBox2DInternalBodyType(const box2DBodyType eType)
49 switch (eType)
51 default:
52 case BOX2D_STATIC_BODY:
53 return b2_staticBody;
54 case BOX2D_KINEMATIC_BODY:
55 return b2_kinematicBody;
56 case BOX2D_DYNAMIC_BODY:
57 return b2_dynamicBody;
61 box2DBodyType getBox2DLOBodyType(const b2BodyType eType)
63 switch (eType)
65 default:
66 case b2_staticBody:
67 return BOX2D_STATIC_BODY;
68 case b2_kinematicBody:
69 return BOX2D_KINEMATIC_BODY;
70 case b2_dynamicBody:
71 return BOX2D_DYNAMIC_BODY;
75 b2Vec2 convertB2DPointToBox2DVec2(const basegfx::B2DPoint& aPoint, const double fScaleFactor)
77 return { static_cast<float>(aPoint.getX() * fScaleFactor),
78 static_cast<float>(aPoint.getY() * -fScaleFactor) };
81 // expects rTriangleVector to have coordinates relative to the shape's bounding box center
82 void addTriangleVectorToBody(const basegfx::triangulator::B2DTriangleVector& rTriangleVector,
83 b2Body* aBody, const float fDensity, const float fFriction,
84 const float fRestitution, const double fScaleFactor)
86 for (const basegfx::triangulator::B2DTriangle& aTriangle : rTriangleVector)
88 b2FixtureDef aFixture;
89 b2PolygonShape aPolygonShape;
90 b2Vec2 aTriangleVertices[3]
91 = { convertB2DPointToBox2DVec2(aTriangle.getA(), fScaleFactor),
92 convertB2DPointToBox2DVec2(aTriangle.getB(), fScaleFactor),
93 convertB2DPointToBox2DVec2(aTriangle.getC(), fScaleFactor) };
95 bool bValidPointDistance
96 = b2DistanceSquared(aTriangleVertices[0], aTriangleVertices[1]) > 0.003f
97 && b2DistanceSquared(aTriangleVertices[0], aTriangleVertices[2]) > 0.003f
98 && b2DistanceSquared(aTriangleVertices[1], aTriangleVertices[2]) > 0.003f;
100 if (bValidPointDistance)
102 // create a fixture that represents the triangle
103 aPolygonShape.Set(aTriangleVertices, 3);
104 aFixture.shape = &aPolygonShape;
105 aFixture.density = fDensity;
106 aFixture.friction = fFriction;
107 aFixture.restitution = fRestitution;
108 aBody->CreateFixture(&aFixture);
113 // expects rPolygon to have coordinates relative to it's center
114 void addEdgeShapeToBody(const basegfx::B2DPolygon& rPolygon, b2Body* aBody, const float fDensity,
115 const float fFriction, const float fRestitution, const double fScaleFactor)
117 // make sure there's no bezier curves on the polygon
118 assert(!rPolygon.areControlPointsUsed());
119 basegfx::B2DPolygon aPolygon = basegfx::utils::removeNeutralPoints(rPolygon);
121 // value that somewhat defines half width of the quadrilateral
122 // that will be representing edge segment in the box2d world
123 const float fHalfWidth = 0.1f;
124 bool bHasPreviousQuadrilateralEdge = false;
125 b2Vec2 aQuadrilateralVertices[4];
127 for (sal_uInt32 nIndex = 0; nIndex < aPolygon.count(); nIndex++)
129 b2FixtureDef aFixture;
130 b2PolygonShape aPolygonShape;
132 basegfx::B2DPoint aPointA;
133 basegfx::B2DPoint aPointB;
134 if (nIndex != 0)
136 // get two adjacent points to create an edge out of
137 aPointA = aPolygon.getB2DPoint(nIndex - 1);
138 aPointB = aPolygon.getB2DPoint(nIndex);
140 else if (aPolygon.isClosed())
142 // start by connecting the last point to the first one
143 aPointA = aPolygon.getB2DPoint(aPolygon.count() - 1);
144 aPointB = aPolygon.getB2DPoint(nIndex);
146 else // the polygon isn't closed, won't connect last and first points
148 continue;
151 // create a vector that represents the direction of the edge
152 // and make it a unit vector
153 b2Vec2 aEdgeUnitVec(convertB2DPointToBox2DVec2(aPointB, fScaleFactor)
154 - convertB2DPointToBox2DVec2(aPointA, fScaleFactor));
155 aEdgeUnitVec.Normalize();
157 // create a unit vector that represents Normal of the edge
158 b2Vec2 aEdgeNormal(-aEdgeUnitVec.y, aEdgeUnitVec.x);
160 // if there was an edge previously created it should just connect
161 // using it's ending points so that there are no empty spots
162 // between edge segments, if not use wherever aPointA is at
163 if (!bHasPreviousQuadrilateralEdge)
165 // the point is translated along the edge normal both directions by
166 // fHalfWidth to create a quadrilateral edge
167 aQuadrilateralVertices[0]
168 = convertB2DPointToBox2DVec2(aPointA, fScaleFactor) + fHalfWidth * aEdgeNormal;
169 aQuadrilateralVertices[1]
170 = convertB2DPointToBox2DVec2(aPointA, fScaleFactor) + -fHalfWidth * aEdgeNormal;
171 bHasPreviousQuadrilateralEdge = true;
173 aQuadrilateralVertices[2]
174 = convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + fHalfWidth * aEdgeNormal;
175 aQuadrilateralVertices[3]
176 = convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + -fHalfWidth * aEdgeNormal;
178 // check whether the edge would have degenerately close points
179 bool bValidPointDistance
180 = b2DistanceSquared(aQuadrilateralVertices[0], aQuadrilateralVertices[2]) > 0.003f;
182 if (bValidPointDistance)
184 // create a quadrilateral shaped fixture to represent the edge
185 aPolygonShape.Set(aQuadrilateralVertices, 4);
186 aFixture.shape = &aPolygonShape;
187 aFixture.density = fDensity;
188 aFixture.friction = fFriction;
189 aFixture.restitution = fRestitution;
190 aBody->CreateFixture(&aFixture);
192 // prepare the quadrilateral edge for next connection
193 aQuadrilateralVertices[0] = aQuadrilateralVertices[2];
194 aQuadrilateralVertices[1] = aQuadrilateralVertices[3];
199 void addEdgeShapeToBody(const basegfx::B2DPolyPolygon& rPolyPolygon, b2Body* aBody,
200 const float fDensity, const float fFriction, const float fRestitution,
201 const double fScaleFactor)
203 for (const basegfx::B2DPolygon& rPolygon : rPolyPolygon)
205 addEdgeShapeToBody(rPolygon, aBody, fDensity, fFriction, fRestitution, fScaleFactor);
210 box2DWorld::box2DWorld(const ::basegfx::B2DVector& rSlideSize)
211 : mpBox2DWorld()
212 , mfScaleFactor(calculateScaleFactor(rSlideSize))
213 , mbShapesInitialized(false)
214 , mbHasWorldStepper(false)
215 , mbAlreadyStepped(false)
216 , mnPhysicsAnimationCounter(0)
217 , mpXShapeToBodyMap()
218 , maShapeParallelUpdateQueue()
222 box2DWorld::~box2DWorld() = default;
224 bool box2DWorld::initiateWorld(const ::basegfx::B2DVector& rSlideSize)
226 if (!mpBox2DWorld)
228 mpBox2DWorld = std::make_unique<b2World>(b2Vec2(0.0f, -30.0f));
229 createStaticFrameAroundSlide(rSlideSize);
230 return false;
232 else
234 return true;
238 void box2DWorld::createStaticFrameAroundSlide(const ::basegfx::B2DVector& rSlideSize)
240 assert(mpBox2DWorld);
242 float fWidth = static_cast<float>(rSlideSize.getX() * mfScaleFactor);
243 float fHeight = static_cast<float>(rSlideSize.getY() * mfScaleFactor);
245 // static body for creating the frame around the slide
246 b2BodyDef aBodyDef;
247 aBodyDef.type = b2_staticBody;
248 aBodyDef.position.Set(0, 0);
250 // not going to be stored anywhere, will live
251 // as long as the Box2DWorld does
252 b2Body* pStaticBody = mpBox2DWorld->CreateBody(&aBodyDef);
254 // create an edge loop that represents slide frame
255 b2Vec2 aEdgePoints[4];
256 aEdgePoints[0].Set(0, 0);
257 aEdgePoints[1].Set(0, -fHeight);
258 aEdgePoints[2].Set(fWidth, -fHeight);
259 aEdgePoints[3].Set(fWidth, 0);
261 b2ChainShape aEdgesChainShape;
262 aEdgesChainShape.CreateLoop(aEdgePoints, 4);
264 // create the fixture for the shape
265 b2FixtureDef aFixtureDef;
266 aFixtureDef.shape = &aEdgesChainShape;
267 pStaticBody->CreateFixture(&aFixtureDef);
270 void box2DWorld::setShapePosition(const css::uno::Reference<css::drawing::XShape>& xShape,
271 const basegfx::B2DPoint& rOutPos)
273 const auto iter = mpXShapeToBodyMap.find(xShape);
274 assert(iter != mpXShapeToBodyMap.end());
275 Box2DBodySharedPtr pBox2DBody = iter->second;
276 pBox2DBody->setPosition(rOutPos);
279 void box2DWorld::setShapePositionByLinearVelocity(
280 const css::uno::Reference<css::drawing::XShape>& xShape, const basegfx::B2DPoint& rOutPos,
281 const double fPassedTime)
283 assert(mpBox2DWorld);
284 if (fPassedTime > 0) // this only makes sense if there was an advance in time
286 const auto iter = mpXShapeToBodyMap.find(xShape);
287 assert(iter != mpXShapeToBodyMap.end());
288 Box2DBodySharedPtr pBox2DBody = iter->second;
289 pBox2DBody->setPositionByLinearVelocity(rOutPos, fPassedTime);
293 void box2DWorld::setShapeLinearVelocity(const css::uno::Reference<css::drawing::XShape>& xShape,
294 const basegfx::B2DVector& rVelocity)
296 assert(mpBox2DWorld);
297 const auto iter = mpXShapeToBodyMap.find(xShape);
298 assert(iter != mpXShapeToBodyMap.end());
299 Box2DBodySharedPtr pBox2DBody = iter->second;
300 pBox2DBody->setLinearVelocity(rVelocity);
303 void box2DWorld::setShapeAngle(const css::uno::Reference<css::drawing::XShape>& xShape,
304 const double fAngle)
306 const auto iter = mpXShapeToBodyMap.find(xShape);
307 assert(iter != mpXShapeToBodyMap.end());
308 Box2DBodySharedPtr pBox2DBody = iter->second;
309 pBox2DBody->setAngle(fAngle);
312 void box2DWorld::setShapeAngleByAngularVelocity(
313 const css::uno::Reference<css::drawing::XShape>& xShape, const double fAngle,
314 const double fPassedTime)
316 assert(mpBox2DWorld);
317 if (fPassedTime > 0) // this only makes sense if there was an advance in time
319 const auto iter = mpXShapeToBodyMap.find(xShape);
320 assert(iter != mpXShapeToBodyMap.end());
321 Box2DBodySharedPtr pBox2DBody = iter->second;
322 pBox2DBody->setAngleByAngularVelocity(fAngle, fPassedTime);
326 void box2DWorld::setShapeAngularVelocity(const css::uno::Reference<css::drawing::XShape>& xShape,
327 const double fAngularVelocity)
329 assert(mpBox2DWorld);
330 const auto iter = mpXShapeToBodyMap.find(xShape);
331 assert(iter != mpXShapeToBodyMap.end());
332 Box2DBodySharedPtr pBox2DBody = iter->second;
333 pBox2DBody->setAngularVelocity(fAngularVelocity);
336 void box2DWorld::setShapeCollision(const css::uno::Reference<css::drawing::XShape>& xShape,
337 bool bCanCollide)
339 assert(mpBox2DWorld);
340 const auto iter = mpXShapeToBodyMap.find(xShape);
341 assert(iter != mpXShapeToBodyMap.end());
342 Box2DBodySharedPtr pBox2DBody = iter->second;
343 pBox2DBody->setCollision(bCanCollide);
346 void box2DWorld::processUpdateQueue(const double fPassedTime)
348 while (!maShapeParallelUpdateQueue.empty())
350 Box2DDynamicUpdateInformation& aQueueElement = maShapeParallelUpdateQueue.front();
352 if (aQueueElement.mnDelayForSteps > 0)
354 // it was queued as a delayed action, skip it, don't pop
355 aQueueElement.mnDelayForSteps--;
357 else
359 switch (aQueueElement.meUpdateType)
361 default:
362 case BOX2D_UPDATE_POSITION_CHANGE:
363 setShapePositionByLinearVelocity(aQueueElement.mxShape,
364 aQueueElement.maPosition, fPassedTime);
365 break;
366 case BOX2D_UPDATE_POSITION:
367 setShapePosition(aQueueElement.mxShape, aQueueElement.maPosition);
368 break;
369 case BOX2D_UPDATE_ANGLE:
370 setShapeAngleByAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngle,
371 fPassedTime);
372 break;
373 case BOX2D_UPDATE_SIZE:
374 break;
375 case BOX2D_UPDATE_VISIBILITY:
376 setShapeCollision(aQueueElement.mxShape, aQueueElement.mbVisibility);
377 break;
378 case BOX2D_UPDATE_LINEAR_VELOCITY:
379 setShapeLinearVelocity(aQueueElement.mxShape, aQueueElement.maVelocity);
380 break;
381 case BOX2D_UPDATE_ANGULAR_VELOCITY:
382 setShapeAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngularVelocity);
384 maShapeParallelUpdateQueue.pop();
389 void box2DWorld::initiateAllShapesAsStaticBodies(
390 const slideshow::internal::ShapeManagerSharedPtr& pShapeManager)
392 assert(mpBox2DWorld);
394 mbShapesInitialized = true;
395 auto aXShapeToShapeMap = pShapeManager->getXShapeToShapeMap();
397 std::unordered_map<css::uno::Reference<css::drawing::XShape>, bool> aXShapeBelongsToAGroup;
399 // iterate over the shapes in the current slide and flag them if they belong to a group
400 // will flag the only ones that are belong to a group since std::unordered_map operator[]
401 // defaults the value to false if the key doesn't have a corresponding value
402 for (auto aIt = aXShapeToShapeMap.begin(); aIt != aXShapeToShapeMap.end(); aIt++)
404 slideshow::internal::ShapeSharedPtr pShape = aIt->second;
405 if (pShape->isForeground())
407 SdrObject* pTemp = SdrObject::getSdrObjectFromXShape(pShape->getXShape());
408 if (pTemp && pTemp->IsGroupObject())
410 // if it is a group object iterate over its children and flag them
411 SdrObjList* aObjList = pTemp->GetSubList();
413 for (const rtl::Reference<SdrObject>& pGroupMember : *aObjList)
415 aXShapeBelongsToAGroup.insert(
416 std::make_pair(GetXShapeForSdrObject(pGroupMember.get()), true));
422 // iterate over shapes in the current slide
423 for (auto aIt = aXShapeToShapeMap.begin(); aIt != aXShapeToShapeMap.end(); aIt++)
425 slideshow::internal::ShapeSharedPtr pShape = aIt->second;
426 // only create static bodies for the shapes that do not belong to a group
427 // groups themselves will have one body that represents the whole shape
428 // collection
429 if (pShape->isForeground() && !aXShapeBelongsToAGroup[pShape->getXShape()])
431 Box2DBodySharedPtr pBox2DBody = createStaticBody(pShape);
433 mpXShapeToBodyMap.insert(std::make_pair(pShape->getXShape(), pBox2DBody));
434 if (!pShape->isVisible())
436 // if the shape isn't visible, queue an update for it
437 queueShapeVisibilityUpdate(pShape->getXShape(), false);
443 bool box2DWorld::hasWorldStepper() const { return mbHasWorldStepper; }
445 void box2DWorld::setHasWorldStepper(const bool bHasWorldStepper)
447 mbHasWorldStepper = bHasWorldStepper;
450 void box2DWorld::queueDynamicPositionUpdate(const css::uno::Reference<css::drawing::XShape>& xShape,
451 const basegfx::B2DPoint& rOutPos)
453 Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_POSITION_CHANGE };
454 aQueueElement.maPosition = rOutPos;
455 maShapeParallelUpdateQueue.push(aQueueElement);
458 void box2DWorld::queueLinearVelocityUpdate(const css::uno::Reference<css::drawing::XShape>& xShape,
459 const basegfx::B2DVector& rVelocity,
460 const int nDelayForSteps)
462 Box2DDynamicUpdateInformation aQueueElement
463 = { xShape, {}, BOX2D_UPDATE_LINEAR_VELOCITY, nDelayForSteps };
464 aQueueElement.maVelocity = rVelocity;
465 maShapeParallelUpdateQueue.push(aQueueElement);
468 void box2DWorld::queueDynamicRotationUpdate(const css::uno::Reference<css::drawing::XShape>& xShape,
469 const double fAngle)
471 Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_ANGLE };
472 aQueueElement.mfAngle = fAngle;
473 maShapeParallelUpdateQueue.push(aQueueElement);
476 void box2DWorld::queueAngularVelocityUpdate(const css::uno::Reference<css::drawing::XShape>& xShape,
477 const double fAngularVelocity, const int nDelayForSteps)
479 Box2DDynamicUpdateInformation aQueueElement
480 = { xShape, {}, BOX2D_UPDATE_ANGULAR_VELOCITY, nDelayForSteps };
481 aQueueElement.mfAngularVelocity = fAngularVelocity;
482 maShapeParallelUpdateQueue.push(aQueueElement);
485 void box2DWorld::queueShapeVisibilityUpdate(const css::uno::Reference<css::drawing::XShape>& xShape,
486 const bool bVisibility)
488 Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_VISIBILITY };
489 aQueueElement.mbVisibility = bVisibility;
490 maShapeParallelUpdateQueue.push(aQueueElement);
493 void box2DWorld::queueShapePositionUpdate(const css::uno::Reference<css::drawing::XShape>& xShape,
494 const basegfx::B2DPoint& rOutPos)
496 Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_POSITION };
497 aQueueElement.maPosition = rOutPos;
498 maShapeParallelUpdateQueue.push(aQueueElement);
501 void box2DWorld::queueShapePathAnimationUpdate(
502 const css::uno::Reference<css::drawing::XShape>& xShape,
503 const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer, const bool bIsFirstUpdate)
505 // Workaround for PathAnimations since they do not have their own AttributeType
506 // - using PosX makes it register a DynamicPositionUpdate -
507 queueShapeAnimationUpdate(xShape, pAttrLayer, slideshow::internal::AttributeType::PosX,
508 bIsFirstUpdate);
511 void box2DWorld::queueShapeAnimationUpdate(
512 const css::uno::Reference<css::drawing::XShape>& xShape,
513 const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer,
514 const slideshow::internal::AttributeType eAttrType, const bool bIsFirstUpdate)
516 switch (eAttrType)
518 case slideshow::internal::AttributeType::Visibility:
519 queueShapeVisibilityUpdate(xShape, pAttrLayer->getVisibility());
520 return;
521 case slideshow::internal::AttributeType::Rotate:
522 queueDynamicRotationUpdate(xShape, pAttrLayer->getRotationAngle());
523 return;
524 case slideshow::internal::AttributeType::PosX:
525 case slideshow::internal::AttributeType::PosY:
526 if (bIsFirstUpdate) // if it is the first update shape should _teleport_ to the position
527 queueShapePositionUpdate(xShape, { pAttrLayer->getPosX(), pAttrLayer->getPosY() });
528 else
529 queueDynamicPositionUpdate(xShape,
530 { pAttrLayer->getPosX(), pAttrLayer->getPosY() });
531 return;
532 default:
533 return;
537 void box2DWorld::queueShapeAnimationEndUpdate(
538 const css::uno::Reference<css::drawing::XShape>& xShape,
539 const slideshow::internal::AttributeType eAttrType)
541 switch (eAttrType)
543 // end updates that change the velocity are delayed for a step
544 // since we do not want them to override the last position/angle
545 case slideshow::internal::AttributeType::Rotate:
546 queueAngularVelocityUpdate(xShape, 0.0, 1);
547 return;
548 case slideshow::internal::AttributeType::PosX:
549 case slideshow::internal::AttributeType::PosY:
550 queueLinearVelocityUpdate(xShape, { 0, 0 }, 1);
551 return;
552 default:
553 return;
557 void box2DWorld::alertPhysicsAnimationEnd(const slideshow::internal::ShapeSharedPtr& pShape)
559 const auto iter = mpXShapeToBodyMap.find(pShape->getXShape());
560 assert(iter != mpXShapeToBodyMap.end());
561 Box2DBodySharedPtr pBox2DBody = iter->second;
562 // since the animation ended make the body static
563 makeBodyStatic(pBox2DBody);
564 pBox2DBody->setRestitution(fDefaultStaticBodyBounciness);
565 if (--mnPhysicsAnimationCounter == 0)
567 // if there are no more physics animation effects going on clean up
568 maShapeParallelUpdateQueue = {};
569 mbShapesInitialized = false;
570 // clearing the map will make the box2d bodies get
571 // destroyed if there's nothing else that owns them
572 mpXShapeToBodyMap.clear();
574 else
576 // the physics animation that will take over the lock after this one
577 // shouldn't step the world for an update cycle - since it was already
578 // stepped.
579 mbAlreadyStepped = true;
583 void box2DWorld::alertPhysicsAnimationStart(
584 const ::basegfx::B2DVector& rSlideSize,
585 const slideshow::internal::ShapeManagerSharedPtr& pShapeManager)
587 if (!mpBox2DWorld)
588 initiateWorld(rSlideSize);
590 if (!mbShapesInitialized)
591 initiateAllShapesAsStaticBodies(pShapeManager);
593 mnPhysicsAnimationCounter++;
596 void box2DWorld::step(const float fTimeStep, const int nVelocityIterations,
597 const int nPositionIterations)
599 assert(mpBox2DWorld);
600 mpBox2DWorld->Step(fTimeStep, nVelocityIterations, nPositionIterations);
603 double box2DWorld::stepAmount(const double fPassedTime, const float fTimeStep,
604 const int nVelocityIterations, const int nPositionIterations)
606 assert(mpBox2DWorld);
608 unsigned int nStepAmount = static_cast<unsigned int>(std::round(fPassedTime / fTimeStep));
609 // find the actual time that will be stepped through so
610 // that the updates can be processed using that value
611 double fTimeSteppedThrough = fTimeStep * nStepAmount;
613 // do the updates required to simulate other animation effects going in parallel
614 processUpdateQueue(fTimeSteppedThrough);
616 if (!mbAlreadyStepped)
618 for (unsigned int nStepCounter = 0; nStepCounter < nStepAmount; nStepCounter++)
620 step(fTimeStep, nVelocityIterations, nPositionIterations);
623 else
625 // just got the step lock from another physics animation
626 // so skipping stepping the world for an update cycle
627 mbAlreadyStepped = false;
630 return fTimeSteppedThrough;
633 bool box2DWorld::shapesInitialized() { return mbShapesInitialized; }
635 bool box2DWorld::isInitialized() const
637 if (mpBox2DWorld)
638 return true;
639 else
640 return false;
643 Box2DBodySharedPtr
644 box2DWorld::makeShapeDynamic(const css::uno::Reference<css::drawing::XShape>& xShape,
645 const basegfx::B2DVector& rStartVelocity, const double fDensity,
646 const double fBounciness)
648 assert(mpBox2DWorld);
649 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
650 pBox2DBody->setDensityAndRestitution(fDensity, fBounciness);
651 queueLinearVelocityUpdate(xShape, rStartVelocity, 1);
652 return makeBodyDynamic(pBox2DBody);
655 Box2DBodySharedPtr makeBodyDynamic(const Box2DBodySharedPtr& pBox2DBody)
657 if (pBox2DBody->getType() != BOX2D_DYNAMIC_BODY)
659 pBox2DBody->setType(BOX2D_DYNAMIC_BODY);
661 return pBox2DBody;
664 Box2DBodySharedPtr box2DWorld::makeShapeStatic(const slideshow::internal::ShapeSharedPtr& pShape)
666 assert(mpBox2DWorld);
667 assert(pShape && pShape->getXShape());
668 const auto iter = mpXShapeToBodyMap.find(pShape->getXShape());
669 assert(iter != mpXShapeToBodyMap.end());
670 Box2DBodySharedPtr pBox2DBody = iter->second;
671 return makeBodyStatic(pBox2DBody);
674 Box2DBodySharedPtr makeBodyStatic(const Box2DBodySharedPtr& pBox2DBody)
676 if (pBox2DBody->getType() != BOX2D_STATIC_BODY)
678 pBox2DBody->setType(BOX2D_STATIC_BODY);
680 return pBox2DBody;
683 Box2DBodySharedPtr box2DWorld::createStaticBody(const slideshow::internal::ShapeSharedPtr& rShape,
684 const float fDensity, const float fFriction)
686 assert(mpBox2DWorld);
688 ::basegfx::B2DRectangle aShapeBounds = rShape->getBounds();
690 b2BodyDef aBodyDef;
691 aBodyDef.type = b2_staticBody;
692 aBodyDef.position = convertB2DPointToBox2DVec2(aShapeBounds.getCenter(), mfScaleFactor);
694 slideshow::internal::ShapeAttributeLayerSharedPtr pShapeAttributeLayer
695 = static_cast<slideshow::internal::AttributableShape*>(rShape.get())
696 ->getTopmostAttributeLayer();
697 if (pShapeAttributeLayer && pShapeAttributeLayer->isRotationAngleValid())
699 // if the shape's rotation value was altered by another animation effect set it.
700 aBodyDef.angle = ::basegfx::deg2rad(-pShapeAttributeLayer->getRotationAngle());
703 // create a shared pointer with a destructor so that the body will be properly destroyed
704 std::shared_ptr<b2Body> pBody(mpBox2DWorld->CreateBody(&aBodyDef), [](b2Body* pB2Body) {
705 pB2Body->GetWorld()->DestroyBody(pB2Body);
708 SdrObject* pSdrObject = SdrObject::getSdrObjectFromXShape(rShape->getXShape());
710 rtl::OUString aShapeType = rShape->getXShape()->getShapeType();
712 basegfx::B2DPolyPolygon aPolyPolygon;
713 // workaround:
714 // TakeXorPoly() doesn't return beziers for CustomShapes and we want the beziers
715 // so that we can decide the complexity of the polygons generated from them
716 if (aShapeType == "com.sun.star.drawing.CustomShape")
718 aPolyPolygon = static_cast<SdrObjCustomShape*>(pSdrObject)->GetLineGeometry(true);
720 else
722 aPolyPolygon = pSdrObject->TakeXorPoly();
725 // make beziers into polygons, using a high degree angle as fAngleBound in
726 // adaptiveSubdivideByAngle reduces complexity of the resulting polygon shapes
727 aPolyPolygon = aPolyPolygon.areControlPointsUsed()
728 ? basegfx::utils::adaptiveSubdivideByAngle(aPolyPolygon, 20)
729 : aPolyPolygon;
730 aPolyPolygon.removeDoublePoints();
732 // make polygon coordinates relative to the center of the shape instead of top left of the slide
733 // since box2d shapes are expressed this way
734 aPolyPolygon
735 = basegfx::utils::distort(aPolyPolygon, aPolyPolygon.getB2DRange(),
736 { -aShapeBounds.getWidth() / 2, -aShapeBounds.getHeight() / 2 },
737 { aShapeBounds.getWidth() / 2, -aShapeBounds.getHeight() / 2 },
738 { -aShapeBounds.getWidth() / 2, aShapeBounds.getHeight() / 2 },
739 { aShapeBounds.getWidth() / 2, aShapeBounds.getHeight() / 2 });
741 if (pSdrObject->IsClosedObj() && !pSdrObject->IsEdgeObj() && pSdrObject->HasFillStyle())
743 basegfx::triangulator::B2DTriangleVector aTriangleVector;
744 // iterate over the polygons of the shape and create representations for them
745 for (const auto& rPolygon : std::as_const(aPolyPolygon))
747 // if the polygon is closed it will be represented by triangles
748 if (rPolygon.isClosed())
750 basegfx::triangulator::B2DTriangleVector aTempTriangleVector(
751 basegfx::triangulator::triangulate(rPolygon));
752 aTriangleVector.insert(aTriangleVector.end(), aTempTriangleVector.begin(),
753 aTempTriangleVector.end());
755 else // otherwise it will be an edge representation (example: smile line of the smiley shape)
757 addEdgeShapeToBody(rPolygon, pBody.get(), fDensity, fFriction,
758 static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor);
761 addTriangleVectorToBody(aTriangleVector, pBody.get(), fDensity, fFriction,
762 static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor);
764 else
766 addEdgeShapeToBody(aPolyPolygon, pBody.get(), fDensity, fFriction,
767 static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor);
770 return std::make_shared<box2DBody>(pBody, mfScaleFactor);
773 box2DBody::box2DBody(std::shared_ptr<b2Body> pBox2DBody, double fScaleFactor)
774 : mpBox2DBody(std::move(pBox2DBody))
775 , mfScaleFactor(fScaleFactor)
779 ::basegfx::B2DPoint box2DBody::getPosition() const
781 b2Vec2 aPosition = mpBox2DBody->GetPosition();
782 double fX = static_cast<double>(aPosition.x) / mfScaleFactor;
783 double fY = static_cast<double>(aPosition.y) / -mfScaleFactor;
784 return ::basegfx::B2DPoint(fX, fY);
787 void box2DBody::setPosition(const basegfx::B2DPoint& rPos)
789 mpBox2DBody->SetTransform(convertB2DPointToBox2DVec2(rPos, mfScaleFactor),
790 mpBox2DBody->GetAngle());
793 void box2DBody::setPositionByLinearVelocity(const basegfx::B2DPoint& rDesiredPos,
794 const double fPassedTime)
796 // kinematic bodies are not affected by other bodies, but unlike static ones can still have velocity
797 if (mpBox2DBody->GetType() != b2_kinematicBody)
798 mpBox2DBody->SetType(b2_kinematicBody);
800 ::basegfx::B2DPoint aCurrentPos = getPosition();
801 // calculate the velocity needed to reach the rDesiredPos in the given time frame
802 ::basegfx::B2DVector aVelocity = (rDesiredPos - aCurrentPos) / fPassedTime;
804 setLinearVelocity(aVelocity);
807 void box2DBody::setAngleByAngularVelocity(const double fDesiredAngle, const double fPassedTime)
809 // kinematic bodies are not affected by other bodies, but unlike static ones can still have velocity
810 if (mpBox2DBody->GetType() != b2_kinematicBody)
811 mpBox2DBody->SetType(b2_kinematicBody);
813 double fDeltaAngle = fDesiredAngle - getAngle();
815 // temporary hack for repeating animation effects
816 while (fDeltaAngle > 180
817 || fDeltaAngle < -180) // if it is bigger than 180 opposite rotation is actually closer
818 fDeltaAngle += fDeltaAngle > 0 ? -360 : +360;
820 double fAngularVelocity = fDeltaAngle / fPassedTime;
821 setAngularVelocity(fAngularVelocity);
824 void box2DBody::setLinearVelocity(const ::basegfx::B2DVector& rVelocity)
826 b2Vec2 aVelocity = { static_cast<float>(rVelocity.getX() * mfScaleFactor),
827 static_cast<float>(rVelocity.getY() * -mfScaleFactor) };
828 mpBox2DBody->SetLinearVelocity(aVelocity);
831 void box2DBody::setAngularVelocity(const double fAngularVelocity)
833 float fBox2DAngularVelocity = static_cast<float>(basegfx::deg2rad(-fAngularVelocity));
834 mpBox2DBody->SetAngularVelocity(fBox2DAngularVelocity);
837 void box2DBody::setCollision(const bool bCanCollide)
839 // collision have to be set for each fixture of the body individually
840 for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture;
841 pFixture = pFixture->GetNext())
843 b2Filter aFilter = pFixture->GetFilterData();
844 // 0xFFFF means collides with everything
845 // 0x0000 means collides with nothing
846 aFilter.maskBits = bCanCollide ? 0xFFFF : 0x0000;
847 pFixture->SetFilterData(aFilter);
851 double box2DBody::getAngle() const
853 double fAngle = static_cast<double>(mpBox2DBody->GetAngle());
854 return ::basegfx::rad2deg(-fAngle);
857 void box2DBody::setAngle(const double fAngle)
859 mpBox2DBody->SetTransform(mpBox2DBody->GetPosition(), ::basegfx::deg2rad(-fAngle));
862 void box2DBody::setDensityAndRestitution(const double fDensity, const double fRestitution)
864 // density and restitution have to be set for each fixture of the body individually
865 for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture;
866 pFixture = pFixture->GetNext())
868 pFixture->SetDensity(static_cast<float>(fDensity));
869 pFixture->SetRestitution(static_cast<float>(fRestitution));
871 // without resetting the massdata of the body, density change won't take effect
872 mpBox2DBody->ResetMassData();
875 void box2DBody::setRestitution(const double fRestitution)
877 for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture;
878 pFixture = pFixture->GetNext())
880 pFixture->SetRestitution(static_cast<float>(fRestitution));
884 void box2DBody::setType(box2DBodyType eType)
886 mpBox2DBody->SetType(getBox2DInternalBodyType(eType));
889 box2DBodyType box2DBody::getType() const { return getBox2DLOBodyType(mpBox2DBody->GetType()); }
892 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */