diff --git a/build/tizen/CMakeLists.txt b/build/tizen/CMakeLists.txt index 2c6425f..5cb6012 100644 --- a/build/tizen/CMakeLists.txt +++ b/build/tizen/CMakeLists.txt @@ -243,6 +243,21 @@ IF( ENABLE_PKG_CONFIGURE ) SET( ENABLE_SCENE3D "ON" ) ENDIF() + pkg_check_modules(DALI_PHYSICS_2D dali2-physics-2d) + IF( DALI_PHYSICS_2D_FOUND ) + FOREACH(flag ${DALI_PHYSICS_2D_CFLAGS}) + SET(REQUIRED_CFLAGS "${REQUIRED_CFLAGS} ${flag}") + ENDFOREACH(flag) + + SET( REQUIRED_CFLAGS "${REQUIRED_CFLAGS} -DDALI_PHYSICS_2D_AVAILABLE" ) + + FOREACH(flag ${DALI_PHYSICS_2D_LDFLAGS}) + SET(REQUIRED_PKGS_LDFLAGS "${REQUIRED_PKGS_LDFLAGS} ${flag}") + ENDFOREACH(flag) + + SET( ENABLE_PHYSICS_2D "ON" ) + ENDIF() + # if build as tizen platform, use capi-appfw-app-control IF( TIZEN ) pkg_check_modules(CAPI_APPFW_APP_CONTROL capi-appfw-app-control) @@ -292,6 +307,8 @@ IF( WIN32 OR APPLE ) # WIN32 includes x64 as well according to the cmake doc. FIND_PACKAGE( dali2-scene3d ) + FIND_PACKAGE( chipmunk ) + # Set up the include dir SET( INCLUDE_DIR $ENV{includedir} ) IF( NOT INCLUDE_DIR ) @@ -346,6 +363,15 @@ IF( WIN32 OR APPLE ) # WIN32 includes x64 as well according to the cmake doc. ) SET( ENABLE_SCENE3D "ON" ) ENDIF() + + IF (chipmunk_FOUND) + SET(REQUIRED_LIBS + ${REQUIRED_LIBS} + -lchipmunk + ) + SET( ENABLE_PHYSICS_2D "ON" ) + ENDIF() + ELSEIF( UNIX ) SET( REQUIRED_LIBS ${REQUIRED_PKGS_LDFLAGS} @@ -374,6 +400,10 @@ IF( ENABLE_SCENE3D ) SET(DALI_DEMO_CFLAGS "${DALI_DEMO_CFLAGS} -DDALI_SCENE3D_AVAILABLE") ENDIF() +IF( ENABLE_PHYSICS_2D ) + SET(DALI_DEMO_CFLAGS "${DALI_DEMO_CFLAGS} -DDALI_PHYSICS_2D_AVAILABLE") +ENDIF() + IF( UNIX ) IF( NOT ${ENABLE_EXPORTALL} ) ADD_DEFINITIONS( "-DHIDE_DALI_INTERNALS" ) @@ -491,3 +521,4 @@ MESSAGE( " Folder DEMO_LANG : [" ${DEMO_LANG} "]" ) MESSAGE( " Current Build Platform : [" ${CURRENT_BUILD_PLATFORM} "]" ) MESSAGE( " Build example name : [" ${CURRENT_BUILD_EXAMPLE_NAME} "]" ) MESSAGE( " Scene3D Enabled : [" ${ENABLE_SCENE3D} "]" ) +MESSAGE( " Physics 2D Enabled : [" ${ENABLE_PHYSICS_2D} "]" ) diff --git a/build/tizen/examples/CMakeLists.txt b/build/tizen/examples/CMakeLists.txt index 05ebbdf..6a458b3 100644 --- a/build/tizen/examples/CMakeLists.txt +++ b/build/tizen/examples/CMakeLists.txt @@ -20,6 +20,13 @@ IF (NOT "${ENABLE_SCENE3D}" ) ENDIF() ENDIF() +SET(PHYSICS_2D_DIR "chipmunk") +IF (NOT "${ENABLE_PHYSICS_2D}" ) + IF ( ${PHYSICS_2D_DIR} IN_LIST SUBDIRS ) + LIST( REMOVE_ITEM SUBDIRS ${PHYSICS_2D_DIR} ) + ENDIF() +ENDIF() + FIND_PROGRAM( SHADER_GENERATOR "dali-shader-generator" ) IF( NOT SHADER_GENERATOR ) MESSAGE( FATAL_ERROR "dali-shader-generator not found!" ) diff --git a/com.samsung.dali-demo.xml b/com.samsung.dali-demo.xml index 4d2e012..4c12d91 100644 --- a/com.samsung.dali-demo.xml +++ b/com.samsung.dali-demo.xml @@ -67,6 +67,9 @@ + + + diff --git a/examples-reel/dali-examples-reel.cpp b/examples-reel/dali-examples-reel.cpp index b44ab48..a697899 100644 --- a/examples-reel/dali-examples-reel.cpp +++ b/examples-reel/dali-examples-reel.cpp @@ -47,6 +47,7 @@ int DALI_EXPORT_API main(int argc, char** argv) demo.AddExample(Example("builder.example", DALI_DEMO_STR_TITLE_SCRIPT_BASED_UI)); demo.AddExample(Example("buttons.example", DALI_DEMO_STR_TITLE_BUTTONS)); demo.AddExample(Example("canvas-view.example", DALI_DEMO_STR_TITLE_CANVAS_VIEW)); + demo.AddExample(Example("chipmunk-physics.example", DALI_DEMO_STR_TITLE_CHIPMUNK_PHYSICS)); demo.AddExample(Example("clipping.example", DALI_DEMO_STR_TITLE_CLIPPING)); demo.AddExample(Example("clipping-draw-order.example", DALI_DEMO_STR_TITLE_CLIPPING_DRAW_ORDER)); demo.AddExample(Example("color-transition.example", DALI_DEMO_STR_TITLE_COLOR_TRANSITION)); diff --git a/examples/chipmunk-physics/frame-callback.cpp b/examples/chipmunk-physics/frame-callback.cpp new file mode 100644 index 0000000..c6ef8a3 --- /dev/null +++ b/examples/chipmunk-physics/frame-callback.cpp @@ -0,0 +1,52 @@ +/* + * 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 "frame-callback.h" +#include "physics-impl.h" +#include +#include +#include + +using Dali::Vector3; +using Dali::Quaternion; + +FrameCallback::FrameCallback(PhysicsImpl& physicsImpl) +: mPhysicsImpl(physicsImpl) +{ +} + +bool FrameCallback::Update(Dali::UpdateProxy& updateProxy, float elapsedSeconds) +{ + Dali::Mutex::ScopedLock lock(mPhysicsImpl.mMutex); + static float frameTime=0; + frameTime+=elapsedSeconds; + do + { + mPhysicsImpl.Integrate(mPhysicsTimeStep); + frameTime-=mPhysicsTimeStep; + } while (frameTime>0); + + for(auto&& actor : mPhysicsImpl.mPhysicsActors) + { + // Get position, orientation from physics world. + Vector3 position = actor.second.GetActorPosition(); + updateProxy.BakePosition(actor.first, position); + Quaternion rotation = actor.second.GetActorRotation(); + updateProxy.BakeOrientation(actor.first, rotation); + } + + return true; +} diff --git a/examples/chipmunk-physics/frame-callback.h b/examples/chipmunk-physics/frame-callback.h new file mode 100644 index 0000000..c45f019 --- /dev/null +++ b/examples/chipmunk-physics/frame-callback.h @@ -0,0 +1,58 @@ +#ifndef PHYSICS_DEMO_FRAME_CALLBACK_H +#define PHYSICS_DEMO_FRAME_CALLBACK_H + +/* + * 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 + +class PhysicsImpl; + +class FrameCallback : public Dali::FrameCallbackInterface +{ +public: + /** + * Constructor + */ + explicit FrameCallback(PhysicsImpl& physicsImpl); + + /** + * Set the physics time step + * @param timeStep (in seconds) + */ + void SetPhysicsTimeStep(float timeStep) + { + mPhysicsTimeStep = timeStep; + } + +private: + /** + * Called each frame. + * @param[in] updateProxy Used to set world matrix and size + * @param[in] elapsedSeconds Time since last frame + * @return Whether we should keep rendering. + */ + bool Update(Dali::UpdateProxy& updateProxy, float elapsedSeconds) override; + +private: // Member variables + PhysicsImpl& mPhysicsImpl; + float mPhysicsTimeStep{1.0/180.0}; +}; + +#endif //PHYSICS_DEMO_FRAME_CALLBACK_H diff --git a/examples/chipmunk-physics/physics-actor.cpp b/examples/chipmunk-physics/physics-actor.cpp new file mode 100644 index 0000000..07f14a3 --- /dev/null +++ b/examples/chipmunk-physics/physics-actor.cpp @@ -0,0 +1,98 @@ +/* + * 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 "physics-actor.h" +#include "physics-impl.h" +#include +#include +#include + +using Dali::Vector3; +using Dali::Quaternion; +using Dali::Radian; + +void PhysicsActor::ClearForces() +{ + printf("Not Implemented\n"); + //mBody->clearForces(); + // No similar API +} + +Dali::Vector3 PhysicsActor::GetPhysicsPosition() +{ + cpVect cpPosition = cpBodyGetPosition(mBody); + return Vector3(cpPosition.x, cpPosition.y, 0.0f); +} + +void PhysicsActor::SetPhysicsPosition(Dali::Vector3 actorPosition) +{ + Dali::Mutex::ScopedLock lock(mImpl->mMutex); + Vector3 physicsPosition = mImpl->TranslateToPhysicsSpace(actorPosition); + cpBodySetPosition(mBody, cpv(physicsPosition.x, physicsPosition.y)); +} + +void PhysicsActor::SetPhysicsVelocity(Dali::Vector3 actorVelocity) +{ + Dali::Mutex::ScopedLock lock(mImpl->mMutex); + Vector3 physicsVelocity = mImpl->ConvertVectorToPhysicsSpace(actorVelocity); + cpBodySetVelocity(mBody, cpv(physicsVelocity.x, physicsVelocity.y)); +} + +void PhysicsActor::SetPhysicsAngularVelocity(Dali::Vector3 velocity) +{ + Dali::Mutex::ScopedLock lock(mImpl->mMutex); + printf("Not Implemented\n"); + //mBody->setAngularVelocity(btVector3(velocity.x, velocity.y, velocity.z)); +} + +Quaternion PhysicsActor::GetPhysicsRotation() +{ + return Quaternion{}; +} + +void PhysicsActor::SetPhysicsRotation(Dali::Quaternion rotation) +{ + Dali::Mutex::ScopedLock lock(mImpl->mMutex); + + Vector3 axis; + Radian angle; + rotation.ToAxisAngle(axis, angle); + + //btQuaternion orn = btQuaternion(btVector3(axis.x, -axis.y, axis.z), btScalar(float(-angle))); + //btTransform& transform = mBody->getWorldTransform(); + //transform.setRotation(orn); + printf("Not Implemented\n"); +} + + +Vector3 PhysicsActor::GetActorPosition() +{ + cpVect cpPosition = cpBodyGetPosition(mBody); + return mImpl->TranslateFromPhysicsSpace(Vector3(cpPosition.x, cpPosition.y, 0.0f)); +} + +Vector3 PhysicsActor::GetActorVelocity() +{ + cpVect cpVelocity = cpBodyGetVelocity(mBody); + return mImpl->ConvertVectorFromPhysicsSpace(Vector3(cpVelocity.x, cpVelocity.y, 0.0f)); +} + +Quaternion PhysicsActor::GetActorRotation() +{ + cpFloat angle = cpBodyGetAngle(mBody); + return Quaternion(Radian(angle), -Vector3::ZAXIS); +} diff --git a/examples/chipmunk-physics/physics-actor.h b/examples/chipmunk-physics/physics-actor.h new file mode 100644 index 0000000..aeaa612 --- /dev/null +++ b/examples/chipmunk-physics/physics-actor.h @@ -0,0 +1,106 @@ +#ifndef DALI_PHYSICS_DEMO_PHYSICS_ACTOR_H +#define DALI_PHYSICS_DEMO_PHYSICS_ACTOR_H +/* + * 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 + +// Forward declarations +class PhysicsImpl; + +/** + * Class that associates an actor with a physics body. (Initially, rigid body) + */ +class PhysicsActor +{ +public: + PhysicsActor() = default; + PhysicsActor(Dali::Actor& actor, cpBody* body, PhysicsImpl* impl, Dali::Property::Index brightnessId) + : mImpl(impl), + mActorId(actor.GetProperty(Dali::Actor::Property::ID)), + mBody(body), + mBrightnessIndex(brightnessId) + { + cpBodySetUserData(mBody, this); + } + + PhysicsActor(const PhysicsActor& rhs)=delete; + PhysicsActor& operator=(const PhysicsActor& rhs)=delete; + + PhysicsActor(const PhysicsActor&& rhs) + { + if(this != &rhs) + { + mImpl = rhs.mImpl; + mActorId = rhs.mActorId; + mBody = rhs.mBody; + cpBodySetUserData(mBody, this); + mBrightnessIndex = rhs.mBrightnessIndex; + } + } + + PhysicsActor& operator=(const PhysicsActor&& rhs) + { + if(this != &rhs) + { + mActorId = rhs.mActorId; + mBody = rhs.mBody; + mImpl = rhs.mImpl; + mBrightnessIndex = rhs.mBrightnessIndex; + cpBodySetUserData(mBody, this); + } + return *this; + } + + uint32_t GetId() + { + return mActorId; + } + + cpBody* GetBody() + { + return mBody; + } + + Dali::Property::Index GetBrightnessIndex() + { + return mBrightnessIndex; + } + + Dali::Vector3 GetPhysicsPosition(); + Dali::Quaternion GetPhysicsRotation(); + + void SetPhysicsPosition(Dali::Vector3 actorPosition); + void SetPhysicsVelocity(Dali::Vector3 actorVelocity); + void SetPhysicsAngularVelocity(Dali::Vector3 actorVelocity); + void SetPhysicsRotation(Dali::Quaternion actorRotation); + Dali::Vector3 GetActorPosition(); + Dali::Vector3 GetActorVelocity(); + Dali::Quaternion GetActorRotation(); + void ClearForces(); + +private: + PhysicsImpl* mImpl{nullptr}; + uint32_t mActorId{0}; + cpBody* mBody{nullptr}; + Dali::Property::Index mBrightnessIndex{Dali::Property::INVALID_INDEX}; +}; + +#endif // DALI_PHYSICS_DEMO_PHYSICS_ACTOR_H diff --git a/examples/chipmunk-physics/physics-demo-controller.cpp b/examples/chipmunk-physics/physics-demo-controller.cpp new file mode 100644 index 0000000..2faa3f8 --- /dev/null +++ b/examples/chipmunk-physics/physics-demo-controller.cpp @@ -0,0 +1,359 @@ +/* + * 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 "generated/rendering-textured-shape-frag.h" +#include "generated/rendering-textured-shape-vert.h" +#include "physics-actor.h" +#include "physics-impl.h" + +using namespace Dali; + +namespace KeyModifier +{ +enum Key +{ + CONTROL_L = DevelKey::DALI_KEY_CONTROL_LEFT, + CONTROL_R = DevelKey::DALI_KEY_CONTROL_RIGHT, + SHIFT_L = 50, + SHIFT_R = 62, + ALT_L = 64, + ALT_R = 108, + SUPER_L = 133, + SUPER_R = 134, + MENU = 135, +}; +} + +const std::string BRICK_WALL = DEMO_IMAGE_DIR "/brick-wall.jpg"; +const std::string BALL_IMAGE = DEMO_IMAGE_DIR "/blocks-ball.png"; +const std::string BRICK_URIS[4] = { + DEMO_IMAGE_DIR "/blocks-brick-1.png", DEMO_IMAGE_DIR "/blocks-brick-2.png", DEMO_IMAGE_DIR "/blocks-brick-3.png", DEMO_IMAGE_DIR "/blocks-brick-4.png"}; + +/** + * @brief The physics demo using Chipmunk2D APIs. + */ +class PhysicsDemoController : public ConnectionTracker +{ +public: + PhysicsDemoController(Application& app) + : mApplication(app) + { + app.InitSignal().Connect(this, &PhysicsDemoController::OnInit); + app.TerminateSignal().Connect(this, &PhysicsDemoController::OnTerminate); + } + + ~PhysicsDemoController() override + { + } + + void OnInit(Application& application) + { + mWindow = application.GetWindow(); + mWindow.ResizeSignal().Connect(this, &PhysicsDemoController::OnWindowResize); + mWindow.KeyEventSignal().Connect(this, &PhysicsDemoController::OnKeyEv); + Stage::GetCurrent().KeepRendering(30); + mWindow.SetBackgroundColor(Color::DARK_SLATE_GRAY); + Window::WindowSize windowSize = mWindow.GetSize(); + + mPhysicsRoot = mPhysicsImpl.Initialize(mWindow); + mPhysicsRoot.TouchedSignal().Connect(this, &PhysicsDemoController::OnTouched); + + mWindow.Add(mPhysicsRoot); + + CreateBall(); + CreateBrickPyramid(windowSize); + + // For funky mouse drag + mMouseBody = mPhysicsImpl.AddMouseBody(); + } + + void CreateBall() + { + const float BALL_MASS = 10.0f; + const float BALL_RADIUS = 26.0f; + const float BALL_ELASTICITY = 0.5f; + const float BALL_FRICTION = 0.5f; + + Property::Value v{std::string{SHADER_RENDERING_TEXTURED_SHAPE_VERT}}; + Property::Value f{std::string{SHADER_RENDERING_TEXTURED_SHAPE_FRAG}}; + + auto image = Property::Map{{Toolkit::Visual::Property::TYPE, Toolkit::Visual::IMAGE}, + {Toolkit::ImageVisual::Property::URL, BALL_IMAGE}, + {Toolkit::Visual::Property::SHADER, {{Toolkit::Visual::Shader::Property::FRAGMENT_SHADER, f}}}}; + + auto ball = Toolkit::ImageView::New(); + ball[Toolkit::ImageView::Property::IMAGE] = image; + mPhysicsImpl.AddBall(ball, BALL_MASS, BALL_RADIUS, BALL_ELASTICITY, BALL_FRICTION); + } + + void CreateBrickPyramid(Dali::Window::WindowSize windowSize) + { + const float BRICK_MASS = 30.0f; + const float BRICK_ELASTICITY = 0.0f; + const float BRICK_FRICTION = 0.9f; + const int BRICK_WIDTH = 128; + const int BRICK_HEIGHT = 64; + const int BRICK_GAP = 8; + + Property::Value v{std::string{SHADER_RENDERING_TEXTURED_SHAPE_VERT}}; + Property::Value f{std::string{SHADER_RENDERING_TEXTURED_SHAPE_FRAG}}; + + int uriIndex = 0; + int numberOfRows = windowSize.GetWidth() / (BRICK_WIDTH + BRICK_GAP) - 2; + int oY = windowSize.GetHeight() - (1 + numberOfRows) * BRICK_HEIGHT; + for(int i = 0; i < numberOfRows; ++i) + { + // Row start: i+1 is brick number. i is gap# + int w = (i + 1) * BRICK_WIDTH + i * BRICK_GAP; + int oX = (windowSize.GetWidth() - w) / 2; + for(int j = 0; j < i + 1; ++j) + { + auto image = Property::Map{{Toolkit::Visual::Property::TYPE, Toolkit::Visual::IMAGE}, + {Toolkit::ImageVisual::Property::URL, BRICK_URIS[uriIndex]}, + {Toolkit::Visual::Property::SHADER, {{Toolkit::Visual::Shader::Property::FRAGMENT_SHADER, f}}}}; + + auto brick = Toolkit::ImageView::New(); + brick[Toolkit::ImageView::Property::IMAGE] = image; + auto& physicsActor = mPhysicsImpl.AddBrick(brick, BRICK_MASS, BRICK_ELASTICITY, BRICK_FRICTION, Vector3(BRICK_WIDTH, BRICK_HEIGHT, BRICK_HEIGHT)); + physicsActor.SetPhysicsPosition(Vector3(oX + j * (BRICK_WIDTH + BRICK_GAP), oY + i * BRICK_HEIGHT, 0.0f)); + uriIndex += 1; + uriIndex %= 4; + } + } + } + void OnTerminate(Application& application) + { + UnparentAndReset(mPhysicsRoot); + } + + void OnWindowResize(Window window, Window::WindowSize newSize) + { + mPhysicsImpl.CreateWorldBounds(newSize); + } + + bool OnTouched(Dali::Actor actor, const Dali::TouchEvent& touch) + { + static enum { + None, + MoveCameraXZ, + MovePivot, + } state = None; + + auto renderTask = mWindow.GetRenderTaskList().GetTask(0); + auto screenCoords = touch.GetScreenPosition(0); + Vector3 origin, direction; + Dali::HitTestAlgorithm::BuildPickingRay(renderTask, screenCoords, origin, direction); + + switch(state) + { + case None: + { + if(touch.GetState(0) == Dali::PointState::STARTED) + { + if(mCtrlDown) + { + state = MoveCameraXZ; + // local to top left + //cameraY = touch.GetLocalPosition(0).y; + // Could move on fixed plane, e.g. y=0. + // position.Y corresponds to a z value depending on perspective + // position.X scales to an x value depending on perspective + } + else + { + state = MovePivot; + Dali::Mutex::ScopedLock lock(mPhysicsImpl.mMutex); + + Vector3 localPivot; + float pickingDistance; + auto body = mPhysicsImpl.HitTest(screenCoords, origin, direction, localPivot, pickingDistance); + if(body) + { + mPickedBody = body; + mPhysicsImpl.HighlightBody(mPickedBody, true); + mPickedSavedState = mPhysicsImpl.ActivateBody(mPickedBody); + mPickedConstraint = mPhysicsImpl.AddPivotJoint(mPickedBody, mMouseBody, localPivot); + } + } + } + break; + } + case MovePivot: + { + if(touch.GetState(0) == Dali::PointState::MOTION) + { + if(mPickedBody && mPickedConstraint) + { + if(!mShiftDown) + { + // Move point in XY plane, projected into scene + Dali::Mutex::ScopedLock lock(mPhysicsImpl.mMutex); + + Vector3 position = mPhysicsImpl.TranslateToPhysicsSpace(Vector3(screenCoords)); + mPhysicsImpl.MoveMouseBody(mMouseBody, position); + } + else + { + // Move point in XZ plane + // Above vanishing pt, it's on top plane of frustum; below vanishing pt it's on bottom plane. + // Kind of want to project onto the plane using initial touch xy, rather than top/bottom. + // Whole new projection code needed. + + // Cheat! + } + } + } + else if(touch.GetState(0) == Dali::PointState::FINISHED || + touch.GetState(0) == Dali::PointState::INTERRUPTED) + { + if(mPickedConstraint) + { + mPhysicsImpl.HighlightBody(mPickedBody, false); + + Dali::Mutex::ScopedLock lock(mPhysicsImpl.mMutex); + mPhysicsImpl.RestoreBodyState(mPickedBody, mPickedSavedState); + mPhysicsImpl.ReleaseConstraint(mPickedConstraint); + mPickedConstraint = nullptr; + mPickedBody = nullptr; + } + state = None; + } + break; + } + case MoveCameraXZ: + { + if(touch.GetState(0) == Dali::PointState::MOTION) + { + // Move camera in XZ plane + //float y = cameraY; // touch point in Y. Move camera in an XZ plane on this point. + } + else if(touch.GetState(0) == Dali::PointState::FINISHED || + touch.GetState(0) == Dali::PointState::INTERRUPTED) + { + state = None; + } + break; + } + } + + //std::cout<<"Touch State: "< +#include +#include +#include + +using Dali::Layer; +using Dali::Actor; +using Dali::Window; +using Dali::Vector2; +using Dali::Vector3; +using Dali::Stage; +using namespace Dali::DevelStage; + +#define GRABBABLE_MASK_BIT (1u<<31) +cpShapeFilter GRAB_FILTER = {CP_NO_GROUP, GRABBABLE_MASK_BIT, GRABBABLE_MASK_BIT}; +cpShapeFilter NOT_GRABBABLE_FILTER = {CP_NO_GROUP, ~GRABBABLE_MASK_BIT, ~GRABBABLE_MASK_BIT}; + +Actor PhysicsImpl::Initialize(Window window) +{ + mWindow = window; + mSpace = cpSpaceNew(); + cpSpaceSetIterations(mSpace, 30); + cpSpaceSetSleepTimeThreshold(mSpace, 0.5f); + cpSpaceSetGravity(mSpace, cpv(0, -200)); + + auto windowSize = window.GetSize(); + CreateWorldBounds(windowSize); + + // Create an actor that can handle mouse events. + mPhysicsRoot = Layer::New(); + mPhysicsRoot[Actor::Property::SIZE] = Vector2(windowSize.GetWidth(), windowSize.GetHeight()); + mPhysicsRoot[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; + mPhysicsRoot[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER; + + mFrameCallback = new FrameCallback(*this); + AddFrameCallback(Stage::GetCurrent(), *mFrameCallback, window.GetRootLayer()); + Stage::GetCurrent().KeepRendering(30); + + + return mPhysicsRoot; +} + +Layer PhysicsImpl::CreateDebug(Vector2 windowSize) +{ + return Layer(); +} + +void PhysicsImpl::CreateWorldBounds(Window::WindowSize size) +{ + // Physics origin is 0,0,0 in DALi coords. + // But, Y is inverted, so bottom is -ve, top is +ve. + // Perform this correction when applying position to actor. + // But, can't use actors in update, so cache transform. + SetTransform(Vector2(size.GetWidth(), size.GetHeight())); + + int xBound=size.GetWidth()/2; + int yBound=size.GetHeight()/2; + + cpBody *staticBody = cpSpaceGetStaticBody(mSpace); + + if(mLeftBound) + { + cpSpaceRemoveShape(mSpace, mLeftBound); + cpSpaceRemoveShape(mSpace, mRightBound); + cpSpaceRemoveShape(mSpace, mTopBound); + cpSpaceRemoveShape(mSpace, mBottomBound); + cpShapeFree(mLeftBound); + cpShapeFree(mRightBound); + cpShapeFree(mTopBound); + cpShapeFree(mBottomBound); + } + mLeftBound = AddBound(staticBody, cpv(-xBound, -yBound), cpv(-xBound, yBound)); + mRightBound = AddBound(staticBody, cpv( xBound, -yBound), cpv( xBound, yBound)); + mTopBound = AddBound(staticBody, cpv(-xBound, -yBound), cpv( xBound, -yBound)); + mBottomBound = AddBound(staticBody, cpv(-xBound, yBound), cpv( xBound, yBound)); +} + +void PhysicsImpl::SetTransform(Vector2 worldSize) +{ + mWorldOffset.x = worldSize.x * 0.5f; + mWorldOffset.y = worldSize.y * 0.5f; + // y is always inverted. +} + +cpShape* PhysicsImpl::AddBound(cpBody* staticBody, cpVect start, cpVect end) +{ + cpShape* shape = cpSpaceAddShape(mSpace, cpSegmentShapeNew(staticBody,start, end,0.0f)); + cpShapeSetElasticity(shape, 1.0f); + cpShapeSetFriction(shape, 1.0f); + cpShapeSetFilter(shape, NOT_GRABBABLE_FILTER); + return shape; +} + +PhysicsActor& PhysicsImpl::AddBall(::Actor actor, float mass, float radius, float elasticity, float friction) +{ + Dali::Mutex::ScopedLock lock(mMutex); + cpBody* body = cpSpaceAddBody(mSpace, cpBodyNew(mass, cpMomentForCircle(mass, 0.0f, radius, cpvzero))); + cpBodySetPosition(body, cpv(0, 0)); + cpBodySetVelocity(body, cpv(0, 0)); + + cpShape* shape = cpSpaceAddShape(mSpace, cpCircleShapeNew(body, radius, cpvzero)); + cpShapeSetElasticity(shape, elasticity); + cpShapeSetFriction(shape, friction); + + int id = actor[Actor::Property::ID]; + Dali::Property::Index index = actor.RegisterProperty("uBrightness", 0.0f); + mPhysicsActors.insert(std::make_pair(id, PhysicsActor{actor, body, this, index})); + actor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::TOP_LEFT; + actor[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; + mPhysicsRoot.Add(actor); + return mPhysicsActors.at(id); +} + +PhysicsActor& PhysicsImpl::AddBrick(Dali::Actor actor, float mass, float elasticity, float friction, Vector3 size) +{ + Dali::Mutex::ScopedLock lock(mMutex); + cpBody* body = cpSpaceAddBody(mSpace, cpBodyNew(mass, cpMomentForBox(mass, size.width, size.height))); + cpBodySetPosition(body, cpv(0, 0)); + cpBodySetVelocity(body, cpv(0, 0)); + + cpShape* shape = cpSpaceAddShape(mSpace, cpBoxShapeNew(body, size.width, size.height, 0.0f)); + cpShapeSetFriction(shape, friction); + cpShapeSetElasticity(shape, elasticity); + + int id = actor[Actor::Property::ID]; + Dali::Property::Index index = actor.RegisterProperty("uBrightness", 0.0f); + mPhysicsActors.insert(std::make_pair(id, PhysicsActor{actor, body, this, index})); + actor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::TOP_LEFT; + actor[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; + mPhysicsRoot.Add(actor); + return mPhysicsActors.at(id); +} + +cpBody* PhysicsImpl::AddMouseBody() +{ + Dali::Mutex::ScopedLock lock(mMutex); + auto kinematicBody = cpBodyNewKinematic(); // Mouse actor is a kinematic body that is not integrated + return kinematicBody; +} + +PhysicsActor* PhysicsImpl::GetPhysicsActor(cpBody* body) +{ + return reinterpret_cast(cpBodyGetUserData(body)); +} + +void PhysicsImpl::HighlightBody(cpBody* body, bool highlight) +{ + auto physicsActor = GetPhysicsActor(body); + if(physicsActor) + { + Actor actor = mPhysicsRoot.FindChildById(physicsActor->GetId()); + if(actor) + { + actor[physicsActor->GetBrightnessIndex()] = highlight?1.0f:0.0f; + } + } +} + +// Convert from root actor local space to physics space +Vector3 PhysicsImpl::TranslateToPhysicsSpace(Vector3 vector) +{ + // root actor origin is top left, DALi Y is inverted. + // Physics origin is center. Y: 0->1 => 0.5=>-0.5 + return Vector3(vector.x-mWorldOffset.x, mWorldOffset.y-vector.y, vector.z); +} + +// Convert from physics space to root actor local space +Vector3 PhysicsImpl::TranslateFromPhysicsSpace(Vector3 vector) +{ + return Vector3(vector.x+mWorldOffset.x, mWorldOffset.y-vector.y, vector.z); +} + +// Convert a vector from dali space to physics space +Vector3 PhysicsImpl::ConvertVectorToPhysicsSpace(Vector3 vector) +{ + // root actor origin is top left, DALi Y is inverted. + // @todo Add space config scale. + return Vector3(vector.x, -vector.y, vector.z); +} + +// Convert a vector physics space to root actor local space +Vector3 PhysicsImpl::ConvertVectorFromPhysicsSpace(Vector3 vector) +{ + return Vector3(vector.x, -vector.y, vector.z); +} + +void PhysicsImpl::Integrate(float timestep) +{ + if(mPhysicsIntegrateState) + { + cpSpaceStep(mSpace, timestep); + } +// if(mDynamicsWorld->getDebugDrawer() && mPhysicsDebugState) +// { +// mDynamicsWorld->debugDrawWorld(); +// } +} + +cpBody* PhysicsImpl::HitTest(Vector2 screenCoords, Vector3 origin, Vector3 direction, Vector3& localPivot, float& distanceFromCamera) +{ + Vector3 spacePosition = TranslateToPhysicsSpace(Vector3{screenCoords}); + cpVect mousePosition = cpv(spacePosition.x, spacePosition.y); + cpFloat radius = 5.0f; + cpPointQueryInfo info = {0}; + cpShape *shape = cpSpacePointQueryNearest(mSpace, mousePosition, radius, GRAB_FILTER, &info); + + cpBody *body{nullptr}; + + if(shape && cpBodyGetMass(cpShapeGetBody(shape)) < INFINITY) + { + // Use the closest point on the surface if the click is outside the shape. + cpVect nearest = (info.distance > 0.0f ? info.point : mousePosition); + body = cpShapeGetBody(shape); + cpVect local = cpBodyWorldToLocal(body, nearest); + localPivot.x = local.x; + localPivot.y = local.y; + localPivot.z = 0.0; + } + return body; +} + + +cpConstraint* PhysicsImpl::AddPivotJoint(cpBody* body1, cpBody* body2, Vector3 localPivot) +{ + cpVect pivot{localPivot.x, localPivot.y}; + cpConstraint* joint = cpPivotJointNew2(body2, body1, cpvzero, pivot); + cpConstraintSetMaxForce(joint, 50000.0f); // Magic numbers for mouse feedback. + cpConstraintSetErrorBias(joint, cpfpow(1.0f - 0.15f, 60.0f)); + cpConstraint* constraint = cpSpaceAddConstraint(mSpace, joint); + return constraint; // Constraint & joint are the same... +} + +void PhysicsImpl::MoveMouseBody(cpBody* mouseBody, Vector3 position) +{ + cpVect cpPosition = cpv(position.x, position.y); + cpVect newPoint = cpvlerp(cpBodyGetPosition(mouseBody), cpPosition, 0.25f); + cpBodySetVelocity(mouseBody, cpvmult(cpvsub(newPoint, cpBodyGetPosition(mouseBody)), 60.0f)); + // Normally, kinematic body's position would be calculated by engine. + // For mouse, though, we want to set it. + cpBodySetPosition(mouseBody, newPoint); +} + +void PhysicsImpl::MoveConstraint(cpConstraint* constraint, Vector3 newPosition) +{ +} + +void PhysicsImpl::ReleaseConstraint(cpConstraint* constraint) +{ + cpSpaceRemoveConstraint(mSpace, constraint); + cpConstraintFree(constraint); +} + +int PhysicsImpl::ActivateBody(cpBody* body) +{ + int oldState = cpBodyIsSleeping(body); + cpBodyActivate(body); + + return oldState; +} + +void PhysicsImpl::RestoreBodyState(cpBody* body, int oldState) +{ + if(oldState) + { + cpBodyActivate(body); + } + else + { + cpBodySleep(body); + } +} diff --git a/examples/chipmunk-physics/physics-impl.h b/examples/chipmunk-physics/physics-impl.h new file mode 100644 index 0000000..519a7b9 --- /dev/null +++ b/examples/chipmunk-physics/physics-impl.h @@ -0,0 +1,175 @@ +#ifndef DALI_PHYSICS_DEMO_PHYSICS_IMPL_H +#define DALI_PHYSICS_DEMO_PHYSICS_IMPL_H +/* + * 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 "physics-actor.h" +#include "frame-callback.h" + + +class PhysicsImpl : public Dali::ConnectionTracker +{ +public: + Dali::Actor Initialize(Dali::Window window); + + /** + * Create a layer & debug renderer + */ + Dali::Layer CreateDebug(Dali::Vector2 windowSize); + + /** + * Converts a point in RootActor local coords (e.g. gesture) + * into physics space coords. + * @param vector The point to convert + * @return The converted point + */ + Dali::Vector3 TranslateToPhysicsSpace(Dali::Vector3 vector); + + /** + * Converts a point in physics space coords. + * into RootActor local coords + * @param vector The point to convert + * @return The converted point + */ + Dali::Vector3 TranslateFromPhysicsSpace(Dali::Vector3 vector); + + /** + * Converts a vector in DALi space into physics space. + * @param vector The vector to convert + * @return The converted vector + */ + Dali::Vector3 ConvertVectorToPhysicsSpace(Dali::Vector3 vector); + + /** + * Converts a vector in physics space to DALi space + * @param vector The vector to convert + * @return The converted vector + */ + Dali::Vector3 ConvertVectorFromPhysicsSpace(Dali::Vector3 vector); + + /** + * Set up the transform from world space to physics space + * @param[in] worldSize The 2d bounding box of the world in screen space + */ + void SetTransform(Dali::Vector2 worldSize); + + /** + * Run the physics integration over the given timestep. + * @param timeStep + */ + void Integrate(float timeStep); + + /** + * Toggle the integration state. If it's turned on, physics will run + * during the frame callback. + */ + void ToggleIntegrateState() + { + mPhysicsIntegrateState ^= true; + } + + /** + * Toggle the debug state. If debug is turned on, use the physics engine + * debug to show wireframes. + */ + void ToggleDebugState() + { + mPhysicsDebugState ^= true; + } + + void CreateWorldBounds(Dali::Window::WindowSize size); + cpShape* AddBound(cpBody* staticBody, cpVect start, cpVect end); + PhysicsActor& AddBall(Dali::Actor actor, float mass, float radius, float elasticity, float friction); + PhysicsActor& AddBrick(Dali::Actor actor,float mass, float elasticity, float friction, Dali::Vector3 size); + + cpBody* AddMouseBody(); + + /** + * @param[in] screenCoords The touch point in screen coordinates + * @param[in] origin The camera origin in DALi world space + * @param[in] direction The ray direction in DALi world space + * @param[out] localPivot The hit point local to the body + * @param[out] distanceFromCamera The distance of the pick point from the camera + * @return nullptr if no dynamic body found, otherwise a valid ptr to the hit body. + */ + cpBody* HitTest(Dali::Vector2 screenCoords, Dali::Vector3 origin, Dali::Vector3 direction, + Dali::Vector3& localPivot, float& distanceFromCamera); + + cpConstraint* AddPivotJoint(cpBody* body1, cpBody* body2, Dali::Vector3 localPivot); + + void MoveMouseBody(cpBody* mouseBody, Dali::Vector3 position); + + void MoveConstraint(cpConstraint* constraint, Dali::Vector3 newPosition); + + void ReleaseConstraint(cpConstraint* constraint); + + /** + * Ensure that the physics body does not go to sleep + * @param[in] body The physics body + * @return The old state + */ + int ActivateBody(cpBody* body); + + /** + * Restore the state of the physics body + * @param[in] body The physics body + * @param[in] oldState The previous state to restore + */ + void RestoreBodyState(cpBody* body, int oldState); + + /** + * Get the physics actor associated with the given body + * @param[in] body The physics body + * @return the associated physics actor + */ + PhysicsActor* GetPhysicsActor(cpBody* body); + + /** + * Set the highlight state of the actor associated with the physics body + * @param[in] body The physics body + * @param[in] highlight Whether to turn the highlight on or off. + */ + void HighlightBody(cpBody* body, bool highlight); + + +public: + std::map mPhysicsActors; + bool mPhysicsIntegrateState{true}; + bool mPhysicsDebugState{true}; + + cpSpace* mSpace; + cpShape* mLeftBound{nullptr}; + cpShape* mRightBound{nullptr}; + cpShape* mTopBound{nullptr}; + cpShape* mBottomBound{nullptr}; + + Dali::Window mWindow; + Dali::Mutex mMutex; + + Dali::Actor mPhysicsRoot; + Dali::Vector2 mWorldOffset; + FrameCallback* mFrameCallback; +}; + +#endif // DALI_PHYSICS_DEMO_PHYSICS_IMPL_H diff --git a/examples/chipmunk-physics/shaders/rendering-textured-shape.frag b/examples/chipmunk-physics/shaders/rendering-textured-shape.frag new file mode 100644 index 0000000..42bf947 --- /dev/null +++ b/examples/chipmunk-physics/shaders/rendering-textured-shape.frag @@ -0,0 +1,29 @@ +uniform sampler2D uTexture; +uniform mediump float uBrightness; +varying mediump vec2 vTexCoord; +varying mediump vec3 vIllumination; + +mediump vec3 redistribute_rgb(mediump vec3 color) +{ + mediump float threshold = 0.9999999; + mediump float m = max(max(color.r, color.g), color.b); + if(m <= threshold) + { + return color; + } + mediump float total = color.r + color.g + color.b; + if( total >= 3.0 * threshold) + { + return vec3(threshold); + } + mediump float x = (3.0 * threshold - total) / (3.0 * m - total); + mediump float gray = threshold - x * m; + return vec3(gray) + vec3(x)*color; +} + +void main() +{ + mediump vec4 texColor = texture2D( uTexture, vTexCoord ); + mediump vec3 pcol=texColor.rgb*(1.0+uBrightness); + gl_FragColor = vec4( redistribute_rgb(pcol), texColor.a); +} \ No newline at end of file diff --git a/examples/chipmunk-physics/shaders/rendering-textured-shape.vert b/examples/chipmunk-physics/shaders/rendering-textured-shape.vert new file mode 100644 index 0000000..4c41f54 --- /dev/null +++ b/examples/chipmunk-physics/shaders/rendering-textured-shape.vert @@ -0,0 +1,26 @@ +attribute mediump vec3 aPosition; // DALi shader builtin +//attribute mediump vec2 aTexCoord; // DALi shader builtin +uniform mediump mat4 uMvpMatrix; // DALi shader builtin +uniform mediump mat4 uViewMatrix; // DALi shader builtin +uniform mediump mat4 uModelView; // DALi shader builtin +uniform mediump vec3 uSize; // DALi shader builtin +varying mediump vec3 vIllumination; +varying mediump vec2 vTexCoord; + +void main() +{ + mediump vec4 vertexPosition = vec4(aPosition, 1.0); + mediump vec3 normal = normalize(vertexPosition.xyz); + + vertexPosition.xyz *= uSize; + vec4 pos = uModelView * vertexPosition; + + vec4 lightPosition = vec4(400.0, 0.0, 100.0, 1.0); + vec4 mvLightPos = uViewMatrix * lightPosition; + vec3 vectorToLight = normalize(mvLightPos.xyz - pos.xyz); + float lightDiffuse = max(dot(vectorToLight, normal), 0.0); + + vIllumination = vec3(lightDiffuse * 0.5 + 0.5); + vTexCoord = aPosition.xy*2.0; + gl_Position = uMvpMatrix * vertexPosition; +} \ No newline at end of file diff --git a/packaging/com.samsung.dali-demo.spec b/packaging/com.samsung.dali-demo.spec index 853a111..b3590fa 100755 --- a/packaging/com.samsung.dali-demo.spec +++ b/packaging/com.samsung.dali-demo.spec @@ -23,6 +23,7 @@ BuildRequires: pkgconfig(dali2-core) BuildRequires: pkgconfig(dali2-adaptor) BuildRequires: pkgconfig(dali2-toolkit) BuildRequires: pkgconfig(dali2-scene3d) +BuildRequires: pkgconfig(dali2-physics-2d) BuildRequires: pkgconfig(libtzplatform-config) BuildRequires: pkgconfig(gles20) BuildRequires: pkgconfig(glesv2) diff --git a/resources/po/en_GB.po b/resources/po/en_GB.po index c7abd09..d35f25d 100755 --- a/resources/po/en_GB.po +++ b/resources/po/en_GB.po @@ -43,6 +43,9 @@ msgstr "Card Active" msgid "DALI_DEMO_STR_TITLE_COMPRESSED_TEXTURE_FORMATS" msgstr "Compressed Texture Formats" +msgid "DALI_DEMO_STR_TITLE_CHIPMUNK_PHYSICS" +msgstr "Chipmunk Physics" + msgid "DALI_DEMO_STR_TITLE_CLIPPING" msgstr "Clipping" diff --git a/resources/po/en_US.po b/resources/po/en_US.po index 266c445..9a5c9b8 100755 --- a/resources/po/en_US.po +++ b/resources/po/en_US.po @@ -46,6 +46,9 @@ msgstr "Card Active" msgid "DALI_DEMO_STR_TITLE_COMPRESSED_TEXTURE_FORMATS" msgstr "Compressed Texture Formats" +msgid "DALI_DEMO_STR_TITLE_CHIPMUNK_PHYSICS" +msgstr "Chipmunk Physics" + msgid "DALI_DEMO_STR_TITLE_CLIPPING" msgstr "Clipping" diff --git a/shared/dali-demo-strings.h b/shared/dali-demo-strings.h index a371246..3c31c54 100644 --- a/shared/dali-demo-strings.h +++ b/shared/dali-demo-strings.h @@ -50,6 +50,7 @@ extern "C" #define DALI_DEMO_STR_TITLE_CANVAS_VIEW dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_CANVAS_VIEW") #define DALI_DEMO_STR_TITLE_CALL_ACTIVE dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_CALL_ACTIVE") #define DALI_DEMO_STR_TITLE_CARD_ACTIVE dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_CARD_ACTIVE") +#define DALI_DEMO_STR_TITLE_CHIPMUNK_PHYSICS dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_CHIPMUNK_PHYSICS") #define DALI_DEMO_STR_TITLE_CLIPPING dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_CLIPPING") #define DALI_DEMO_STR_TITLE_CLIPPING_DRAW_ORDER dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_CLIPPING_DRAW_ORDER") #define DALI_DEMO_STR_TITLE_COLOR_TRANSITION dgettext(DALI_DEMO_DOMAIN_LOCAL, "DALI_DEMO_STR_TITLE_COLOR_TRANSITION") @@ -169,6 +170,7 @@ extern "C" #define DALI_DEMO_STR_TITLE_CANVAS_VIEW "Canvas View" #define DALI_DEMO_STR_TITLE_CALL_ACTIVE "Call Active" #define DALI_DEMO_STR_TITLE_CARD_ACTIVE "Card Active" +#define DALI_DEMO_STR_TITLE_CHIPMUNK_PHYSICS "Chipmunk Physics" #define DALI_DEMO_STR_TITLE_CLIPPING "Clipping" #define DALI_DEMO_STR_TITLE_CLIPPING_DRAW_ORDER "Clipping Draw Order" #define DALI_DEMO_STR_TITLE_COLOR_TRANSITION "Color Transition"