2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include <dali-physics/dali-physics.h>
18 #include <dali-toolkit/dali-toolkit.h>
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>
26 #include <chipmunk/chipmunk.h>
32 using namespace Dali::Toolkit::Physics;
33 using namespace Dali::ParentOrigin;
34 using namespace Dali::AnchorPoint;
36 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gPhysicsDemo = Debug::Filter::New(Debug::Concise, false, "LOG_PHYSICS_EXAMPLE");
40 const float MAX_ANIMATION_DURATION{60.0f};
41 const uint32_t ANIMATION_TIME{30000};
42 const uint32_t DEFAULT_BALL_COUNT{500};
44 #if defined(_ARCH_ARM_)
45 #define DEMO_ICON_DIR "/usr/share/icons"
47 #define DEMO_ICON_DIR DEMO_IMAGE_DIR
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"};
55 const Vector2 BALL_SIZE{26.0f, 26.0f};
57 // Groups that can collide with each other:
58 const cpGroup BALL_GROUP{1 << 0};
59 const cpGroup BOUNDS_GROUP{1 << 1};
61 const cpBitmask COLLISION_MASK{0xfF};
63 const cpBitmask BALL_COLLIDES_WITH{BALL_GROUP | BOUNDS_GROUP};
66 * @brief The physics demo using Chipmunk2D APIs.
68 class Physics2dBenchmarkController : public ConnectionTracker
71 Physics2dBenchmarkController(Application& app, int numberOfBalls)
73 mBallNumber(numberOfBalls)
75 app.InitSignal().Connect(this, &Physics2dBenchmarkController::OnInit);
76 app.TerminateSignal().Connect(this, &Physics2dBenchmarkController::OnTerminate);
79 ~Physics2dBenchmarkController() = default;
81 void OnInit(Application& application)
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);
89 CreateAnimationSimulation();
91 mTimer = Timer::New(ANIMATION_TIME);
92 mTimer.TickSignal().Connect(this, &Physics2dBenchmarkController::AnimationSimFinished);
96 void CreateAnimationSimulation()
98 Window::WindowSize windowSize = mWindow.GetSize();
99 mBallActors.resize(mBallNumber);
100 mBallVelocity.resize(mBallNumber);
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;
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);
118 const float margin(BALL_SIZE.width * 0.5f);
120 for(int i = 0; i < mBallNumber; ++i)
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;
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);
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);
138 PropertyNotification leftNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_X, LessThanCondition(margin - width));
139 leftNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitLeftWall);
141 PropertyNotification rightNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_X, GreaterThanCondition(width - margin));
142 rightNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitRightWall);
144 PropertyNotification topNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_Y, LessThanCondition(margin - height));
145 topNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitTopWall);
147 PropertyNotification bottomNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_Y, GreaterThanCondition(height - margin));
148 bottomNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitBottomWall);
155 bool AnimationSimFinished()
157 static bool first = true;
160 UnparentAndReset(mAnimationSimRootActor);
161 mBallAnimation.Stop();
162 mBallAnimation.Clear();
165 CreatePhysicsSimulation();
173 void ContinueAnimation()
177 mBallAnimation.Clear();
179 mBallAnimation = Animation::New(MAX_ANIMATION_DURATION);
180 for(int i = 0; i < mBallNumber; ++i)
182 mBallAnimation.AnimateBy(Property(mBallActors[i], Actor::Property::POSITION), mBallVelocity[i] * MAX_ANIMATION_DURATION);
184 mBallAnimation.Play();
187 void OnHitLeftWall(PropertyNotification& source)
189 auto actor = Actor::DownCast(source.GetTarget());
192 int index = actor["index"];
193 mBallVelocity[index].x = fabsf(mBallVelocity[index].x);
198 void OnHitRightWall(PropertyNotification& source)
200 auto actor = Actor::DownCast(source.GetTarget());
203 int index = actor["index"];
204 mBallVelocity[index].x = -fabsf(mBallVelocity[index].x);
209 void OnHitBottomWall(PropertyNotification& source)
211 auto actor = Actor::DownCast(source.GetTarget());
214 int index = actor["index"];
215 mBallVelocity[index].y = -fabsf(mBallVelocity[index].y);
220 void OnHitTopWall(PropertyNotification& source)
222 auto actor = Actor::DownCast(source.GetTarget());
225 int index = actor["index"];
226 mBallVelocity[index].y = fabsf(mBallVelocity[index].y);
231 void CreatePhysicsSimulation()
233 Window::WindowSize windowSize = mWindow.GetSize();
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,
242 mPhysicsAdaptor = PhysicsAdaptor::New(mPhysicsTransform, windowSize);
243 mPhysicsRoot = mPhysicsAdaptor.GetRootActor();
244 mWindow.Add(mPhysicsRoot);
246 auto scopedAccessor = mPhysicsAdaptor.GetPhysicsAccessor();
247 cpSpace* space = scopedAccessor->GetNative().Get<cpSpace*>();
248 cpSpaceSetGravity(space, cpv(0, 0));
250 CreateBounds(space, windowSize);
252 for(int i = 0; i < mBallNumber; ++i)
254 mBalls.push_back(CreateBall(space));
257 // Process any async queued methods next frame
258 mPhysicsAdaptor.CreateSyncPoint();
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);
272 PhysicsActor CreateBall(cpSpace* space)
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;
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)));
284 cpShape* shape = cpSpaceAddShape(space, cpCircleShapeNew(body, BALL_RADIUS, cpvzero));
285 cpShapeSetElasticity(shape, BALL_ELASTICITY);
286 cpShapeSetFriction(shape, BALL_FRICTION);
288 PhysicsActor physicsBall = mPhysicsAdaptor.AddActorBody(ball, body);
290 Window::WindowSize windowSize = mWindow.GetSize();
292 const float fw = 0.5f * (windowSize.GetWidth() - BALL_RADIUS);
293 const float fh = 0.5f * (windowSize.GetHeight() - BALL_RADIUS);
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));
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)));
305 void CreateBounds(cpSpace* space, Window::WindowSize size)
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()));
311 cpBody* staticBody = cpSpaceGetStaticBody(space);
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);
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));
330 cpShape* AddBound(cpSpace* space, cpBody* staticBody, cpVect start, cpVect end)
332 cpShape* shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, start, end, 0.0f));
333 cpShapeSetElasticity(shape, 1.0f);
334 cpShapeSetFriction(shape, 1.0f);
336 cpShapeSetFilter(shape, cpShapeFilterNew(BOUNDS_GROUP, COLLISION_MASK, COLLISION_MASK));
340 void OnTerminate(Application& application)
342 UnparentAndReset(mPhysicsRoot);
345 void OnWindowResize(Window window, Window::WindowSize newSize)
347 auto scopedAccessor = mPhysicsAdaptor.GetPhysicsAccessor();
348 cpSpace* space = scopedAccessor->GetNative().Get<cpSpace*>();
350 CreateBounds(space, newSize);
353 bool OnTouched(Dali::Actor actor, const Dali::TouchEvent& touch)
359 void OnKeyEv(const Dali::KeyEvent& event)
361 if(event.GetState() == KeyEvent::DOWN)
363 if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
371 Application& mApplication;
374 PhysicsAdaptor mPhysicsAdaptor;
375 std::vector<PhysicsActor> mBalls;
376 Matrix mPhysicsTransform;
378 Layer mPhysicsDebugLayer;
379 Layer mAnimationSimRootActor;
380 cpShape* mLeftBound{nullptr};
381 cpShape* mRightBound{nullptr};
382 cpShape* mTopBound{nullptr};
383 cpShape* mBottomBound{nullptr};
385 std::vector<Actor> mBallActors;
386 std::vector<Vector3> mBallVelocity;
388 Animation mBallAnimation;
392 int DALI_EXPORT_API main(int argc, char** argv)
394 setenv("DALI_FPS_TRACKING", "5", 1);
395 Application application = Application::New(&argc, &argv);
397 int numberOfBalls = DEFAULT_BALL_COUNT;
400 numberOfBalls = atoi(argv[1]);
403 Physics2dBenchmarkController controller(application, numberOfBalls);
404 application.MainLoop();