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.
18 #include "physics-impl.h"
19 #include "physics-actor.h"
21 #include <devel-api/common/stage.h>
32 using namespace Dali::DevelStage;
34 #define GRABBABLE_MASK_BIT (1u << 31)
35 cpShapeFilter GRAB_FILTER = {CP_NO_GROUP, GRABBABLE_MASK_BIT, GRABBABLE_MASK_BIT};
36 cpShapeFilter NOT_GRABBABLE_FILTER = {CP_NO_GROUP, ~GRABBABLE_MASK_BIT, ~GRABBABLE_MASK_BIT};
38 Actor PhysicsImpl::Initialize(Window window)
41 mSpace = cpSpaceNew();
42 cpSpaceSetIterations(mSpace, 30);
43 cpSpaceSetSleepTimeThreshold(mSpace, 0.5f);
44 cpSpaceSetGravity(mSpace, cpv(0, -200));
46 auto windowSize = window.GetSize();
47 CreateWorldBounds(windowSize);
49 // Create an actor that can handle mouse events.
50 mPhysicsRoot = Layer::New();
51 mPhysicsRoot[Actor::Property::SIZE] = Vector2(windowSize.GetWidth(), windowSize.GetHeight());
52 mPhysicsRoot[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER;
53 mPhysicsRoot[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER;
55 mFrameCallback = new FrameCallback(*this);
56 AddFrameCallback(Stage::GetCurrent(), *mFrameCallback, window.GetRootLayer());
57 Stage::GetCurrent().KeepRendering(30);
62 Layer PhysicsImpl::CreateDebug(Vector2 windowSize)
67 void PhysicsImpl::CreateWorldBounds(Window::WindowSize size)
69 // Physics origin is 0,0,0 in DALi coords.
70 // But, Y is inverted, so bottom is -ve, top is +ve.
71 // Perform this correction when applying position to actor.
72 // But, can't use actors in update, so cache transform.
73 SetTransform(Vector2(size.GetWidth(), size.GetHeight()));
75 int xBound = size.GetWidth() / 2;
76 int yBound = size.GetHeight() / 2;
78 cpBody* staticBody = cpSpaceGetStaticBody(mSpace);
82 cpSpaceRemoveShape(mSpace, mLeftBound);
83 cpSpaceRemoveShape(mSpace, mRightBound);
84 cpSpaceRemoveShape(mSpace, mTopBound);
85 cpSpaceRemoveShape(mSpace, mBottomBound);
86 cpShapeFree(mLeftBound);
87 cpShapeFree(mRightBound);
88 cpShapeFree(mTopBound);
89 cpShapeFree(mBottomBound);
91 mLeftBound = AddBound(staticBody, cpv(-xBound, -yBound), cpv(-xBound, yBound));
92 mRightBound = AddBound(staticBody, cpv(xBound, -yBound), cpv(xBound, yBound));
93 mTopBound = AddBound(staticBody, cpv(-xBound, -yBound), cpv(xBound, -yBound));
94 mBottomBound = AddBound(staticBody, cpv(-xBound, yBound), cpv(xBound, yBound));
97 void PhysicsImpl::SetTransform(Vector2 worldSize)
99 mWorldOffset.x = worldSize.x * 0.5f;
100 mWorldOffset.y = worldSize.y * 0.5f;
101 // y is always inverted.
104 cpShape* PhysicsImpl::AddBound(cpBody* staticBody, cpVect start, cpVect end)
106 cpShape* shape = cpSpaceAddShape(mSpace, cpSegmentShapeNew(staticBody, start, end, 0.0f));
107 cpShapeSetElasticity(shape, 1.0f);
108 cpShapeSetFriction(shape, 1.0f);
109 cpShapeSetFilter(shape, NOT_GRABBABLE_FILTER);
113 PhysicsActor& PhysicsImpl::AddBall(::Actor actor, float mass, float radius, float elasticity, float friction)
115 Dali::Mutex::ScopedLock lock(mMutex);
116 cpBody* body = cpSpaceAddBody(mSpace, cpBodyNew(mass, cpMomentForCircle(mass, 0.0f, radius, cpvzero)));
117 cpBodySetPosition(body, cpv(0, 0));
118 cpBodySetVelocity(body, cpv(0, 0));
120 cpShape* shape = cpSpaceAddShape(mSpace, cpCircleShapeNew(body, radius, cpvzero));
121 cpShapeSetElasticity(shape, elasticity);
122 cpShapeSetFriction(shape, friction);
124 int id = actor[Actor::Property::ID];
125 Dali::Property::Index index = actor.RegisterProperty("uBrightness", 0.0f);
126 mPhysicsActors.insert(std::make_pair(id, PhysicsActor{actor, body, this, index}));
127 actor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::TOP_LEFT;
128 actor[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER;
129 mPhysicsRoot.Add(actor);
130 return mPhysicsActors.at(id);
133 PhysicsActor& PhysicsImpl::AddBrick(Dali::Actor actor, float mass, float elasticity, float friction, Vector3 size)
135 Dali::Mutex::ScopedLock lock(mMutex);
136 cpBody* body = cpSpaceAddBody(mSpace, cpBodyNew(mass, cpMomentForBox(mass, size.width, size.height)));
137 cpBodySetPosition(body, cpv(0, 0));
138 cpBodySetVelocity(body, cpv(0, 0));
140 cpShape* shape = cpSpaceAddShape(mSpace, cpBoxShapeNew(body, size.width, size.height, 0.0f));
141 cpShapeSetFriction(shape, friction);
142 cpShapeSetElasticity(shape, elasticity);
144 int id = actor[Actor::Property::ID];
145 Dali::Property::Index index = actor.RegisterProperty("uBrightness", 0.0f);
146 mPhysicsActors.insert(std::make_pair(id, PhysicsActor{actor, body, this, index}));
147 actor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::TOP_LEFT;
148 actor[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER;
149 mPhysicsRoot.Add(actor);
150 return mPhysicsActors.at(id);
153 cpBody* PhysicsImpl::AddMouseBody()
155 Dali::Mutex::ScopedLock lock(mMutex);
156 auto kinematicBody = cpBodyNewKinematic(); // Mouse actor is a kinematic body that is not integrated
157 return kinematicBody;
160 PhysicsActor* PhysicsImpl::GetPhysicsActor(cpBody* body)
162 return reinterpret_cast<PhysicsActor*>(cpBodyGetUserData(body));
165 void PhysicsImpl::HighlightBody(cpBody* body, bool highlight)
167 auto physicsActor = GetPhysicsActor(body);
170 Actor actor = mPhysicsRoot.FindChildById(physicsActor->GetId());
173 actor[physicsActor->GetBrightnessIndex()] = highlight ? 1.0f : 0.0f;
178 // Convert from root actor local space to physics space
179 Vector3 PhysicsImpl::TranslateToPhysicsSpace(Vector3 vector)
181 // root actor origin is top left, DALi Y is inverted.
182 // Physics origin is center. Y: 0->1 => 0.5=>-0.5
183 return Vector3(vector.x - mWorldOffset.x, mWorldOffset.y - vector.y, vector.z);
186 // Convert from physics space to root actor local space
187 Vector3 PhysicsImpl::TranslateFromPhysicsSpace(Vector3 vector)
189 return Vector3(vector.x + mWorldOffset.x, mWorldOffset.y - vector.y, vector.z);
192 // Convert a vector from dali space to physics space
193 Vector3 PhysicsImpl::ConvertVectorToPhysicsSpace(Vector3 vector)
195 // root actor origin is top left, DALi Y is inverted.
196 // @todo Add space config scale.
197 return Vector3(vector.x, -vector.y, vector.z);
200 // Convert a vector physics space to root actor local space
201 Vector3 PhysicsImpl::ConvertVectorFromPhysicsSpace(Vector3 vector)
203 return Vector3(vector.x, -vector.y, vector.z);
206 void PhysicsImpl::Integrate(float timestep)
208 if(mPhysicsIntegrateState)
210 cpSpaceStep(mSpace, timestep);
212 // if(mDynamicsWorld->getDebugDrawer() && mPhysicsDebugState)
214 // mDynamicsWorld->debugDrawWorld();
218 cpBody* PhysicsImpl::HitTest(Vector2 screenCoords, Vector3 origin, Vector3 direction, Vector3& localPivot, float& distanceFromCamera)
220 Vector3 spacePosition = TranslateToPhysicsSpace(Vector3{screenCoords});
221 cpVect mousePosition = cpv(spacePosition.x, spacePosition.y);
222 cpFloat radius = 5.0f;
223 cpPointQueryInfo info = {0};
224 cpShape* shape = cpSpacePointQueryNearest(mSpace, mousePosition, radius, GRAB_FILTER, &info);
226 cpBody* body{nullptr};
228 if(shape && cpBodyGetMass(cpShapeGetBody(shape)) < INFINITY)
230 // Use the closest point on the surface if the click is outside the shape.
231 cpVect nearest = (info.distance > 0.0f ? info.point : mousePosition);
232 body = cpShapeGetBody(shape);
233 cpVect local = cpBodyWorldToLocal(body, nearest);
234 localPivot.x = local.x;
235 localPivot.y = local.y;
241 cpConstraint* PhysicsImpl::AddPivotJoint(cpBody* body1, cpBody* body2, Vector3 localPivot)
243 cpVect pivot{localPivot.x, localPivot.y};
244 cpConstraint* joint = cpPivotJointNew2(body2, body1, cpvzero, pivot);
245 cpConstraintSetMaxForce(joint, 50000.0f); // Magic numbers for mouse feedback.
246 cpConstraintSetErrorBias(joint, cpfpow(1.0f - 0.15f, 60.0f));
247 cpConstraint* constraint = cpSpaceAddConstraint(mSpace, joint);
248 return constraint; // Constraint & joint are the same...
251 void PhysicsImpl::MoveMouseBody(cpBody* mouseBody, Vector3 position)
253 cpVect cpPosition = cpv(position.x, position.y);
254 cpVect newPoint = cpvlerp(cpBodyGetPosition(mouseBody), cpPosition, 0.25f);
255 cpBodySetVelocity(mouseBody, cpvmult(cpvsub(newPoint, cpBodyGetPosition(mouseBody)), 60.0f));
256 // Normally, kinematic body's position would be calculated by engine.
257 // For mouse, though, we want to set it.
258 cpBodySetPosition(mouseBody, newPoint);
261 void PhysicsImpl::MoveConstraint(cpConstraint* constraint, Vector3 newPosition)
265 void PhysicsImpl::ReleaseConstraint(cpConstraint* constraint)
267 cpSpaceRemoveConstraint(mSpace, constraint);
268 cpConstraintFree(constraint);
271 int PhysicsImpl::ActivateBody(cpBody* body)
273 int oldState = cpBodyIsSleeping(body);
274 cpBodyActivate(body);
279 void PhysicsImpl::RestoreBodyState(cpBody* body, int oldState)
283 cpBodyActivate(body);