1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/.
10 #include <box2dtools.hxx>
11 #include <config_box2d.h>
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>
27 #define BOX2D_SLIDE_SIZE_IN_METERS 100.00f
28 constexpr double fDefaultStaticBodyBounciness(0.1);
30 namespace box2d::utils
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
42 return BOX2D_SLIDE_SIZE_IN_METERS
/ fWidth
;
44 return BOX2D_SLIDE_SIZE_IN_METERS
/ fHeight
;
47 b2BodyType
getBox2DInternalBodyType(const box2DBodyType eType
)
52 case BOX2D_STATIC_BODY
:
54 case BOX2D_KINEMATIC_BODY
:
55 return b2_kinematicBody
;
56 case BOX2D_DYNAMIC_BODY
:
57 return b2_dynamicBody
;
61 box2DBodyType
getBox2DLOBodyType(const b2BodyType eType
)
67 return BOX2D_STATIC_BODY
;
68 case b2_kinematicBody
:
69 return BOX2D_KINEMATIC_BODY
;
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
;
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
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
)
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
)
228 mpBox2DWorld
= std::make_unique
<b2World
>(b2Vec2(0.0f
, -30.0f
));
229 createStaticFrameAroundSlide(rSlideSize
);
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
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
,
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
,
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
--;
359 switch (aQueueElement
.meUpdateType
)
362 case BOX2D_UPDATE_POSITION_CHANGE
:
363 setShapePositionByLinearVelocity(aQueueElement
.mxShape
,
364 aQueueElement
.maPosition
, fPassedTime
);
366 case BOX2D_UPDATE_POSITION
:
367 setShapePosition(aQueueElement
.mxShape
, aQueueElement
.maPosition
);
369 case BOX2D_UPDATE_ANGLE
:
370 setShapeAngleByAngularVelocity(aQueueElement
.mxShape
, aQueueElement
.mfAngle
,
373 case BOX2D_UPDATE_SIZE
:
375 case BOX2D_UPDATE_VISIBILITY
:
376 setShapeCollision(aQueueElement
.mxShape
, aQueueElement
.mbVisibility
);
378 case BOX2D_UPDATE_LINEAR_VELOCITY
:
379 setShapeLinearVelocity(aQueueElement
.mxShape
, aQueueElement
.maVelocity
);
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
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
,
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
,
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
)
518 case slideshow::internal::AttributeType::Visibility
:
519 queueShapeVisibilityUpdate(xShape
, pAttrLayer
->getVisibility());
521 case slideshow::internal::AttributeType::Rotate
:
522 queueDynamicRotationUpdate(xShape
, pAttrLayer
->getRotationAngle());
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() });
529 queueDynamicPositionUpdate(xShape
,
530 { pAttrLayer
->getPosX(), pAttrLayer
->getPosY() });
537 void box2DWorld::queueShapeAnimationEndUpdate(
538 const css::uno::Reference
<css::drawing::XShape
>& xShape
,
539 const slideshow::internal::AttributeType 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);
548 case slideshow::internal::AttributeType::PosX
:
549 case slideshow::internal::AttributeType::PosY
:
550 queueLinearVelocityUpdate(xShape
, { 0, 0 }, 1);
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();
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
579 mbAlreadyStepped
= true;
583 void box2DWorld::alertPhysicsAnimationStart(
584 const ::basegfx::B2DVector
& rSlideSize
,
585 const slideshow::internal::ShapeManagerSharedPtr
& pShapeManager
)
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
);
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
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
);
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
);
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();
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
;
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);
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)
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
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
);
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: */