LibreOffice Module slideshow (master) 1
box2dtools.cxx
Go to the documentation of this file.
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.
4 *
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 */
9
10#include <box2dtools.hxx>
11#include <config_box2d.h>
12#include BOX2D_HEADER
13
14#include <shapemanager.hxx>
15#include <attributableshape.hxx>
19
20#include <svx/svdobj.hxx>
21#include <svx/svdoashp.hxx>
22#include <svx/svdpage.hxx>
23
24#include <svx/unoapi.hxx>
25#include <utility>
26
27#define BOX2D_SLIDE_SIZE_IN_METERS 100.00f
28constexpr double fDefaultStaticBodyBounciness(0.1);
29
30namespace box2d::utils
31{
32namespace
33{
34double calculateScaleFactor(const ::basegfx::B2DVector& rSlideSize)
35{
36 double fWidth = rSlideSize.getX();
37 double fHeight = rSlideSize.getY();
38
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;
45}
46
47b2BodyType getBox2DInternalBodyType(const box2DBodyType eType)
48{
49 switch (eType)
50 {
51 default:
53 return b2_staticBody;
55 return b2_kinematicBody;
57 return b2_dynamicBody;
58 }
59}
60
61box2DBodyType getBox2DLOBodyType(const b2BodyType eType)
62{
63 switch (eType)
64 {
65 default:
66 case b2_staticBody:
67 return BOX2D_STATIC_BODY;
68 case b2_kinematicBody:
70 case b2_dynamicBody:
71 return BOX2D_DYNAMIC_BODY;
72 }
73}
74
75b2Vec2 convertB2DPointToBox2DVec2(const basegfx::B2DPoint& aPoint, const double fScaleFactor)
76{
77 return { static_cast<float>(aPoint.getX() * fScaleFactor),
78 static_cast<float>(aPoint.getY() * -fScaleFactor) };
79}
80
81// expects rTriangleVector to have coordinates relative to the shape's bounding box center
82void addTriangleVectorToBody(const basegfx::triangulator::B2DTriangleVector& rTriangleVector,
83 b2Body* aBody, const float fDensity, const float fFriction,
84 const float fRestitution, const double fScaleFactor)
85{
86 for (const basegfx::triangulator::B2DTriangle& aTriangle : rTriangleVector)
87 {
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) };
94
95 bool bValidPointDistance = true;
96
97 // check whether the triangle has degenerately close points
98 for (int nPointIndexA = 0; nPointIndexA < 3; nPointIndexA++)
99 {
100 for (int nPointIndexB = 0; nPointIndexB < 3; nPointIndexB++)
101 {
102 if (nPointIndexA == nPointIndexB)
103 continue;
104
105 if (b2DistanceSquared(aTriangleVertices[nPointIndexA],
106 aTriangleVertices[nPointIndexB])
107 < 0.003f)
108 {
109 bValidPointDistance = false;
110 }
111 }
112 }
113
114 if (bValidPointDistance)
115 {
116 // create a fixture that represents the triangle
117 aPolygonShape.Set(aTriangleVertices, 3);
118 aFixture.shape = &aPolygonShape;
119 aFixture.density = fDensity;
120 aFixture.friction = fFriction;
121 aFixture.restitution = fRestitution;
122 aBody->CreateFixture(&aFixture);
123 }
124 }
125}
126
127// expects rPolygon to have coordinates relative to it's center
128void addEdgeShapeToBody(const basegfx::B2DPolygon& rPolygon, b2Body* aBody, const float fDensity,
129 const float fFriction, const float fRestitution, const double fScaleFactor)
130{
131 // make sure there's no bezier curves on the polygon
132 assert(!rPolygon.areControlPointsUsed());
134
135 // value that somewhat defines half width of the quadrilateral
136 // that will be representing edge segment in the box2d world
137 const float fHalfWidth = 0.1f;
138 bool bHasPreviousQuadrilateralEdge = false;
139 b2Vec2 aQuadrilateralVertices[4];
140
141 for (sal_uInt32 nIndex = 0; nIndex < aPolygon.count(); nIndex++)
142 {
143 b2FixtureDef aFixture;
144 b2PolygonShape aPolygonShape;
145
146 basegfx::B2DPoint aPointA;
147 basegfx::B2DPoint aPointB;
148 if (nIndex != 0)
149 {
150 // get two adjacent points to create an edge out of
151 aPointA = aPolygon.getB2DPoint(nIndex - 1);
152 aPointB = aPolygon.getB2DPoint(nIndex);
153 }
154 else if (aPolygon.isClosed())
155 {
156 // start by connecting the last point to the first one
157 aPointA = aPolygon.getB2DPoint(aPolygon.count() - 1);
158 aPointB = aPolygon.getB2DPoint(nIndex);
159 }
160 else // the polygon isn't closed, won't connect last and first points
161 {
162 continue;
163 }
164
165 // create a vector that represents the direction of the edge
166 // and make it a unit vector
167 b2Vec2 aEdgeUnitVec(convertB2DPointToBox2DVec2(aPointB, fScaleFactor)
168 - convertB2DPointToBox2DVec2(aPointA, fScaleFactor));
169 aEdgeUnitVec.Normalize();
170
171 // create a unit vector that represents Normal of the edge
172 b2Vec2 aEdgeNormal(-aEdgeUnitVec.y, aEdgeUnitVec.x);
173
174 // if there was an edge previously created it should just connect
175 // using it's ending points so that there are no empty spots
176 // between edge segments, if not use wherever aPointA is at
177 if (!bHasPreviousQuadrilateralEdge)
178 {
179 // the point is translated along the edge normal both directions by
180 // fHalfWidth to create a quadrilateral edge
181 aQuadrilateralVertices[0]
182 = convertB2DPointToBox2DVec2(aPointA, fScaleFactor) + fHalfWidth * aEdgeNormal;
183 aQuadrilateralVertices[1]
184 = convertB2DPointToBox2DVec2(aPointA, fScaleFactor) + -fHalfWidth * aEdgeNormal;
185 bHasPreviousQuadrilateralEdge = true;
186 }
187 aQuadrilateralVertices[2]
188 = convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + fHalfWidth * aEdgeNormal;
189 aQuadrilateralVertices[3]
190 = convertB2DPointToBox2DVec2(aPointB, fScaleFactor) + -fHalfWidth * aEdgeNormal;
191
192 // check whether the edge would have degenerately close points
193 bool bValidPointDistance
194 = b2DistanceSquared(aQuadrilateralVertices[0], aQuadrilateralVertices[2]) > 0.003f;
195
196 if (bValidPointDistance)
197 {
198 // create a quadrilateral shaped fixture to represent the edge
199 aPolygonShape.Set(aQuadrilateralVertices, 4);
200 aFixture.shape = &aPolygonShape;
201 aFixture.density = fDensity;
202 aFixture.friction = fFriction;
203 aFixture.restitution = fRestitution;
204 aBody->CreateFixture(&aFixture);
205
206 // prepare the quadrilateral edge for next connection
207 aQuadrilateralVertices[0] = aQuadrilateralVertices[2];
208 aQuadrilateralVertices[1] = aQuadrilateralVertices[3];
209 }
210 }
211}
212
213void addEdgeShapeToBody(const basegfx::B2DPolyPolygon& rPolyPolygon, b2Body* aBody,
214 const float fDensity, const float fFriction, const float fRestitution,
215 const double fScaleFactor)
216{
217 for (const basegfx::B2DPolygon& rPolygon : rPolyPolygon)
218 {
219 addEdgeShapeToBody(rPolygon, aBody, fDensity, fFriction, fRestitution, fScaleFactor);
220 }
221}
222}
223
224box2DWorld::box2DWorld(const ::basegfx::B2DVector& rSlideSize)
225 : mpBox2DWorld()
226 , mfScaleFactor(calculateScaleFactor(rSlideSize))
227 , mbShapesInitialized(false)
228 , mbHasWorldStepper(false)
229 , mbAlreadyStepped(false)
230 , mnPhysicsAnimationCounter(0)
231 , mpXShapeToBodyMap()
232 , maShapeParallelUpdateQueue()
233{
234}
235
236box2DWorld::~box2DWorld() = default;
237
238bool box2DWorld::initiateWorld(const ::basegfx::B2DVector& rSlideSize)
239{
240 if (!mpBox2DWorld)
241 {
242 mpBox2DWorld = std::make_unique<b2World>(b2Vec2(0.0f, -30.0f));
244 return false;
245 }
246 else
247 {
248 return true;
249 }
250}
251
252void box2DWorld::createStaticFrameAroundSlide(const ::basegfx::B2DVector& rSlideSize)
253{
254 assert(mpBox2DWorld);
255
256 float fWidth = static_cast<float>(rSlideSize.getX() * mfScaleFactor);
257 float fHeight = static_cast<float>(rSlideSize.getY() * mfScaleFactor);
258
259 // static body for creating the frame around the slide
260 b2BodyDef aBodyDef;
261 aBodyDef.type = b2_staticBody;
262 aBodyDef.position.Set(0, 0);
263
264 // not going to be stored anywhere, will live
265 // as long as the Box2DWorld does
266 b2Body* pStaticBody = mpBox2DWorld->CreateBody(&aBodyDef);
267
268 // create an edge loop that represents slide frame
269 b2Vec2 aEdgePoints[4];
270 aEdgePoints[0].Set(0, 0);
271 aEdgePoints[1].Set(0, -fHeight);
272 aEdgePoints[2].Set(fWidth, -fHeight);
273 aEdgePoints[3].Set(fWidth, 0);
274
275 b2ChainShape aEdgesChainShape;
276 aEdgesChainShape.CreateLoop(aEdgePoints, 4);
277
278 // create the fixture for the shape
279 b2FixtureDef aFixtureDef;
280 aFixtureDef.shape = &aEdgesChainShape;
281 pStaticBody->CreateFixture(&aFixtureDef);
282}
283
284void box2DWorld::setShapePosition(const css::uno::Reference<com::sun::star::drawing::XShape> xShape,
285 const basegfx::B2DPoint& rOutPos)
286{
287 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
288 pBox2DBody->setPosition(rOutPos);
289}
290
292 const css::uno::Reference<com::sun::star::drawing::XShape> xShape,
293 const basegfx::B2DPoint& rOutPos, const double fPassedTime)
294{
295 assert(mpBox2DWorld);
296 if (fPassedTime > 0) // this only makes sense if there was an advance in time
297 {
298 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
299 pBox2DBody->setPositionByLinearVelocity(rOutPos, fPassedTime);
300 }
301}
302
304 const css::uno::Reference<com::sun::star::drawing::XShape> xShape,
305 const basegfx::B2DVector& rVelocity)
306{
307 assert(mpBox2DWorld);
308 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
309 pBox2DBody->setLinearVelocity(rVelocity);
310}
311
312void box2DWorld::setShapeAngle(const css::uno::Reference<com::sun::star::drawing::XShape> xShape,
313 const double fAngle)
314{
315 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
316 pBox2DBody->setAngle(fAngle);
317}
318
320 const css::uno::Reference<com::sun::star::drawing::XShape> xShape, const double fAngle,
321 const double fPassedTime)
322{
323 assert(mpBox2DWorld);
324 if (fPassedTime > 0) // this only makes sense if there was an advance in time
325 {
326 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
327 pBox2DBody->setAngleByAngularVelocity(fAngle, fPassedTime);
328 }
329}
330
332 const css::uno::Reference<com::sun::star::drawing::XShape> xShape,
333 const double fAngularVelocity)
334{
335 assert(mpBox2DWorld);
336 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
337 pBox2DBody->setAngularVelocity(fAngularVelocity);
338}
339
341 const css::uno::Reference<com::sun::star::drawing::XShape> xShape, bool bCanCollide)
342{
343 assert(mpBox2DWorld);
344 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
345 pBox2DBody->setCollision(bCanCollide);
346}
347
348void box2DWorld::processUpdateQueue(const double fPassedTime)
349{
350 while (!maShapeParallelUpdateQueue.empty())
351 {
353
354 if (aQueueElement.mnDelayForSteps > 0)
355 {
356 // it was queued as a delayed action, skip it, don't pop
357 aQueueElement.mnDelayForSteps--;
358 }
359 else
360 {
361 switch (aQueueElement.meUpdateType)
362 {
363 default:
366 aQueueElement.maPosition, fPassedTime);
367 break;
369 setShapePosition(aQueueElement.mxShape, aQueueElement.maPosition);
370 break;
372 setShapeAngleByAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngle,
373 fPassedTime);
374 break;
376 break;
378 setShapeCollision(aQueueElement.mxShape, aQueueElement.mbVisibility);
379 break;
381 setShapeLinearVelocity(aQueueElement.mxShape, aQueueElement.maVelocity);
382 break;
384 setShapeAngularVelocity(aQueueElement.mxShape, aQueueElement.mfAngularVelocity);
385 }
387 }
388 }
389}
390
393{
394 assert(mpBox2DWorld);
395
396 mbShapesInitialized = true;
397 auto aXShapeToShapeMap = pShapeManager->getXShapeToShapeMap();
398
399 std::unordered_map<css::uno::Reference<css::drawing::XShape>, bool> aXShapeBelongsToAGroup;
400
401 // iterate over the shapes in the current slide and flag them if they belong to a group
402 // will flag the only ones that are belong to a group since std::unordered_map operator[]
403 // defaults the value to false if the key doesn't have a corresponding value
404 for (auto aIt = aXShapeToShapeMap.begin(); aIt != aXShapeToShapeMap.end(); aIt++)
405 {
406 slideshow::internal::ShapeSharedPtr pShape = aIt->second;
407 if (pShape->isForeground())
408 {
409 SdrObject* pTemp = SdrObject::getSdrObjectFromXShape(pShape->getXShape());
410 if (pTemp && pTemp->IsGroupObject())
411 {
412 // if it is a group object iterate over its children and flag them
413 SdrObjList* aObjList = pTemp->GetSubList();
414 const size_t nObjCount(aObjList->GetObjCount());
415
416 for (size_t nObjIndex = 0; nObjIndex < nObjCount; ++nObjIndex)
417 {
418 SdrObject* pGroupMember(aObjList->GetObj(nObjIndex));
419 aXShapeBelongsToAGroup.insert(
420 std::make_pair(GetXShapeForSdrObject(pGroupMember), true));
421 }
422 }
423 }
424 }
425
426 // iterate over shapes in the current slide
427 for (auto aIt = aXShapeToShapeMap.begin(); aIt != aXShapeToShapeMap.end(); aIt++)
428 {
429 slideshow::internal::ShapeSharedPtr pShape = aIt->second;
430 // only create static bodies for the shapes that do not belong to a group
431 // groups themselves will have one body that represents the whole shape
432 // collection
433 if (pShape->isForeground() && !aXShapeBelongsToAGroup[pShape->getXShape()])
434 {
435 Box2DBodySharedPtr pBox2DBody = createStaticBody(pShape);
436
437 mpXShapeToBodyMap.insert(std::make_pair(pShape->getXShape(), pBox2DBody));
438 if (!pShape->isVisible())
439 {
440 // if the shape isn't visible, queue an update for it
441 queueShapeVisibilityUpdate(pShape->getXShape(), false);
442 }
443 }
444 }
445}
446
448
449void box2DWorld::setHasWorldStepper(const bool bHasWorldStepper)
450{
451 mbHasWorldStepper = bHasWorldStepper;
452}
453
455 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape,
456 const basegfx::B2DPoint& rOutPos)
457{
458 Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_POSITION_CHANGE };
459 aQueueElement.maPosition = rOutPos;
460 maShapeParallelUpdateQueue.push(aQueueElement);
461}
462
464 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape,
465 const basegfx::B2DVector& rVelocity, const int nDelayForSteps)
466{
468 = { xShape, {}, BOX2D_UPDATE_LINEAR_VELOCITY, nDelayForSteps };
469 aQueueElement.maVelocity = rVelocity;
470 maShapeParallelUpdateQueue.push(aQueueElement);
471}
472
474 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, const double fAngle)
475{
476 Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_ANGLE };
477 aQueueElement.mfAngle = fAngle;
478 maShapeParallelUpdateQueue.push(aQueueElement);
479}
480
482 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape,
483 const double fAngularVelocity, const int nDelayForSteps)
484{
486 = { xShape, {}, BOX2D_UPDATE_ANGULAR_VELOCITY, nDelayForSteps };
487 aQueueElement.mfAngularVelocity = fAngularVelocity;
488 maShapeParallelUpdateQueue.push(aQueueElement);
489}
490
492 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape, const bool bVisibility)
493{
494 Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_VISIBILITY };
495 aQueueElement.mbVisibility = bVisibility;
496 maShapeParallelUpdateQueue.push(aQueueElement);
497}
498
500 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape,
501 const basegfx::B2DPoint& rOutPos)
502{
503 Box2DDynamicUpdateInformation aQueueElement = { xShape, {}, BOX2D_UPDATE_POSITION };
504 aQueueElement.maPosition = rOutPos;
505 maShapeParallelUpdateQueue.push(aQueueElement);
506}
507
509 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape,
510 const slideshow::internal::ShapeAttributeLayerSharedPtr& pAttrLayer, const bool bIsFirstUpdate)
511{
512 // Workaround for PathAnimations since they do not have their own AttributeType
513 // - using PosX makes it register a DynamicPositionUpdate -
515 bIsFirstUpdate);
516}
517
519 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape,
521 const slideshow::internal::AttributeType eAttrType, const bool bIsFirstUpdate)
522{
523 switch (eAttrType)
524 {
526 queueShapeVisibilityUpdate(xShape, pAttrLayer->getVisibility());
527 return;
529 queueDynamicRotationUpdate(xShape, pAttrLayer->getRotationAngle());
530 return;
533 if (bIsFirstUpdate) // if it is the first update shape should _teleport_ to the position
534 queueShapePositionUpdate(xShape, { pAttrLayer->getPosX(), pAttrLayer->getPosY() });
535 else
537 { pAttrLayer->getPosX(), pAttrLayer->getPosY() });
538 return;
539 default:
540 return;
541 }
542}
543
545 const css::uno::Reference<com::sun::star::drawing::XShape>& xShape,
547{
548 switch (eAttrType)
549 {
550 // end updates that change the velocity are delayed for a step
551 // since we do not want them to override the last position/angle
553 queueAngularVelocityUpdate(xShape, 0.0, 1);
554 return;
557 queueLinearVelocityUpdate(xShape, { 0, 0 }, 1);
558 return;
559 default:
560 return;
561 }
562}
563
565{
566 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(pShape->getXShape())->second;
567 // since the animation ended make the body static
568 makeBodyStatic(pBox2DBody);
569 pBox2DBody->setRestitution(fDefaultStaticBodyBounciness);
570 if (--mnPhysicsAnimationCounter == 0)
571 {
572 // if there are no more physics animation effects going on clean up
574 mbShapesInitialized = false;
575 // clearing the map will make the box2d bodies get
576 // destroyed if there's nothing else that owns them
577 mpXShapeToBodyMap.clear();
578 }
579 else
580 {
581 // the physics animation that will take over the lock after this one
582 // shouldn't step the world for an update cycle - since it was already
583 // stepped.
584 mbAlreadyStepped = true;
585 }
586}
587
589 const ::basegfx::B2DVector& rSlideSize,
591{
592 if (!mpBox2DWorld)
593 initiateWorld(rSlideSize);
594
596 initiateAllShapesAsStaticBodies(pShapeManager);
597
599}
600
601void box2DWorld::step(const float fTimeStep, const int nVelocityIterations,
602 const int nPositionIterations)
603{
604 assert(mpBox2DWorld);
605 mpBox2DWorld->Step(fTimeStep, nVelocityIterations, nPositionIterations);
606}
607
608double box2DWorld::stepAmount(const double fPassedTime, const float fTimeStep,
609 const int nVelocityIterations, const int nPositionIterations)
610{
611 assert(mpBox2DWorld);
612
613 unsigned int nStepAmount = static_cast<unsigned int>(std::round(fPassedTime / fTimeStep));
614 // find the actual time that will be stepped through so
615 // that the updates can be processed using that value
616 double fTimeSteppedThrough = fTimeStep * nStepAmount;
617
618 // do the updates required to simulate other animation effects going in parallel
619 processUpdateQueue(fTimeSteppedThrough);
620
621 if (!mbAlreadyStepped)
622 {
623 for (unsigned int nStepCounter = 0; nStepCounter < nStepAmount; nStepCounter++)
624 {
625 step(fTimeStep, nVelocityIterations, nPositionIterations);
626 }
627 }
628 else
629 {
630 // just got the step lock from another physics animation
631 // so skipping stepping the world for an update cycle
632 mbAlreadyStepped = false;
633 }
634
635 return fTimeSteppedThrough;
636}
637
639
641{
642 if (mpBox2DWorld)
643 return true;
644 else
645 return false;
646}
647
649box2DWorld::makeShapeDynamic(const css::uno::Reference<css::drawing::XShape>& xShape,
650 const basegfx::B2DVector& rStartVelocity, const double fDensity,
651 const double fBounciness)
652{
653 assert(mpBox2DWorld);
654 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(xShape)->second;
655 pBox2DBody->setDensityAndRestitution(fDensity, fBounciness);
656 queueLinearVelocityUpdate(xShape, rStartVelocity, 1);
657 return makeBodyDynamic(pBox2DBody);
658}
659
661{
662 if (pBox2DBody->getType() != BOX2D_DYNAMIC_BODY)
663 {
664 pBox2DBody->setType(BOX2D_DYNAMIC_BODY);
665 }
666 return pBox2DBody;
667}
668
670{
671 assert(mpBox2DWorld);
672 Box2DBodySharedPtr pBox2DBody = mpXShapeToBodyMap.find(pShape->getXShape())->second;
673 return makeBodyStatic(pBox2DBody);
674}
675
677{
678 if (pBox2DBody->getType() != BOX2D_STATIC_BODY)
679 {
680 pBox2DBody->setType(BOX2D_STATIC_BODY);
681 }
682 return pBox2DBody;
683}
684
686 const float fDensity, const float fFriction)
687{
688 assert(mpBox2DWorld);
689
690 ::basegfx::B2DRectangle aShapeBounds = rShape->getBounds();
691
692 b2BodyDef aBodyDef;
693 aBodyDef.type = b2_staticBody;
694 aBodyDef.position = convertB2DPointToBox2DVec2(aShapeBounds.getCenter(), mfScaleFactor);
695
697 = static_cast<slideshow::internal::AttributableShape*>(rShape.get())
698 ->getTopmostAttributeLayer();
699 if (pShapeAttributeLayer && pShapeAttributeLayer->isRotationAngleValid())
700 {
701 // if the shape's rotation value was altered by another animation effect set it.
702 aBodyDef.angle = ::basegfx::deg2rad(-pShapeAttributeLayer->getRotationAngle());
703 }
704
705 // create a shared pointer with a destructor so that the body will be properly destroyed
706 std::shared_ptr<b2Body> pBody(mpBox2DWorld->CreateBody(&aBodyDef), [](b2Body* pB2Body) {
707 pB2Body->GetWorld()->DestroyBody(pB2Body);
708 });
709
710 SdrObject* pSdrObject = SdrObject::getSdrObjectFromXShape(rShape->getXShape());
711
712 rtl::OUString aShapeType = rShape->getXShape()->getShapeType();
713
714 basegfx::B2DPolyPolygon aPolyPolygon;
715 // workaround:
716 // TakeXorPoly() doesn't return beziers for CustomShapes and we want the beziers
717 // so that we can decide the complexity of the polygons generated from them
718 if (aShapeType == "com.sun.star.drawing.CustomShape")
719 {
720 aPolyPolygon = static_cast<SdrObjCustomShape*>(pSdrObject)->GetLineGeometry(true);
721 }
722 else
723 {
724 aPolyPolygon = pSdrObject->TakeXorPoly();
725 }
726
727 // make beziers into polygons, using a high degree angle as fAngleBound in
728 // adaptiveSubdivideByAngle reduces complexity of the resulting polygon shapes
729 aPolyPolygon = aPolyPolygon.areControlPointsUsed()
731 : aPolyPolygon;
732 aPolyPolygon.removeDoublePoints();
733
734 // make polygon coordinates relative to the center of the shape instead of top left of the slide
735 // since box2d shapes are expressed this way
736 aPolyPolygon
737 = basegfx::utils::distort(aPolyPolygon, aPolyPolygon.getB2DRange(),
738 { -aShapeBounds.getWidth() / 2, -aShapeBounds.getHeight() / 2 },
739 { aShapeBounds.getWidth() / 2, -aShapeBounds.getHeight() / 2 },
740 { -aShapeBounds.getWidth() / 2, aShapeBounds.getHeight() / 2 },
741 { aShapeBounds.getWidth() / 2, aShapeBounds.getHeight() / 2 });
742
743 if (pSdrObject->IsClosedObj() && !pSdrObject->IsEdgeObj() && pSdrObject->HasFillStyle())
744 {
746 // iterate over the polygons of the shape and create representations for them
747 for (const auto& rPolygon : std::as_const(aPolyPolygon))
748 {
749 // if the polygon is closed it will be represented by triangles
750 if (rPolygon.isClosed())
751 {
754 aTriangleVector.insert(aTriangleVector.end(), aTempTriangleVector.begin(),
755 aTempTriangleVector.end());
756 }
757 else // otherwise it will be an edge representation (example: smile line of the smiley shape)
758 {
759 addEdgeShapeToBody(rPolygon, pBody.get(), fDensity, fFriction,
760 static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor);
761 }
762 }
763 addTriangleVectorToBody(aTriangleVector, pBody.get(), fDensity, fFriction,
764 static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor);
765 }
766 else
767 {
768 addEdgeShapeToBody(aPolyPolygon, pBody.get(), fDensity, fFriction,
769 static_cast<float>(fDefaultStaticBodyBounciness), mfScaleFactor);
770 }
771
772 return std::make_shared<box2DBody>(pBody, mfScaleFactor);
773}
774
775box2DBody::box2DBody(std::shared_ptr<b2Body> pBox2DBody, double fScaleFactor)
776 : mpBox2DBody(std::move(pBox2DBody))
777 , mfScaleFactor(fScaleFactor)
778{
779}
780
782{
783 b2Vec2 aPosition = mpBox2DBody->GetPosition();
784 double fX = static_cast<double>(aPosition.x) / mfScaleFactor;
785 double fY = static_cast<double>(aPosition.y) / -mfScaleFactor;
786 return ::basegfx::B2DPoint(fX, fY);
787}
788
790{
791 mpBox2DBody->SetTransform(convertB2DPointToBox2DVec2(rPos, mfScaleFactor),
792 mpBox2DBody->GetAngle());
793}
794
796 const double fPassedTime)
797{
798 // kinematic bodies are not affected by other bodies, but unlike static ones can still have velocity
799 if (mpBox2DBody->GetType() != b2_kinematicBody)
800 mpBox2DBody->SetType(b2_kinematicBody);
801
802 ::basegfx::B2DPoint aCurrentPos = getPosition();
803 // calculate the velocity needed to reach the rDesiredPos in the given time frame
804 ::basegfx::B2DVector aVelocity = (rDesiredPos - aCurrentPos) / fPassedTime;
805
806 setLinearVelocity(aVelocity);
807}
808
809void box2DBody::setAngleByAngularVelocity(const double fDesiredAngle, const double fPassedTime)
810{
811 // kinematic bodies are not affected by other bodies, but unlike static ones can still have velocity
812 if (mpBox2DBody->GetType() != b2_kinematicBody)
813 mpBox2DBody->SetType(b2_kinematicBody);
814
815 double fDeltaAngle = fDesiredAngle - getAngle();
816
817 // temporary hack for repeating animation effects
818 while (fDeltaAngle > 180
819 || fDeltaAngle < -180) // if it is bigger than 180 opposite rotation is actually closer
820 fDeltaAngle += fDeltaAngle > 0 ? -360 : +360;
821
822 double fAngularVelocity = fDeltaAngle / fPassedTime;
823 setAngularVelocity(fAngularVelocity);
824}
825
826void box2DBody::setLinearVelocity(const ::basegfx::B2DVector& rVelocity)
827{
828 b2Vec2 aVelocity = { static_cast<float>(rVelocity.getX() * mfScaleFactor),
829 static_cast<float>(rVelocity.getY() * -mfScaleFactor) };
830 mpBox2DBody->SetLinearVelocity(aVelocity);
831}
832
833void box2DBody::setAngularVelocity(const double fAngularVelocity)
834{
835 float fBox2DAngularVelocity = static_cast<float>(basegfx::deg2rad(-fAngularVelocity));
836 mpBox2DBody->SetAngularVelocity(fBox2DAngularVelocity);
837}
838
839void box2DBody::setCollision(const bool bCanCollide)
840{
841 // collision have to be set for each fixture of the body individually
842 for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture;
843 pFixture = pFixture->GetNext())
844 {
845 b2Filter aFilter = pFixture->GetFilterData();
846 // 0xFFFF means collides with everything
847 // 0x0000 means collides with nothing
848 aFilter.maskBits = bCanCollide ? 0xFFFF : 0x0000;
849 pFixture->SetFilterData(aFilter);
850 }
851}
852
854{
855 double fAngle = static_cast<double>(mpBox2DBody->GetAngle());
856 return ::basegfx::rad2deg(-fAngle);
857}
858
859void box2DBody::setAngle(const double fAngle)
860{
861 mpBox2DBody->SetTransform(mpBox2DBody->GetPosition(), ::basegfx::deg2rad(-fAngle));
862}
863
864void box2DBody::setDensityAndRestitution(const double fDensity, const double fRestitution)
865{
866 // density and restitution have to be set for each fixture of the body individually
867 for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture;
868 pFixture = pFixture->GetNext())
869 {
870 pFixture->SetDensity(static_cast<float>(fDensity));
871 pFixture->SetRestitution(static_cast<float>(fRestitution));
872 }
873 // without resetting the massdata of the body, density change won't take effect
874 mpBox2DBody->ResetMassData();
875}
876
877void box2DBody::setRestitution(const double fRestitution)
878{
879 for (b2Fixture* pFixture = mpBox2DBody->GetFixtureList(); pFixture;
880 pFixture = pFixture->GetNext())
881 {
882 pFixture->SetRestitution(static_cast<float>(fRestitution));
883 }
884}
885
887{
888 mpBox2DBody->SetType(getBox2DInternalBodyType(eType));
889}
890
891box2DBodyType box2DBody::getType() const { return getBox2DLOBodyType(mpBox2DBody->GetType()); }
892}
893
894/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
box2d::utils::Box2DBodySharedPtr mpBox2DBody
box2d::utils::Box2DWorldSharedPtr mpBox2DWorld
constexpr double fDefaultStaticBodyBounciness(0.1)
#define BOX2D_SLIDE_SIZE_IN_METERS
Definition: box2dtools.cxx:27
static SdrObject * getSdrObjectFromXShape(const css::uno::Reference< css::uno::XInterface > &xInt)
virtual basegfx::B2DPolyPolygon TakeXorPoly() const
bool IsEdgeObj() const
bool IsClosedObj() const
virtual SdrObjList * GetSubList() const
bool IsGroupObject() const
bool HasFillStyle() const
B2DRange getB2DRange() const
bool areControlPointsUsed() const
bool isClosed() const
basegfx::B2DPoint const & getB2DPoint(sal_uInt32 nIndex) const
bool areControlPointsUsed() const
sal_uInt32 count() const
B2DPoint getCenter() const
TYPE getX() const
TYPE getY() const
void setPosition(const ::basegfx::B2DPoint &rPos)
Set the position of box2d body.
Definition: box2dtools.cxx:789
std::shared_ptr< b2Body > mpBox2DBody
Pointer to the body that this class manages.
Definition: box2dtools.hxx:369
void setAngularVelocity(const double fAngularVelocity)
Sets angular velocity of the body.
Definition: box2dtools.cxx:833
void setCollision(const bool bCanCollide)
Sets whether the body have collisions or not.
Definition: box2dtools.cxx:839
void setPositionByLinearVelocity(const ::basegfx::B2DPoint &rDesiredPos, const double fPassedTime)
Moves body to the specified position.
Definition: box2dtools.cxx:795
box2DBodyType getType() const
Definition: box2dtools.cxx:891
box2DBody(std::shared_ptr< b2Body > pBox2DBody, double fScaleFactor)
Definition: box2dtools.cxx:775
void setAngle(const double fAngle)
Set angle of the box2d body.
Definition: box2dtools.cxx:859
void setLinearVelocity(const ::basegfx::B2DVector &rVelocity)
Sets linear velocity of the body.
Definition: box2dtools.cxx:826
void setType(box2DBodyType eType)
Set type of the body.
Definition: box2dtools.cxx:886
::basegfx::B2DPoint getPosition() const
Definition: box2dtools.cxx:781
double mfScaleFactor
Scale factor for conversions between LO user space coordinates to Box2D World coordinates.
Definition: box2dtools.hxx:371
void setRestitution(const double fRestitution)
Set restitution of the box2d body.
Definition: box2dtools.cxx:877
void setDensityAndRestitution(const double fDensity, const double fRestitution)
Set density and restitution of the box2d body.
Definition: box2dtools.cxx:864
double getAngle() const
Definition: box2dtools.cxx:853
void setAngleByAngularVelocity(const double fDesiredAngle, const double fPassedTime)
Rotate body to specified angle of rotation.
Definition: box2dtools.cxx:809
void queueDynamicRotationUpdate(const css::uno::Reference< com::sun::star::drawing::XShape > &xShape, const double fAngle)
Queue a rotation update that is simulated as if shape's corresponding box2D body rotated to given ang...
Definition: box2dtools.cxx:473
double mfScaleFactor
Scale factor for conversions between LO user space coordinates to Box2D World coordinates.
Definition: box2dtools.hxx:82
void setShapeAngularVelocity(const css::uno::Reference< com::sun::star::drawing::XShape > xShape, const double fAngularVelocity)
Sets angular velocity of the shape's corresponding Box2D body.
Definition: box2dtools.cxx:331
void queueShapePositionUpdate(const css::uno::Reference< css::drawing::XShape > &xShape, const ::basegfx::B2DPoint &rOutPos)
Definition: box2dtools.cxx:499
void initiateAllShapesAsStaticBodies(const slideshow::internal::ShapeManagerSharedPtr &pShapeManager)
Initiate all the shapes in the current slide in the box2DWorld as static ones.
Definition: box2dtools.cxx:391
Box2DBodySharedPtr makeShapeStatic(const slideshow::internal::ShapeSharedPtr &pShape)
Make the Box2D body corresponding to the given shape a static one.
Definition: box2dtools.cxx:669
void queueShapeAnimationUpdate(const css::uno::Reference< css::drawing::XShape > &xShape, const slideshow::internal::ShapeAttributeLayerSharedPtr &pAttrLayer, const slideshow::internal::AttributeType eAttrType, const bool bIsFirstUpdate)
Queue an appropriate update for the animation effect that is in parallel with a physics animation.
Definition: box2dtools.cxx:518
void queueShapeAnimationEndUpdate(const css::uno::Reference< css::drawing::XShape > &xShape, const slideshow::internal::AttributeType eAttrType)
Queue an appropriate update for the animation effect that just ended.
Definition: box2dtools.cxx:544
bool mbAlreadyStepped
Flag used to stop overstepping that occurs when a physics animation effect transfers step-lock to ano...
Definition: box2dtools.hxx:89
void setHasWorldStepper(const bool bHasWorldStepper)
Set the flag for whether the box2DWorld has a stepper or not.
Definition: box2dtools.cxx:449
void setShapeCollision(const css::uno::Reference< com::sun::star::drawing::XShape > xShape, const bool bCanCollide)
Sets whether a shape's corresponding Box2D body has collision in the Box2D World or not.
Definition: box2dtools.cxx:340
std::queue< Box2DDynamicUpdateInformation > maShapeParallelUpdateQueue
Queue that holds any required information to keep LO animation effects and Box2DWorld in sync.
Definition: box2dtools.hxx:101
void queueShapeVisibilityUpdate(const css::uno::Reference< css::drawing::XShape > &xShape, const bool bVisibility)
Queue an collision update that sets the collision of shape's corresponding box2D body when processed.
Definition: box2dtools.cxx:491
void setShapePositionByLinearVelocity(const css::uno::Reference< css::drawing::XShape > xShape, const ::basegfx::B2DPoint &rOutPos, const double fPassedTime)
Moves shape's corresponding Box2D body to specified position.
Definition: box2dtools.cxx:291
void queueLinearVelocityUpdate(const css::uno::Reference< css::drawing::XShape > &xShape, const ::basegfx::B2DVector &rVelocity, const int nDelayForSteps=0)
Queue a update that sets the corresponding box2D body's linear velocity to the given value when proce...
Definition: box2dtools.cxx:463
void alertPhysicsAnimationStart(const ::basegfx::B2DVector &rSlideSize, const slideshow::internal::ShapeManagerSharedPtr &pShapeManager)
Alert that a physics animation effect has started.
Definition: box2dtools.cxx:588
box2DWorld(const ::basegfx::B2DVector &rSlideSize)
Definition: box2dtools.cxx:224
void setShapePosition(const css::uno::Reference< css::drawing::XShape > xShape, const ::basegfx::B2DPoint &rOutPos)
Sets shape's corresponding Box2D body to the specified position.
Definition: box2dtools.cxx:284
void alertPhysicsAnimationEnd(const slideshow::internal::ShapeSharedPtr &pShape)
Alert that a physics animation effect has ended.
Definition: box2dtools.cxx:564
void step(const float fTimeStep=1.0f/100.0f, const int nVelocityIterations=6, const int nPositionIterations=2)
Simulate and step through time in the Box2D World.
Definition: box2dtools.cxx:601
void queueDynamicPositionUpdate(const css::uno::Reference< css::drawing::XShape > &xShape, const ::basegfx::B2DPoint &rOutPos)
Queue a position update that is simulated as if shape's corresponding box2D body moved to given posit...
Definition: box2dtools.cxx:454
double stepAmount(const double fPassedTime, const float fTimeStep=1.0f/100.0f, const int nVelocityIterations=6, const int nPositionIterations=2)
Simulate and step through a given amount of time in the Box2D World.
Definition: box2dtools.cxx:608
int mnPhysicsAnimationCounter
Number of Physics Animations going on.
Definition: box2dtools.hxx:91
bool isInitialized() const
Definition: box2dtools.cxx:640
void queueAngularVelocityUpdate(const css::uno::Reference< com::sun::star::drawing::XShape > &xShape, const double fAngularVelocity, const int nDelayForSteps=0)
Queue an angular velocity update that sets the shape's corresponding box2D body angular velocity to t...
Definition: box2dtools.cxx:481
void processUpdateQueue(const double fPassedTime)
Process the updates queued in the maShapeParallelUpdateQueue.
Definition: box2dtools.cxx:348
void setShapeLinearVelocity(const css::uno::Reference< com::sun::star::drawing::XShape > xShape, const basegfx::B2DVector &rVelocity)
Sets linear velocity of the shape's corresponding Box2D body.
Definition: box2dtools.cxx:303
bool initiateWorld(const ::basegfx::B2DVector &rSlideSize)
Definition: box2dtools.cxx:238
bool hasWorldStepper() const
Definition: box2dtools.cxx:447
Box2DBodySharedPtr createStaticBody(const slideshow::internal::ShapeSharedPtr &rShape, const float fDensity=1.0f, const float fFriction=0.3f)
Create a static body that is represented by the shape's geometry.
Definition: box2dtools.cxx:685
bool mbHasWorldStepper
Holds whether or not there is a PhysicsAnimation that is stepping the Box2D World.
Definition: box2dtools.hxx:86
std::unique_ptr< b2World > mpBox2DWorld
Pointer to the real Box2D World that this class manages.
Definition: box2dtools.hxx:80
void createStaticFrameAroundSlide(const ::basegfx::B2DVector &rSlideSize)
Creates a static frame in Box2D world that corresponds to the slide borders.
Definition: box2dtools.cxx:252
Box2DBodySharedPtr makeShapeDynamic(const css::uno::Reference< css::drawing::XShape > &xShape, const basegfx::B2DVector &rStartVelocity, const double fDensity, const double fBounciness)
Make the shape's corresponding box2D body a dynamic one.
Definition: box2dtools.cxx:649
void setShapeAngleByAngularVelocity(const css::uno::Reference< com::sun::star::drawing::XShape > xShape, const double fAngle, const double fPassedTime)
Rotates shape's corresponding Box2D body to specified angle.
Definition: box2dtools.cxx:319
void setShapeAngle(const css::uno::Reference< com::sun::star::drawing::XShape > xShape, const double fAngle)
Sets rotation angle of the shape's corresponding Box2D body.
Definition: box2dtools.cxx:312
void queueShapePathAnimationUpdate(const css::uno::Reference< com::sun::star::drawing::XShape > &xShape, const slideshow::internal::ShapeAttributeLayerSharedPtr &pAttrLayer, const bool bIsFirstUpdate)
Queue an appropriate update for a path animation that is in parallel with a physics animation.
Definition: box2dtools.cxx:508
std::unordered_map< css::uno::Reference< css::drawing::XShape >, Box2DBodySharedPtr > mpXShapeToBodyMap
Definition: box2dtools.hxx:93
Represents an animatable shape, that can have its attributes changed.
std::deque< AttachedObject_Impl > aObjList
DocumentType eType
sal_Int32 nIndex
::std::vector< B2DTriangle > B2DTriangleVector
B2DTriangleVector triangulate(const B2DPolygon &rCandidate)
B2DPolygon removeNeutralPoints(const B2DPolygon &rCandidate)
B2DPoint distort(const B2DPoint &rCandidate, const B2DRange &rOriginal, const B2DPoint &rTopLeft, const B2DPoint &rTopRight, const B2DPoint &rBottomLeft, const B2DPoint &rBottomRight)
B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon &rCandidate, double fAngleBound)
constexpr double deg2rad(double v)
Box2DBodySharedPtr makeBodyStatic(const Box2DBodySharedPtr &pBox2DBody)
Make the Box2D body a static one.
Definition: box2dtools.cxx:676
Box2DBodySharedPtr makeBodyDynamic(const Box2DBodySharedPtr &pBox2DBody)
Make the Box2D body a dynamic one.
Definition: box2dtools.cxx:660
@ BOX2D_KINEMATIC_BODY
Definition: box2dtools.hxx:37
std::shared_ptr< box2DBody > Box2DBodySharedPtr
Definition: box2dtools.hxx:32
@ BOX2D_UPDATE_LINEAR_VELOCITY
Definition: box2dtools.hxx:48
@ BOX2D_UPDATE_POSITION_CHANGE
Definition: box2dtools.hxx:43
@ BOX2D_UPDATE_VISIBILITY
Definition: box2dtools.hxx:47
@ BOX2D_UPDATE_ANGULAR_VELOCITY
Definition: box2dtools.hxx:49
@ BOX2D_UPDATE_POSITION
Definition: box2dtools.hxx:44
::std::shared_ptr< ShapeAttributeLayer > ShapeAttributeLayerSharedPtr
AttributeType
Type of to-be-animated attribute.
std::shared_ptr< ShapeManager > ShapeManagerSharedPtr
Definition: box2dtools.hxx:23
::std::shared_ptr< Shape > ShapeSharedPtr
Holds required information to perform an update to box2d body of a shape that was altered by an anima...
Definition: box2dtools.hxx:55
int mnDelayForSteps
amount of steps to delay the update for
Definition: box2dtools.hxx:67
css::uno::Reference< css::drawing::XShape > mxShape
reference to the shape that the update belongs to
Definition: box2dtools.hxx:57
box2DNonsimulatedShapeUpdateType meUpdateType
Definition: box2dtools.hxx:65
SVXCORE_DLLPUBLIC css::uno::Reference< css::drawing::XShape > GetXShapeForSdrObject(SdrObject *pObj) noexcept