Added 2d physics benchmark
[platform/core/uifw/dali-demo.git] / examples / benchmark-2dphysics / benchmark-2d-physics-controller.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <dali-physics/dali-physics.h>
18 #include <dali-toolkit/dali-toolkit.h>
19
20 #include <dali-toolkit/devel-api/visuals/image-visual-properties-devel.h>
21 #include <dali-toolkit/devel-api/visuals/visual-properties-devel.h>
22 #include <dali/devel-api/adaptor-framework/key-devel.h>
23 #include <dali/devel-api/events/hit-test-algorithm.h>
24 #include <dali/integration-api/debug.h>
25
26 #include <chipmunk/chipmunk.h>
27 #include <cstdlib>
28 #include <iostream>
29 #include <string>
30
31 using namespace Dali;
32 using namespace Dali::Toolkit::Physics;
33 using namespace Dali::ParentOrigin;
34 using namespace Dali::AnchorPoint;
35
36 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gPhysicsDemo = Debug::Filter::New(Debug::Concise, false, "LOG_PHYSICS_EXAMPLE");
38 #endif
39
40 const float    MAX_ANIMATION_DURATION{60.0f};
41 const uint32_t ANIMATION_TIME{30000};
42 const uint32_t DEFAULT_BALL_COUNT{500};
43
44 #if defined(_ARCH_ARM_)
45 #define DEMO_ICON_DIR "/usr/share/icons"
46 #else
47 #define DEMO_ICON_DIR DEMO_IMAGE_DIR
48 #endif
49
50 const std::string BALL_IMAGES[] = {DEMO_IMAGE_DIR "/blocks-ball.png",
51                                    DEMO_ICON_DIR "/dali-tests.png",
52                                    DEMO_ICON_DIR "/dali-examples.png",
53                                    DEMO_ICON_DIR "/com.samsung.dali-demo.png"};
54
55 const Vector2 BALL_SIZE{26.0f, 26.0f};
56
57 // Groups that can collide with each other:
58 const cpGroup BALL_GROUP{1 << 0};
59 const cpGroup BOUNDS_GROUP{1 << 1};
60
61 const cpBitmask COLLISION_MASK{0xfF};
62
63 const cpBitmask BALL_COLLIDES_WITH{BALL_GROUP | BOUNDS_GROUP};
64
65 /**
66  * @brief The physics demo using Chipmunk2D APIs.
67  */
68 class Physics2dBenchmarkController : public ConnectionTracker
69 {
70 public:
71   Physics2dBenchmarkController(Application& app, int numberOfBalls)
72   : mApplication(app),
73     mBallNumber(numberOfBalls)
74   {
75     app.InitSignal().Connect(this, &Physics2dBenchmarkController::OnInit);
76     app.TerminateSignal().Connect(this, &Physics2dBenchmarkController::OnTerminate);
77   }
78
79   ~Physics2dBenchmarkController() = default;
80
81   void OnInit(Application& application)
82   {
83     mWindow = application.GetWindow();
84     mWindow.ResizeSignal().Connect(this, &Physics2dBenchmarkController::OnWindowResize);
85     mWindow.KeyEventSignal().Connect(this, &Physics2dBenchmarkController::OnKeyEv);
86     mWindow.GetRootLayer().TouchedSignal().Connect(this, &Physics2dBenchmarkController::OnTouched);
87     mWindow.SetBackgroundColor(Color::DARK_SLATE_GRAY);
88
89     CreateAnimationSimulation();
90
91     mTimer = Timer::New(ANIMATION_TIME);
92     mTimer.TickSignal().Connect(this, &Physics2dBenchmarkController::AnimationSimFinished);
93     mTimer.Start();
94   }
95
96   void CreateAnimationSimulation()
97   {
98     Window::WindowSize windowSize = mWindow.GetSize();
99     mBallActors.resize(mBallNumber);
100     mBallVelocity.resize(mBallNumber);
101
102     mAnimationSimRootActor = Layer::New();
103     mAnimationSimRootActor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
104     mAnimationSimRootActor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER;
105     mAnimationSimRootActor[Actor::Property::ANCHOR_POINT]  = Dali::AnchorPoint::CENTER;
106
107     mWindow.Add(mAnimationSimRootActor);
108     std::ostringstream oss;
109     oss << "Animation simulation of " << mBallNumber << " balls";
110     auto title = Toolkit::TextLabel::New(oss.str());
111     mAnimationSimRootActor.Add(title);
112     title[Toolkit::TextLabel::Property::TEXT_COLOR]           = Color::WHITE;
113     title[Actor::Property::PARENT_ORIGIN]                     = Dali::ParentOrigin::TOP_CENTER;
114     title[Actor::Property::ANCHOR_POINT]                      = Dali::AnchorPoint::TOP_CENTER;
115     title[Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT] = HorizontalAlignment::CENTER;
116     title.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS);
117
118     const float margin(BALL_SIZE.width * 0.5f);
119
120     for(int i = 0; i < mBallNumber; ++i)
121     {
122       Actor ball = mBallActors[i]          = Toolkit::ImageView::New(BALL_IMAGES[rand() % 4]);
123       ball[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER;
124       ball[Actor::Property::ANCHOR_POINT]  = Dali::AnchorPoint::CENTER;
125
126       ball[Actor::Property::NAME]     = "Ball";
127       ball[Actor::Property::SIZE]     = BALL_SIZE; // Halve the image size
128       int width                       = windowSize.GetWidth() / 2;
129       int height                      = windowSize.GetHeight() / 2;
130       ball[Actor::Property::POSITION] = Vector3(Random::Range(margin - width, width - margin), Random::Range(margin - height, height - margin), 0.0f);
131       ball.RegisterProperty("index", i);
132       mAnimationSimRootActor.Add(ball);
133
134       mBallVelocity[i] = Vector3(Random::Range(-25.0f, 25.0f), Random::Range(-25.0f, 25.0f), 0.0f);
135       mBallVelocity[i].Normalize();
136       mBallVelocity[i] = mBallVelocity[i] * Random::Range(15.0f, 50.0f);
137
138       PropertyNotification leftNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_X, LessThanCondition(margin - width));
139       leftNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitLeftWall);
140
141       PropertyNotification rightNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_X, GreaterThanCondition(width - margin));
142       rightNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitRightWall);
143
144       PropertyNotification topNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_Y, LessThanCondition(margin - height));
145       topNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitTopWall);
146
147       PropertyNotification bottomNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_Y, GreaterThanCondition(height - margin));
148       bottomNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitBottomWall);
149     }
150
151     title.RaiseToTop();
152     ContinueAnimation();
153   }
154
155   bool AnimationSimFinished()
156   {
157     static bool first = true;
158     if(first)
159     {
160       UnparentAndReset(mAnimationSimRootActor);
161       mBallAnimation.Stop();
162       mBallAnimation.Clear();
163       first = false;
164
165       CreatePhysicsSimulation();
166       return true;
167     }
168
169     mApplication.Quit();
170     return false;
171   }
172
173   void ContinueAnimation()
174   {
175     if(mBallAnimation)
176     {
177       mBallAnimation.Clear();
178     }
179     mBallAnimation = Animation::New(MAX_ANIMATION_DURATION);
180     for(int i = 0; i < mBallNumber; ++i)
181     {
182       mBallAnimation.AnimateBy(Property(mBallActors[i], Actor::Property::POSITION), mBallVelocity[i] * MAX_ANIMATION_DURATION);
183     }
184     mBallAnimation.Play();
185   }
186
187   void OnHitLeftWall(PropertyNotification& source)
188   {
189     auto actor = Actor::DownCast(source.GetTarget());
190     if(actor)
191     {
192       int index        = actor["index"];
193       mBallVelocity[index].x = fabsf(mBallVelocity[index].x);
194       ContinueAnimation();
195     }
196   }
197
198   void OnHitRightWall(PropertyNotification& source)
199   {
200     auto actor = Actor::DownCast(source.GetTarget());
201     if(actor)
202     {
203       int index        = actor["index"];
204       mBallVelocity[index].x = -fabsf(mBallVelocity[index].x);
205       ContinueAnimation();
206     }
207   }
208
209   void OnHitBottomWall(PropertyNotification& source)
210   {
211     auto actor = Actor::DownCast(source.GetTarget());
212     if(actor)
213     {
214       int index        = actor["index"];
215       mBallVelocity[index].y = -fabsf(mBallVelocity[index].y);
216       ContinueAnimation();
217     }
218   }
219
220   void OnHitTopWall(PropertyNotification& source)
221   {
222     auto actor = Actor::DownCast(source.GetTarget());
223     if(actor)
224     {
225       int index        = actor["index"];
226       mBallVelocity[index].y = fabsf(mBallVelocity[index].y);
227       ContinueAnimation();
228     }
229   }
230
231   void CreatePhysicsSimulation()
232   {
233     Window::WindowSize windowSize = mWindow.GetSize();
234
235     // Map Physics space (origin bottom left, +ve Y up)
236     // to DALi space (origin center, +ve Y down)
237     mPhysicsTransform.SetIdentityAndScale(Vector3(1.0f, -1.0f, 1.0f));
238     mPhysicsTransform.SetTranslation(Vector3(windowSize.GetWidth() * 0.5f,
239                                              windowSize.GetHeight() * 0.5f,
240                                              0.0f));
241
242     mPhysicsAdaptor = PhysicsAdaptor::New(mPhysicsTransform, windowSize);
243     mPhysicsRoot    = mPhysicsAdaptor.GetRootActor();
244     mWindow.Add(mPhysicsRoot);
245
246     auto     scopedAccessor = mPhysicsAdaptor.GetPhysicsAccessor();
247     cpSpace* space          = scopedAccessor->GetNative().Get<cpSpace*>();
248     cpSpaceSetGravity(space, cpv(0, 0));
249
250     CreateBounds(space, windowSize);
251
252     for(int i = 0; i < mBallNumber; ++i)
253     {
254       mBalls.push_back(CreateBall(space));
255     }
256
257     // Process any async queued methods next frame
258     mPhysicsAdaptor.CreateSyncPoint();
259
260     std::ostringstream oss;
261     oss << "Physics simulation of " << mBallNumber << " balls";
262     auto title = Toolkit::TextLabel::New(oss.str());
263     mPhysicsRoot.Add(title);
264     title[Toolkit::TextLabel::Property::TEXT_COLOR]           = Color::WHITE;
265     title[Actor::Property::PARENT_ORIGIN]                     = Dali::ParentOrigin::TOP_CENTER;
266     title[Actor::Property::ANCHOR_POINT]                      = Dali::AnchorPoint::TOP_CENTER;
267     title[Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT] = HorizontalAlignment::CENTER;
268     title.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS);
269     title.RaiseToTop();
270   }
271
272   PhysicsActor CreateBall(cpSpace* space)
273   {
274     const float BALL_MASS       = 10.0f;
275     const float BALL_RADIUS     = BALL_SIZE.x * 0.25f;
276     const float BALL_ELASTICITY = 1.0f;
277     const float BALL_FRICTION   = 0.0f;
278
279     auto ball                   = Toolkit::ImageView::New(BALL_IMAGES[rand() % 4]);
280     ball[Actor::Property::NAME] = "Ball";
281     ball[Actor::Property::SIZE] = BALL_SIZE * 0.5f;
282     cpBody* body                = cpSpaceAddBody(space, cpBodyNew(BALL_MASS, cpMomentForCircle(BALL_MASS, 0.0f, BALL_RADIUS, cpvzero)));
283
284     cpShape* shape = cpSpaceAddShape(space, cpCircleShapeNew(body, BALL_RADIUS, cpvzero));
285     cpShapeSetElasticity(shape, BALL_ELASTICITY);
286     cpShapeSetFriction(shape, BALL_FRICTION);
287
288     PhysicsActor physicsBall = mPhysicsAdaptor.AddActorBody(ball, body);
289
290     Window::WindowSize windowSize = mWindow.GetSize();
291
292     const float fw = 0.5f * (windowSize.GetWidth() - BALL_RADIUS);
293     const float fh = 0.5f * (windowSize.GetHeight() - BALL_RADIUS);
294
295     // Example of setting physics property on update thread
296     physicsBall.AsyncSetPhysicsPosition(Vector3(Random::Range(-fw, fw), Random::Range(-fh, -fh * 0.5), 0.0f));
297
298     // Example of queuing a chipmunk method to run on the update thread
299     mPhysicsAdaptor.Queue([body]() {
300       cpBodySetVelocity(body, cpv(Random::Range(-100.0, 100.0), Random::Range(-100.0, 100.0)));
301     });
302     return physicsBall;
303   }
304
305   void CreateBounds(cpSpace* space, Window::WindowSize size)
306   {
307     // We're working in physics space here - coords are: origin: bottom left, +ve Y: up
308     int32_t xBound = static_cast<int32_t>(static_cast<uint32_t>(size.GetWidth()));
309     int32_t yBound = static_cast<int32_t>(static_cast<uint32_t>(size.GetHeight()));
310
311     cpBody* staticBody = cpSpaceGetStaticBody(space);
312
313     if(mLeftBound)
314     {
315       cpSpaceRemoveShape(space, mLeftBound);
316       cpSpaceRemoveShape(space, mRightBound);
317       cpSpaceRemoveShape(space, mTopBound);
318       cpSpaceRemoveShape(space, mBottomBound);
319       cpShapeFree(mLeftBound);
320       cpShapeFree(mRightBound);
321       cpShapeFree(mTopBound);
322       cpShapeFree(mBottomBound);
323     }
324     mLeftBound   = AddBound(space, staticBody, cpv(0, 0), cpv(0, yBound));
325     mRightBound  = AddBound(space, staticBody, cpv(xBound, 0), cpv(xBound, yBound));
326     mTopBound    = AddBound(space, staticBody, cpv(0, 0), cpv(xBound, 0));
327     mBottomBound = AddBound(space, staticBody, cpv(0, yBound), cpv(xBound, yBound));
328   }
329
330   cpShape* AddBound(cpSpace* space, cpBody* staticBody, cpVect start, cpVect end)
331   {
332     cpShape* shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, start, end, 0.0f));
333     cpShapeSetElasticity(shape, 1.0f);
334     cpShapeSetFriction(shape, 1.0f);
335
336     cpShapeSetFilter(shape, cpShapeFilterNew(BOUNDS_GROUP, COLLISION_MASK, COLLISION_MASK));
337     return shape;
338   }
339
340   void OnTerminate(Application& application)
341   {
342     UnparentAndReset(mPhysicsRoot);
343   }
344
345   void OnWindowResize(Window window, Window::WindowSize newSize)
346   {
347     auto     scopedAccessor = mPhysicsAdaptor.GetPhysicsAccessor();
348     cpSpace* space          = scopedAccessor->GetNative().Get<cpSpace*>();
349
350     CreateBounds(space, newSize);
351   }
352
353   bool OnTouched(Dali::Actor actor, const Dali::TouchEvent& touch)
354   {
355     mApplication.Quit();
356     return false;
357   }
358
359   void OnKeyEv(const Dali::KeyEvent& event)
360   {
361     if(event.GetState() == KeyEvent::DOWN)
362     {
363       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
364       {
365         mApplication.Quit();
366       }
367     }
368   }
369
370 private:
371   Application& mApplication;
372   Window       mWindow;
373
374   PhysicsAdaptor            mPhysicsAdaptor;
375   std::vector<PhysicsActor> mBalls;
376   Matrix                    mPhysicsTransform;
377   Actor                     mPhysicsRoot;
378   Layer                     mPhysicsDebugLayer;
379   Layer                     mAnimationSimRootActor;
380   cpShape*                  mLeftBound{nullptr};
381   cpShape*                  mRightBound{nullptr};
382   cpShape*                  mTopBound{nullptr};
383   cpShape*                  mBottomBound{nullptr};
384
385   std::vector<Actor>   mBallActors;
386   std::vector<Vector3> mBallVelocity;
387   int                  mBallNumber;
388   Animation            mBallAnimation;
389   Timer                mTimer;
390 };
391
392 int DALI_EXPORT_API main(int argc, char** argv)
393 {
394   setenv("DALI_FPS_TRACKING", "5", 1);
395   Application application = Application::New(&argc, &argv);
396
397   int numberOfBalls = DEFAULT_BALL_COUNT;
398   if(argc > 1)
399   {
400     numberOfBalls = atoi(argv[1]);
401   }
402
403   Physics2dBenchmarkController controller(application, numberOfBalls);
404   application.MainLoop();
405   return 0;
406 }