/* * Copyright (c) 2023 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include using namespace Dali; using namespace Dali::Toolkit::Physics; using namespace Dali::ParentOrigin; using namespace Dali::AnchorPoint; #if defined(DEBUG_ENABLED) Debug::Filter* gPhysicsDemo = Debug::Filter::New(Debug::Concise, false, "LOG_PHYSICS_EXAMPLE"); #endif const float MAX_ANIMATION_DURATION{60.0f}; const uint32_t ANIMATION_TIME{30000}; const uint32_t DEFAULT_BALL_COUNT{500}; #if defined(_ARCH_ARM_) #define DEMO_ICON_DIR "/usr/share/icons" #else #define DEMO_ICON_DIR DEMO_IMAGE_DIR #endif const std::string BALL_IMAGES[] = {DEMO_IMAGE_DIR "/blocks-ball.png", DEMO_ICON_DIR "/dali-tests.png", DEMO_ICON_DIR "/dali-examples.png", DEMO_ICON_DIR "/com.samsung.dali-demo.png"}; const Vector2 BALL_SIZE{26.0f, 26.0f}; // Groups that can collide with each other: const cpGroup BALL_GROUP{1 << 0}; const cpGroup BOUNDS_GROUP{1 << 1}; const cpBitmask COLLISION_MASK{0xfF}; const cpBitmask BALL_COLLIDES_WITH{BALL_GROUP | BOUNDS_GROUP}; enum BenchmarkType { ANIMATION, PHYSICS_2D, }; /** * @brief The physics demo using Chipmunk2D APIs. */ class Physics2dBenchmarkController : public ConnectionTracker { public: Physics2dBenchmarkController(Application& app, int numberOfBalls) : mApplication(app), mBallNumber(numberOfBalls) { app.InitSignal().Connect(this, &Physics2dBenchmarkController::OnInit); app.TerminateSignal().Connect(this, &Physics2dBenchmarkController::OnTerminate); } ~Physics2dBenchmarkController() = default; void OnInit(Application& application) { mWindow = application.GetWindow(); mWindow.ResizeSignal().Connect(this, &Physics2dBenchmarkController::OnWindowResize); mWindow.KeyEventSignal().Connect(this, &Physics2dBenchmarkController::OnKeyEv); mWindow.GetRootLayer().TouchedSignal().Connect(this, &Physics2dBenchmarkController::OnTouched); mWindow.SetBackgroundColor(Color::DARK_SLATE_GRAY); mType = BenchmarkType::ANIMATION; CreateSimulation(); mTimer = Timer::New(ANIMATION_TIME); mTimer.TickSignal().Connect(this, &Physics2dBenchmarkController::AnimationSimFinished); mTimer.Start(); } void CreateSimulation() { switch(mType) { case BenchmarkType::ANIMATION: default: { CreateAnimationSimulation(); break; } case BenchmarkType::PHYSICS_2D: { CreatePhysicsSimulation(); break; } } } void CreateAnimationSimulation() { Window::WindowSize windowSize = mWindow.GetSize(); mBallActors.resize(mBallNumber); mBallVelocity.resize(mBallNumber); mAnimationSimRootActor = Layer::New(); mAnimationSimRootActor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS); mAnimationSimRootActor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER; mAnimationSimRootActor[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; mWindow.Add(mAnimationSimRootActor); std::ostringstream oss; oss << "Animation simulation of " << mBallNumber << " balls"; auto title = Toolkit::TextLabel::New(oss.str()); mAnimationSimRootActor.Add(title); title[Toolkit::TextLabel::Property::TEXT_COLOR] = Color::WHITE; title[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::TOP_CENTER; title[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::TOP_CENTER; title[Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT] = HorizontalAlignment::CENTER; title.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS); const float margin(BALL_SIZE.width * 0.5f); for(int i = 0; i < mBallNumber; ++i) { Actor ball = mBallActors[i] = Toolkit::ImageView::New(BALL_IMAGES[rand() % 4]); ball[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER; ball[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; ball[Actor::Property::NAME] = "Ball"; ball[Actor::Property::SIZE] = BALL_SIZE; // Halve the image size int width = windowSize.GetWidth() / 2; int height = windowSize.GetHeight() / 2; ball[Actor::Property::POSITION] = Vector3(Random::Range(margin - width, width - margin), Random::Range(margin - height, height - margin), 0.0f); ball.RegisterProperty("index", i); mAnimationSimRootActor.Add(ball); mBallVelocity[i] = Vector3(Random::Range(-25.0f, 25.0f), Random::Range(-25.0f, 25.0f), 0.0f); mBallVelocity[i].Normalize(); mBallVelocity[i] = mBallVelocity[i] * Random::Range(15.0f, 50.0f); PropertyNotification leftNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_X, LessThanCondition(margin - width)); leftNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitLeftWall); PropertyNotification rightNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_X, GreaterThanCondition(width - margin)); rightNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitRightWall); PropertyNotification topNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_Y, LessThanCondition(margin - height)); topNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitTopWall); PropertyNotification bottomNotify = mBallActors[i].AddPropertyNotification(Actor::Property::POSITION_Y, GreaterThanCondition(height - margin)); bottomNotify.NotifySignal().Connect(this, &Physics2dBenchmarkController::OnHitBottomWall); } title.RaiseToTop(); ContinueAnimation(); } bool AnimationSimFinished() { switch(mType) { case BenchmarkType::ANIMATION: default: { UnparentAndReset(mAnimationSimRootActor); mBallAnimation.Stop(); mBallAnimation.Clear(); mType = BenchmarkType::PHYSICS_2D; CreateSimulation(); return true; } case BenchmarkType::PHYSICS_2D: { mApplication.Quit(); break; } } return false; } void ContinueAnimation() { if(mBallAnimation) { mBallAnimation.Clear(); } mBallAnimation = Animation::New(MAX_ANIMATION_DURATION); for(int i = 0; i < mBallNumber; ++i) { mBallAnimation.AnimateBy(Property(mBallActors[i], Actor::Property::POSITION), mBallVelocity[i] * MAX_ANIMATION_DURATION); } mBallAnimation.Play(); } void OnHitLeftWall(PropertyNotification& source) { auto actor = Actor::DownCast(source.GetTarget()); if(actor) { int index = actor["index"]; mBallVelocity[index].x = fabsf(mBallVelocity[index].x); ContinueAnimation(); } } void OnHitRightWall(PropertyNotification& source) { auto actor = Actor::DownCast(source.GetTarget()); if(actor) { int index = actor["index"]; mBallVelocity[index].x = -fabsf(mBallVelocity[index].x); ContinueAnimation(); } } void OnHitBottomWall(PropertyNotification& source) { auto actor = Actor::DownCast(source.GetTarget()); if(actor) { int index = actor["index"]; mBallVelocity[index].y = -fabsf(mBallVelocity[index].y); ContinueAnimation(); } } void OnHitTopWall(PropertyNotification& source) { auto actor = Actor::DownCast(source.GetTarget()); if(actor) { int index = actor["index"]; mBallVelocity[index].y = fabsf(mBallVelocity[index].y); ContinueAnimation(); } } void CreatePhysicsSimulation() { Window::WindowSize windowSize = mWindow.GetSize(); // Map Physics space (origin bottom left, +ve Y up) // to DALi space (origin center, +ve Y down) mPhysicsTransform.SetIdentityAndScale(Vector3(1.0f, -1.0f, 1.0f)); mPhysicsTransform.SetTranslation(Vector3(windowSize.GetWidth() * 0.5f, windowSize.GetHeight() * 0.5f, 0.0f)); mPhysicsAdaptor = PhysicsAdaptor::New(mPhysicsTransform, windowSize); mPhysicsRoot = mPhysicsAdaptor.GetRootActor(); mWindow.Add(mPhysicsRoot); auto scopedAccessor = mPhysicsAdaptor.GetPhysicsAccessor(); cpSpace* space = scopedAccessor->GetNative().Get(); cpSpaceSetGravity(space, cpv(0, 0)); CreateBounds(space, windowSize); for(int i = 0; i < mBallNumber; ++i) { mBalls.push_back(CreateBall(space)); } // Process any async queued methods next frame mPhysicsAdaptor.CreateSyncPoint(); std::ostringstream oss; oss << "Physics simulation of " << mBallNumber << " balls"; auto title = Toolkit::TextLabel::New(oss.str()); mPhysicsRoot.Add(title); title[Toolkit::TextLabel::Property::TEXT_COLOR] = Color::WHITE; title[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::TOP_CENTER; title[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::TOP_CENTER; title[Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT] = HorizontalAlignment::CENTER; title.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS); title.RaiseToTop(); } PhysicsActor CreateBall(cpSpace* space) { const float BALL_MASS = 10.0f; const float BALL_RADIUS = BALL_SIZE.x * 0.25f; const float BALL_ELASTICITY = 1.0f; const float BALL_FRICTION = 0.0f; auto ball = Toolkit::ImageView::New(BALL_IMAGES[rand() % 4]); ball[Actor::Property::NAME] = "Ball"; ball[Actor::Property::SIZE] = BALL_SIZE * 0.5f; cpBody* body = cpSpaceAddBody(space, cpBodyNew(BALL_MASS, cpMomentForCircle(BALL_MASS, 0.0f, BALL_RADIUS, cpvzero))); cpShape* shape = cpSpaceAddShape(space, cpCircleShapeNew(body, BALL_RADIUS, cpvzero)); cpShapeSetElasticity(shape, BALL_ELASTICITY); cpShapeSetFriction(shape, BALL_FRICTION); PhysicsActor physicsBall = mPhysicsAdaptor.AddActorBody(ball, body); Window::WindowSize windowSize = mWindow.GetSize(); const float fw = 0.5f * (windowSize.GetWidth() - BALL_RADIUS); const float fh = 0.5f * (windowSize.GetHeight() - BALL_RADIUS); // Example of setting physics property on update thread physicsBall.AsyncSetPhysicsPosition(Vector3(Random::Range(-fw, fw), Random::Range(-fh, -fh * 0.5), 0.0f)); // Example of queuing a chipmunk method to run on the update thread mPhysicsAdaptor.Queue([body]() { cpBodySetVelocity(body, cpv(Random::Range(-100.0, 100.0), Random::Range(-100.0, 100.0))); }); return physicsBall; } void CreateBounds(cpSpace* space, Window::WindowSize size) { // We're working in physics space here - coords are: origin: bottom left, +ve Y: up int32_t xBound = static_cast(static_cast(size.GetWidth())); int32_t yBound = static_cast(static_cast(size.GetHeight())); cpBody* staticBody = cpSpaceGetStaticBody(space); if(mLeftBound) { cpSpaceRemoveShape(space, mLeftBound); cpSpaceRemoveShape(space, mRightBound); cpSpaceRemoveShape(space, mTopBound); cpSpaceRemoveShape(space, mBottomBound); cpShapeFree(mLeftBound); cpShapeFree(mRightBound); cpShapeFree(mTopBound); cpShapeFree(mBottomBound); } mLeftBound = AddBound(space, staticBody, cpv(0, 0), cpv(0, yBound)); mRightBound = AddBound(space, staticBody, cpv(xBound, 0), cpv(xBound, yBound)); mTopBound = AddBound(space, staticBody, cpv(0, 0), cpv(xBound, 0)); mBottomBound = AddBound(space, staticBody, cpv(0, yBound), cpv(xBound, yBound)); } cpShape* AddBound(cpSpace* space, cpBody* staticBody, cpVect start, cpVect end) { cpShape* shape = cpSpaceAddShape(space, cpSegmentShapeNew(staticBody, start, end, 0.0f)); cpShapeSetElasticity(shape, 1.0f); cpShapeSetFriction(shape, 1.0f); cpShapeSetFilter(shape, cpShapeFilterNew(BOUNDS_GROUP, COLLISION_MASK, COLLISION_MASK)); return shape; } void OnTerminate(Application& application) { UnparentAndReset(mAnimationSimRootActor); UnparentAndReset(mPhysicsRoot); } void OnWindowResize(Window window, Window::WindowSize newSize) { switch(mType) { case BenchmarkType::ANIMATION: default: { // TODO : Implement here if you want. break; } case BenchmarkType::PHYSICS_2D: { if(mPhysicsAdaptor) { auto scopedAccessor = mPhysicsAdaptor.GetPhysicsAccessor(); cpSpace* space = scopedAccessor->GetNative().Get(); CreateBounds(space, newSize); } break; } } } bool OnTouched(Dali::Actor actor, const Dali::TouchEvent& touch) { mApplication.Quit(); return false; } void OnKeyEv(const Dali::KeyEvent& event) { if(event.GetState() == KeyEvent::DOWN) { if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK)) { mApplication.Quit(); } } } private: Application& mApplication; Window mWindow; BenchmarkType mType{BenchmarkType::ANIMATION}; PhysicsAdaptor mPhysicsAdaptor; std::vector mBalls; Matrix mPhysicsTransform; Actor mPhysicsRoot; Layer mPhysicsDebugLayer; Layer mAnimationSimRootActor; cpShape* mLeftBound{nullptr}; cpShape* mRightBound{nullptr}; cpShape* mTopBound{nullptr}; cpShape* mBottomBound{nullptr}; std::vector mBallActors; std::vector mBallVelocity; int mBallNumber; Animation mBallAnimation; Timer mTimer; }; int DALI_EXPORT_API main(int argc, char** argv) { setenv("DALI_FPS_TRACKING", "5", 1); Application application = Application::New(&argc, &argv); int numberOfBalls = DEFAULT_BALL_COUNT; if(argc > 1) { numberOfBalls = atoi(argv[1]); } Physics2dBenchmarkController controller(application, numberOfBalls); application.MainLoop(); return 0; }