From: David Steele Date: Wed, 9 Aug 2023 17:21:38 +0000 (+0100) Subject: Adding bullet physics files X-Git-Tag: dali_2.2.43~4^2~1 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=b068e8289e5975b6afd71c08c9bf522153fe7a89 Adding bullet physics files Created DALi version of bullet-physics-demo from github.sec.samsung.net/dalihub/dali-physics-demo Hidden details of ScopedAccessor Changed how transform works Updated debug renderer Added test cases for PhysicsAdaptor Added test cases for PhysicsActor Fixed quaternion handling to work with mirrored x/y/z coord system Change-Id: I7292a2fbf73ef82a8b5e43701489b460ea36b27d --- diff --git a/.gitignore b/.gitignore index 2c8d0d1..4555b92 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,20 @@ install_manifest.txt /build/tizen/doc /build/tizen/.cov /build/tizen/CPack*.cmake -/build/tizen/dali-physics/* + +/build/tizen/.ninja_deps +/build/tizen/.ninja_log +/build/tizen/build.ninja +/build/tizen/rules.ninja + +/build/tizen/dali-physics/*.cmake +/build/tizen/dali-physics/Makefile +/build/tizen/dali-physics/CMakeCache.txt +/build/tizen/dali-physics/CMakeFiles/ +/build/tizen/dali-physics/cmake_install.cmake +/build/tizen/dali-physics/bullet3/ +/build/tizen/dali-physics/chipmunk2d/ + /build/desktop /packaging/home* compile_commands.json diff --git a/automated-tests/patch-coverage.pl b/automated-tests/patch-coverage.pl index 2f8bdb4..38fc2b0 100755 --- a/automated-tests/patch-coverage.pl +++ b/automated-tests/patch-coverage.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl # -# Copyright (c) 2020 Samsung Electronics Co., Ltd. +# 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. @@ -985,7 +985,7 @@ sub parse_diff $files{$file}->{"patch"} = [@checklines]; $files{$file}->{"b_lines"} = {%b_lines}; - my %filter = map { $_ => $files{$_} } grep {m!^dali(-toolkit|-scene3d)?/!} (keys(%files)); + my %filter = map { $_ => $files{$_} } grep {m!^dali(-toolkit|-scene3d|-physics)?/!} (keys(%files)); if($pd_debug) { diff --git a/automated-tests/src/dali-physics/CMakeLists.txt b/automated-tests/src/dali-physics/CMakeLists.txt new file mode 100644 index 0000000..b6beab1 --- /dev/null +++ b/automated-tests/src/dali-physics/CMakeLists.txt @@ -0,0 +1,99 @@ +SET(PKG_NAME "dali-physics") + +SET(EXEC_NAME "tct-${PKG_NAME}-core") +SET(RPM_NAME "core-${PKG_NAME}-tests") + +SET(CAPI_LIB "dali-physics") + +# List of test case sources (Only these get parsed for test cases) +SET(TC_SOURCES + utc-Dali-PhysicsAdaptor.cpp + utc-Dali-PhysicsActor.cpp + ) + +# List of test harness files (Won't get parsed for test cases) +SET(TEST_HARNESS_DIR "../dali-toolkit/dali-toolkit-test-utils") + +SET(TEST_HARNESS_SOURCES + ${TEST_HARNESS_DIR}/toolkit-adaptor.cpp + ${TEST_HARNESS_DIR}/toolkit-application.cpp + ${TEST_HARNESS_DIR}/toolkit-direct-rendering-egl.cpp + ${TEST_HARNESS_DIR}/toolkit-event-thread-callback.cpp + ${TEST_HARNESS_DIR}/toolkit-environment-variable.cpp + ${TEST_HARNESS_DIR}/toolkit-input-method-context.cpp + ${TEST_HARNESS_DIR}/toolkit-input-method-options.cpp + ${TEST_HARNESS_DIR}/toolkit-lifecycle-controller.cpp + ${TEST_HARNESS_DIR}/toolkit-orientation.cpp + ${TEST_HARNESS_DIR}/toolkit-style-monitor.cpp + ${TEST_HARNESS_DIR}/toolkit-test-application.cpp + ${TEST_HARNESS_DIR}/toolkit-timer.cpp + ${TEST_HARNESS_DIR}/toolkit-trigger-event-factory.cpp + ${TEST_HARNESS_DIR}/toolkit-window.cpp + ${TEST_HARNESS_DIR}/toolkit-scene-holder.cpp + ${TEST_HARNESS_DIR}/dali-test-suite-utils.cpp + ${TEST_HARNESS_DIR}/dali-toolkit-test-suite-utils.cpp + ${TEST_HARNESS_DIR}/dummy-control.cpp + ${TEST_HARNESS_DIR}/mesh-builder.cpp + ${TEST_HARNESS_DIR}/test-actor-utils.cpp + ${TEST_HARNESS_DIR}/test-animation-data.cpp + ${TEST_HARNESS_DIR}/test-application.cpp + ${TEST_HARNESS_DIR}/test-button.cpp + ${TEST_HARNESS_DIR}/test-harness.cpp + ${TEST_HARNESS_DIR}/test-gesture-generator.cpp + ${TEST_HARNESS_DIR}/test-gl-abstraction.cpp + ${TEST_HARNESS_DIR}/test-graphics-sync-impl.cpp + ${TEST_HARNESS_DIR}/test-graphics-sync-object.cpp + ${TEST_HARNESS_DIR}/test-graphics-buffer.cpp + ${TEST_HARNESS_DIR}/test-graphics-command-buffer.cpp + ${TEST_HARNESS_DIR}/test-graphics-framebuffer.cpp + ${TEST_HARNESS_DIR}/test-graphics-controller.cpp + ${TEST_HARNESS_DIR}/test-graphics-texture.cpp + ${TEST_HARNESS_DIR}/test-graphics-sampler.cpp + ${TEST_HARNESS_DIR}/test-graphics-program.cpp + ${TEST_HARNESS_DIR}/test-graphics-pipeline.cpp + ${TEST_HARNESS_DIR}/test-graphics-shader.cpp + ${TEST_HARNESS_DIR}/test-graphics-reflection.cpp + ${TEST_HARNESS_DIR}/test-platform-abstraction.cpp + ${TEST_HARNESS_DIR}/test-render-controller.cpp + ${TEST_HARNESS_DIR}/test-trace-call-stack.cpp +) + +PKG_CHECK_MODULES(${CAPI_LIB} REQUIRED + dali2-core + dali2-adaptor + dali2-toolkit + dali2-physics-3d + bullet3 +) + +ADD_COMPILE_OPTIONS( -O0 -ggdb --coverage -Wall -Werror -DDEBUG_ENABLED) +ADD_COMPILE_OPTIONS( ${${CAPI_LIB}_CFLAGS_OTHER} ) + +ADD_DEFINITIONS(-DTEST_RESOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../resources\" ) + +FOREACH(directory ${${CAPI_LIB}_LIBRARY_DIRS}) + SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -L${directory}") +ENDFOREACH(directory ${CAPI_LIB_LIBRARY_DIRS}) + +INCLUDE_DIRECTORIES( + ../../../ + ${${CAPI_LIB}_INCLUDE_DIRS} + ../dali-toolkit/dali-toolkit-test-utils +) + +ADD_CUSTOM_COMMAND( + COMMAND ${SCRIPT_DIR}/tcheadgen.sh ${EXEC_NAME}.h ${TC_SOURCES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT ${EXEC_NAME}.h + COMMENT "Generating test tables" + ) + +ADD_EXECUTABLE(${EXEC_NAME} ${EXEC_NAME}.h ${EXEC_NAME}.cpp ${TC_SOURCES} ${TEST_HARNESS_SOURCES}) +TARGET_LINK_LIBRARIES(${EXEC_NAME} + ${${CAPI_LIB}_LIBRARIES} + -lpthread -ldl --coverage +) + +INSTALL(PROGRAMS ${EXEC_NAME} + DESTINATION ${BIN_DIR}/${EXEC_NAME} +) diff --git a/automated-tests/src/dali-physics/tct-dali-physics-core.cpp b/automated-tests/src/dali-physics/tct-dali-physics-core.cpp new file mode 100644 index 0000000..64550a1 --- /dev/null +++ b/automated-tests/src/dali-physics/tct-dali-physics-core.cpp @@ -0,0 +1,7 @@ +#include +#include "tct-dali-physics-core.h" + +int main(int argc, char * const argv[]) +{ + return TestHarness::RunTests(argc, argv, tc_array); +} diff --git a/automated-tests/src/dali-physics/utc-Dali-PhysicsActor.cpp b/automated-tests/src/dali-physics/utc-Dali-PhysicsActor.cpp new file mode 100644 index 0000000..e1266e8 --- /dev/null +++ b/automated-tests/src/dali-physics/utc-Dali-PhysicsActor.cpp @@ -0,0 +1,592 @@ +/* + * 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 + +// Need to override adaptor classes for toolkit test harness, so include +// test harness headers before dali headers. +#include +#include + +#include +#include +#include + +using namespace Dali; +using namespace Dali::Toolkit::Physics; + +extern btRigidBody* CreateBody(btDiscreteDynamicsWorld* bulletWorld); + +const char* BALL_IMAGE = TEST_RESOURCE_DIR "/gallery-small-1.jpg"; + +int UtcDaliPhysicsActorNew(void) +{ + ToolkitTestApplication application; + + btRigidBody* body{nullptr}; + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + PhysicsActor physicsActor = PhysicsActor::New(ballActor, body, adaptor); + + DALI_TEST_CHECK(physicsActor); + END_TEST; +} + +int UtcDaliPhysicsActorDownCastP(void) +{ + ToolkitTestApplication application; + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + btRigidBody* body{nullptr}; + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + PhysicsActor physicsActor = PhysicsActor::New(ballActor, body, adaptor); + BaseHandle handle(physicsActor); + + PhysicsActor actor2 = PhysicsActor::DownCast(handle); + DALI_TEST_CHECK(actor2); + DALI_TEST_EQUALS(physicsActor.GetId(), actor2.GetId(), TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsActorDownCastN(void) +{ + BaseHandle uninitializedHandle; + PhysicsActor actor = PhysicsActor::DownCast(uninitializedHandle); + DALI_TEST_CHECK(!actor); + END_TEST; +} + +int UtcDaliPhysicsActorMoveConstructor(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the move constructor"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + btRigidBody* body{nullptr}; + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + PhysicsActor physicsActor = PhysicsActor::New(ballActor, body, adaptor); + + DALI_TEST_CHECK(physicsActor); + uint32_t id = physicsActor.GetId(); + + PhysicsActor moved = std::move(physicsActor); + DALI_TEST_CHECK(moved); + DALI_TEST_CHECK(!physicsActor); + DALI_TEST_CHECK(moved != physicsActor); + DALI_TEST_EQUALS(moved.GetId(), id, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsActorCopyConstructor(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the move constructor"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + btRigidBody* body{nullptr}; + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + PhysicsActor physicsActor = PhysicsActor::New(ballActor, body, adaptor); + + DALI_TEST_CHECK(physicsActor); + uint32_t id = physicsActor.GetId(); + + PhysicsActor selectedActor(physicsActor); + DALI_TEST_CHECK(selectedActor); + DALI_TEST_CHECK(physicsActor); + DALI_TEST_CHECK(selectedActor == physicsActor); // should point at same object + DALI_TEST_EQUALS(selectedActor.GetId(), id, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsActorCopyAssign(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the copy assign"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + btRigidBody* body{nullptr}; + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + PhysicsActor physicsActor = PhysicsActor::New(ballActor, body, adaptor); + + DALI_TEST_CHECK(physicsActor); + uint32_t id = physicsActor.GetId(); + + PhysicsActor selectedActor = physicsActor; + DALI_TEST_CHECK(selectedActor); + DALI_TEST_CHECK(physicsActor); + DALI_TEST_CHECK(selectedActor == physicsActor); // should point at same object + DALI_TEST_EQUALS(selectedActor.GetId(), id, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsActorMoveAssignment(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the move constructor"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + btRigidBody* body{nullptr}; + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + PhysicsActor physicsActor = PhysicsActor::New(ballActor, body, adaptor); + + DALI_TEST_CHECK(physicsActor); + uint32_t id = physicsActor.GetId(); + + PhysicsActor moved; + moved = std::move(physicsActor); + DALI_TEST_CHECK(moved); + DALI_TEST_CHECK(!physicsActor); + DALI_TEST_EQUALS(moved.GetId(), id, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsActorGetIdP(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the ID Getter"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + btRigidBody* body{nullptr}; + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + PhysicsActor physicsActor = adaptor.AddActorBody(ballActor, body); + int id = physicsActor.GetId(); + int actorId = ballActor[Actor::Property::ID]; + DALI_TEST_EQUALS(id, actorId, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsActorGetIdN(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the ID Getter"); + + PhysicsActor physicsActor; + try + { + uint32_t id __attribute__((unused)) = physicsActor.GetId(); + tet_result(TET_FAIL); + } + catch(DaliException e) + { + DALI_TEST_ASSERT(e, "Physics actor handle is empty", TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsActorGetBodyP(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the body Getter"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + PhysicsActor physicsActor; + btRigidBody* body{nullptr}; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + physicsActor = adaptor.AddActorBody(ballActor, body); + } + + application.Render(); + Test::WaitForEventThreadTrigger(1); + + Dali::Any any = physicsActor.GetBody(); + DALI_TEST_EQUALS(any.Get(), body, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsActorGetBodyN(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the ID Getter"); + + PhysicsActor physicsActor; + try + { + Dali::Any any __attribute__((unused)) = physicsActor.GetBody(); + tet_result(TET_FAIL); + } + catch(DaliException e) + { + DALI_TEST_ASSERT(e, "Physics actor handle is empty", TEST_LOCATION); + } + END_TEST; +} + +int UtcDaliPhysicsActorSetPosition(void) +{ + tet_infoline("Test the AsyncSetPhysicsPosition() function"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + btRigidBody* body{nullptr}; + PhysicsActor physicsActor; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + physicsActor = adaptor.AddActorBody(ballActor, body); + physicsActor.AsyncSetPhysicsPosition(Vector3(10, 20, -30)); + } + + Test::WaitForEventThreadTrigger(1); + adaptor.CreateSyncPoint(); + application.SendNotification(); + application.Render(); + + // Run 2 frames to ensure both buffers are set. + application.SendNotification(); + application.Render(); + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto actor = rootActor.FindChildById(physicsActor.GetId()); + // Warning - physics properties are never reflected in the event size cache. + // Have to use GetCurrentProperty to see the updated values. + DALI_TEST_EQUALS(actor.GetCurrentProperty(Actor::Property::POSITION), Vector3(10, 20, -30), 0.0001f, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsActorSetRotation1(void) +{ + tet_infoline("Test the AsyncSetPhysicsRotation() function"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, -2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + btRigidBody* body{nullptr}; + PhysicsActor physicsActor; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + physicsActor = adaptor.AddActorBody(ballActor, body); + physicsActor.AsyncSetPhysicsRotation(Quaternion(Degree(30), Vector3::YAXIS)); + } + + Test::WaitForEventThreadTrigger(1); + adaptor.CreateSyncPoint(); + application.SendNotification(); + application.Render(); + + // Run 2 frames to ensure both buffers are set. + application.SendNotification(); + application.Render(); + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto actor = rootActor.FindChildById(physicsActor.GetId()); + // Warning - physics properties are never reflected in the event size cache. + // Have to use GetCurrentProperty to see the updated values. + Quaternion q = actor.GetCurrentProperty(Actor::Property::ORIENTATION); + Quaternion expected(Degree(30), Vector3::YAXIS); + DALI_TEST_EQUALS(q, expected, 0.0001f, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsActorSetRotation2(void) +{ + tet_infoline("Test the AsyncSetPhysicsRotation() function"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, -2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + btRigidBody* body{nullptr}; + PhysicsActor physicsActor; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + physicsActor = adaptor.AddActorBody(ballActor, body); + physicsActor.AsyncSetPhysicsRotation(Quaternion(Degree(30), Vector3::ZAXIS)); + } + + Test::WaitForEventThreadTrigger(1); + adaptor.CreateSyncPoint(); + application.SendNotification(); + application.Render(); + + // Run 2 frames to ensure both buffers are set. + application.SendNotification(); + application.Render(); + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto actor = rootActor.FindChildById(physicsActor.GetId()); + // Warning - physics properties are never reflected in the event size cache. + // Have to use GetCurrentProperty to see the updated values. + Quaternion q = actor.GetCurrentProperty(Actor::Property::ORIENTATION); + Quaternion expected(Degree(30), Vector3::ZAXIS); + DALI_TEST_EQUALS(q, expected, 0.0001f, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsActorGetActorPosition(void) +{ + tet_infoline("Test the GetActorPosition() function"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + btRigidBody* body{nullptr}; + PhysicsActor physicsActor; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + physicsActor = adaptor.AddActorBody(ballActor, body); + physicsActor.AsyncSetPhysicsPosition(Vector3(10, 20, -30)); + } + + Test::WaitForEventThreadTrigger(1); + adaptor.CreateSyncPoint(); + application.SendNotification(); + application.Render(); + + // Run 2 frames to ensure both buffers are set. + application.SendNotification(); + application.Render(); + { + auto accessor = adaptor.GetPhysicsAccessor(); + DALI_TEST_EQUALS(physicsActor.GetActorPosition(), Vector3(10, 20, -30), 0.0001f, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsActorGetActorRotation(void) +{ + tet_infoline("Test the GetActorRotation() function"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + btRigidBody* body{nullptr}; + PhysicsActor physicsActor; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + physicsActor = adaptor.AddActorBody(ballActor, body); + physicsActor.AsyncSetPhysicsRotation(Quaternion(Degree(30), Vector3::ZAXIS)); + } + + Test::WaitForEventThreadTrigger(1); + adaptor.CreateSyncPoint(); + application.SendNotification(); + application.Render(); + + // Run 2 frames to ensure both buffers are set. + application.SendNotification(); + application.Render(); + { + auto accessor = adaptor.GetPhysicsAccessor(); + DALI_TEST_EQUALS(physicsActor.GetActorRotation(), Quaternion(Degree(30), Vector3::ZAXIS), 0.0001f, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsActorGetPhysicsPosition(void) +{ + tet_infoline("Test the GetPhysicsPosition() function"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + btRigidBody* body{nullptr}; + PhysicsActor physicsActor; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + physicsActor = adaptor.AddActorBody(ballActor, body); + physicsActor.AsyncSetPhysicsPosition(Vector3(10, 20, -30)); + } + + Test::WaitForEventThreadTrigger(1); + adaptor.CreateSyncPoint(); + application.SendNotification(); + application.Render(); + + // Run 2 frames to ensure both buffers are set. + application.SendNotification(); + application.Render(); + { + auto accessor = adaptor.GetPhysicsAccessor(); + Vector4 pos = transform * Vector4(10, 20, -30, 1); + DALI_TEST_EQUALS(physicsActor.GetPhysicsPosition(), Vector3(pos), 0.0001f, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsActorGetPhysicsRotation(void) +{ + tet_infoline("Test the GetPhysicsRotation() function"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, -2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE); + + btRigidBody* body{nullptr}; + PhysicsActor physicsActor; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + physicsActor = adaptor.AddActorBody(ballActor, body); + physicsActor.AsyncSetPhysicsRotation(Quaternion(Degree(30), Vector3::ZAXIS)); + } + + Test::WaitForEventThreadTrigger(1); + adaptor.CreateSyncPoint(); + application.SendNotification(); + application.Render(); + + // Run 2 frames to ensure both buffers are set. + application.SendNotification(); + application.Render(); + { + auto accessor = adaptor.GetPhysicsAccessor(); + DALI_TEST_EQUALS(physicsActor.GetPhysicsRotation(), Quaternion(Degree(-30), Vector3::ZAXIS), 0.0001f, TEST_LOCATION); + } + + END_TEST; +} diff --git a/automated-tests/src/dali-physics/utc-Dali-PhysicsAdaptor.cpp b/automated-tests/src/dali-physics/utc-Dali-PhysicsAdaptor.cpp new file mode 100644 index 0000000..01dae19 --- /dev/null +++ b/automated-tests/src/dali-physics/utc-Dali-PhysicsAdaptor.cpp @@ -0,0 +1,847 @@ +/* + * 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 + +// Need to override adaptor classes for toolkit test harness, so include +// test harness headers before dali headers. +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace Dali; +using namespace Dali::Toolkit::Physics; + +void utc_dali_physics3d_startup(void) +{ + test_return_value = TET_UNDEF; +} + +void utc_dali_physics3d_cleanup(void) +{ + test_return_value = TET_PASS; +} + +btRigidBody* CreateBody(btDiscreteDynamicsWorld* bulletWorld) +{ + btSphereShape* ball = new btSphereShape(30); + btVector3 localInertia(0.f, 0.f, 0.f); + ball->calculateLocalInertia(10, localInertia); + btTransform transform; + transform.setIdentity(); + auto* motionState = new btDefaultMotionState(transform); + + btRigidBody::btRigidBodyConstructionInfo ci(10, motionState, ball, localInertia); + + btRigidBody* body = new btRigidBody(ci); + body->setFriction(0.5f); + body->setRestitution(0.5f); + bulletWorld->addRigidBody(body); + return body; +} + +int UtcDaliPhysicsCreateAdaptorP1(void) +{ + ToolkitTestApplication application; + + Matrix transform(true); + Uint16Pair size(640, 480); + + PhysicsAdaptor handle = PhysicsAdaptor::New(transform, size); + DALI_TEST_CHECK(handle); + + END_TEST; +} + +int UtcDaliPhysicsCreateAdaptorN1(void) +{ + ToolkitTestApplication application; + + PhysicsAdaptor handle; + DALI_TEST_CHECK(!handle); + + END_TEST; +} + +int UtcDaliPhysicsDowncastP1(void) +{ + ToolkitTestApplication application; + + Matrix transform(true); + Uint16Pair size(640, 480); + + BaseHandle handle = PhysicsAdaptor::New(transform, size); + + auto adaptor = PhysicsAdaptor::DownCast(handle); + DALI_TEST_CHECK(adaptor); + //Following only works if type is registered + //DALI_TEST_EQUALS("PhysicsAdaptor", adaptor.GetTypeName(), TEST_LOCATION); + END_TEST; +} + +int UtcDaliPhysicsDowncastN1(void) +{ + BaseHandle handle; + auto adaptor = PhysicsAdaptor::DownCast(handle); + DALI_TEST_CHECK(!adaptor); + + DALI_TEST_CHECK(typeid(PhysicsAdaptor) == typeid(decltype(adaptor))); + END_TEST; +} + +int UtcDaliPhysicsAdaptorMoveConstructor(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the move constructor"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + DALI_TEST_CHECK(adaptor); + + PhysicsAdaptor moved = std::move(adaptor); + DALI_TEST_CHECK(moved); + DALI_TEST_CHECK(!adaptor); + DALI_TEST_CHECK(moved != adaptor); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorCopyConstructor(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the move constructor"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + DALI_TEST_CHECK(adaptor); + + PhysicsAdaptor altAdaptor = adaptor; + DALI_TEST_CHECK(altAdaptor); + DALI_TEST_CHECK(adaptor); + DALI_TEST_CHECK(altAdaptor == adaptor); // should point at same object + + END_TEST; +} + +int UtcDaliPhysicsAdaptorCopyAssign(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the copy assign"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + DALI_TEST_CHECK(adaptor); + + PhysicsAdaptor altAdaptor = adaptor; + DALI_TEST_CHECK(altAdaptor); + DALI_TEST_CHECK(adaptor); + DALI_TEST_CHECK(altAdaptor == adaptor); // should point at same object + + END_TEST; +} + +int UtcDaliPhysicsAdaptorMoveAssignment(void) +{ + ToolkitTestApplication application; + tet_infoline("Testing the move constructor"); + + Matrix transform(true); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + DALI_TEST_CHECK(adaptor); + + PhysicsAdaptor moved; + moved = std::move(adaptor); + DALI_TEST_CHECK(moved); + DALI_TEST_CHECK(!adaptor); + + END_TEST; +} + +int UtcDaliPhysicsSetTimestep(void) +{ + ToolkitTestApplication application; + + Matrix transform(true); + Uint16Pair size(640, 480); + + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + adaptor.SetTimestep(1.0f / 60.0f); + + DALI_TEST_EQUALS(adaptor.GetTimestep(), 1.0f / 60.0f, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsGetTimestep(void) +{ + ToolkitTestApplication application; + + Matrix transform(true); + Uint16Pair size(640, 480); + + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + adaptor.SetTimestep(1.0f / 60.0f); + float timestep = adaptor.GetTimestep(); + float expected = 1.0f / 60.0f; + DALI_TEST_EQUALS(timestep, expected, 0.0001f, TEST_LOCATION); + + adaptor.SetTimestep(1.0f / 120.0f); + timestep = adaptor.GetTimestep(); + expected = 1.0f / 120.0f; + DALI_TEST_EQUALS(timestep, expected, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsGetPhysicsAccessorP1(void) +{ + ToolkitTestApplication application; + + Matrix transform(true); + Uint16Pair size(640, 480); + + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + PhysicsAdaptor::ScopedPhysicsAccessorPtr accessor = adaptor.GetPhysicsAccessor(); + DALI_TEST_CHECK(accessor.get() != nullptr); + + Dali::Any world = accessor->GetNative(); + DALI_TEST_CHECK(!world.Empty()); + + END_TEST; +} + +int UtcDaliPhysicsGetPhysicsAccessorN1(void) +{ + ToolkitTestApplication application; + + PhysicsAdaptor handle; + DALI_TEST_CHECK(!handle); + + try + { + auto ptr = handle.GetPhysicsAccessor(); + DALI_TEST_CHECK(ptr == nullptr); + + tet_result(TET_FAIL); + } + catch(DaliException& e) + { + DALI_TEST_ASSERT(e, "Physics adaptor handle is empty", TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsAdaptorGetRootActor(void) +{ + tet_infoline("Test that the root actor can be retrieved"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + auto scene = application.GetScene(); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + scene.Add(rootActor); + + DALI_TEST_CHECK(rootActor); + DALI_TEST_EQUALS(rootActor.GetProperty(Actor::Property::SIZE), Vector2(640.0f, 480.0f), 0.001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorCreateDebugLayer(void) +{ + ToolkitTestApplication application; + Matrix transform(true); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + auto scene = application.GetScene(); + + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + scene.Add(rootActor); + Window window = DevelWindow::Get(rootActor); + + Layer layer = adaptor.CreateDebugLayer(window); + DALI_TEST_CHECK(layer); + + adaptor.SetDebugState(PhysicsAdaptor::DebugState::ON); + + btRigidBody* body{nullptr}; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New(TEST_RESOURCE_DIR "/gallery-small-1.jpg"); + auto physicsActor = adaptor.AddActorBody(ballActor, body); + physicsActor.AsyncSetPhysicsPosition(Vector3(0.f, 0.f, 0.f)); + } + Test::WaitForEventThreadTrigger(1); + application.SendNotification(); + application.Render(); + + application.SendNotification(); + application.Render(); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorTranslateToPhysicsSpace1(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + Vector3 a(30, 20, 10); + Vector3 expected = a * 2.0f; + DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(a), expected, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorTranslateToPhysicsSpace2(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using an alternative scale doesn't change rotation"); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + // Rotation shouldn't change under this scale + Quaternion q(Degree(30.0f), Vector3::XAXIS); + DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(q), q, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorTranslateToPhysicsSpace3(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using an inverted Y scale also inverts quaternions"); + + transform.SetIdentityAndScale(Vector3(1.0f, -1.0f, 1.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + Quaternion q(Degree(30.0f), Vector3::ZAXIS); + Quaternion qp(Degree(-30.0f), Vector3::ZAXIS); // We have mirrored along Y axis, so Z rot is opposite. + + DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(q), qp, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorTranslateToPhysicsSpace4(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using an inverted Y scale also inverts quaternions"); + + transform.SetIdentityAndScale(Vector3(1.0f, -1.0f, 1.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + Quaternion q(Degree(30.0f), Vector3::XAXIS); + Quaternion qp(Degree(-30.0f), Vector3::XAXIS); // We have mirrored along Y axis, so Z rot is opposite. + + DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(q), qp, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorTranslateToPhysicsSpace5(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using an inverted Y scale also inverts quaternions, except along Y axis"); + + transform.SetIdentityAndScale(Vector3(1.0f, -1.0f, 1.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + Quaternion q(Degree(30.0f), Vector3::YAXIS); + Quaternion qp(Degree(30.0f), Vector3::YAXIS); + + DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(q), qp, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorTranslateFromPhysicsSpace1(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using a double scale halves position"); + + transform.SetIdentityAndScale(Vector3(2.0f, -2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + Vector3 position(20.0f, 20.0f, 0.0f); + Vector3 expected(10.0f, -10.0f, 0.0f); + + DALI_TEST_EQUALS(adaptor.TranslateFromPhysicsSpace(position), expected, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorConvertVectorToPhysicsSpace01(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using a translation does not translate vector"); + + transform.SetIdentityAndScale(Vector3(1.0f, 1.0f, 1.0f)); + transform.SetTranslation(Vector3(0.0f, 100.0f, 0.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Vector3 vector(20.0f, 20.0f, 0.0f); + DALI_TEST_EQUALS(adaptor.ConvertVectorToPhysicsSpace(vector), vector, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorConvertVectorToPhysicsSpace02(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using a translation with inverse Y does not translate vector"); + + transform.SetIdentityAndScale(Vector3(1.0f, -1.0f, 1.0f)); + transform.SetTranslation(Vector3(0.0f, 100.0f, 0.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Vector3 vector(20.0f, 20.0f, 0.0f); + Vector3 expected(20.0f, -20.0f, 0.0f); + DALI_TEST_EQUALS(adaptor.ConvertVectorToPhysicsSpace(vector), expected, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorConvertVectorFromPhysicsSpace01(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using a translation does not translate vector"); + + transform.SetIdentityAndScale(Vector3(1.0f, 1.0f, 1.0f)); + transform.SetTranslation(Vector3(0.0f, 100.0f, 0.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Vector3 vector(20.0f, 20.0f, 0.0f); + DALI_TEST_EQUALS(adaptor.ConvertVectorFromPhysicsSpace(vector), vector, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorConvertVectorFromPhysicsSpace02(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + tet_infoline("Test that using a translation with inverse Y does not translate vector"); + + transform.SetIdentityAndScale(Vector3(1.0f, -1.0f, 1.0f)); + transform.SetTranslation(Vector3(0.0f, 100.0f, 0.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Vector3 vector(20.0f, 20.0f, 0.0f); + Vector3 expected(20.0f, -20.0f, 0.0f); + DALI_TEST_EQUALS(adaptor.ConvertVectorFromPhysicsSpace(vector), expected, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorSetTransformAndSize(void) +{ + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + Vector3 a(30, 20, 10); + Vector3 expected = a * 2.0f; + DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(a), expected, 0.0001f, TEST_LOCATION); + + transform.SetIdentityAndScale(Vector3(1.0f, -1.0f, 1.0f)); + transform.SetTranslation(Vector3(0.0f, 100.0f, 0.0f)); + adaptor.SetTransformAndSize(transform, size); + + Vector3 expect2(30, 80, 10); + DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(a), expect2, 0.0001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorSetIntegrationState(void) +{ + tet_infoline("Test that changing the integration state is reflected"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + auto scene = application.GetScene(); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + scene.Add(rootActor); + + DALI_TEST_CHECK(adaptor.GetIntegrationState() == PhysicsAdaptor::IntegrationState::ON); + + adaptor.SetIntegrationState(PhysicsAdaptor::IntegrationState::OFF); + DALI_TEST_CHECK(adaptor.GetIntegrationState() == PhysicsAdaptor::IntegrationState::OFF); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorGetIntegrationState(void) +{ + tet_infoline("Test that changing the integration state is reflected"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + auto scene = application.GetScene(); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + scene.Add(rootActor); + + adaptor.SetIntegrationState(PhysicsAdaptor::IntegrationState::OFF); + DALI_TEST_CHECK(adaptor.GetIntegrationState() == PhysicsAdaptor::IntegrationState::OFF); + + adaptor.SetIntegrationState(PhysicsAdaptor::IntegrationState::ON); + DALI_TEST_CHECK(adaptor.GetIntegrationState() == PhysicsAdaptor::IntegrationState::ON); + + // Can't test actual integration step runs without adding actors - see utc-Dali-PhysicsActor.cpp. + END_TEST; +} + +int UtcDaliPhysicsAdaptorSetDebugState(void) +{ + tet_infoline("Test that changing the debug state is reflected"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + auto scene = application.GetScene(); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + scene.Add(rootActor); + + DALI_TEST_CHECK(adaptor.GetDebugState() == PhysicsAdaptor::DebugState::OFF); + + adaptor.SetDebugState(PhysicsAdaptor::DebugState::ON); + DALI_TEST_CHECK(adaptor.GetDebugState() == PhysicsAdaptor::DebugState::ON); + + adaptor.SetDebugState(PhysicsAdaptor::DebugState::OFF); + DALI_TEST_CHECK(adaptor.GetDebugState() == PhysicsAdaptor::DebugState::OFF); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorGetDebugState(void) +{ + tet_infoline("Test that changing the debug state is reflected"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + auto scene = application.GetScene(); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + scene.Add(rootActor); + + adaptor.SetDebugState(PhysicsAdaptor::DebugState::OFF); + DALI_TEST_CHECK(adaptor.GetDebugState() == PhysicsAdaptor::DebugState::OFF); + + adaptor.SetDebugState(PhysicsAdaptor::DebugState::ON); + DALI_TEST_CHECK(adaptor.GetDebugState() == PhysicsAdaptor::DebugState::ON); + + // Can't test actual debug step runs without adding actors - see utc-Dali-PhysicsActor.cpp. + END_TEST; +} + +int UtcDaliPhysicsAdaptorAddActorBody(void) +{ + tet_infoline("Test that an actor/body pair can be added"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + auto scene = application.GetScene(); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + scene.Add(rootActor); + + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + + btRigidBody* body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New("gallery-small-1.jpg"); + auto physicsActor = adaptor.AddActorBody(ballActor, body); + + DALI_TEST_CHECK(physicsActor); + int id = ballActor[Actor::Property::ID]; + + DALI_TEST_EQUALS(physicsActor.GetId(), id, TEST_LOCATION); + DALI_TEST_EQUALS(physicsActor.GetBody().Get(), body, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorGetPhysicsActor(void) +{ + tet_infoline("Test that an actor/body pair can be retrieved"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + auto scene = application.GetScene(); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + scene.Add(rootActor); + + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + + btRigidBody* body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New("gallery-small-1.jpg"); + auto physicsActor = adaptor.AddActorBody(ballActor, body); + + DALI_TEST_CHECK(physicsActor); + + PhysicsActor testActor = adaptor.GetPhysicsActor(body); + DALI_TEST_CHECK(testActor); + DALI_TEST_CHECK(physicsActor == testActor); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorBuildPickingRay(void) +{ + tet_infoline("Test that a touch can be converted to a picking ray"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + Vector3 from, to; + adaptor.BuildPickingRay(Vector3(), -Vector3::ZAXIS, from, to); + + DALI_TEST_EQUALS(from, Vector3(), 0.001f, TEST_LOCATION); + DALI_TEST_EQUALS(to, Vector3(0.0f, 0.0f, -20000.0f), 0.001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorProjectPoint(void) +{ + tet_infoline("Test that a point is projected into physics space"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + + // distance is in physics units, not DALi units! + Vector3 projectedPoint = adaptor.ProjectPoint(Vector3(), -Vector3::ZAXIS, 200); + + DALI_TEST_EQUALS(projectedPoint, Vector3(0.0f, 0.0f, -200.0f), 0.001f, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliPhysicsAdaptorQueue(void) +{ + tet_infoline("Test that Queue and CreateSyncPoint both work"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + btRigidBody* body{nullptr}; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New("gallery-small-1.jpg"); + auto physicsActor = adaptor.AddActorBody(ballActor, body); + } + + tet_infoline("Test that Queue works without accessor"); + adaptor.Queue([body]() { + body->getWorldTransform().setOrigin(btVector3(100.0f, 20.0f, 20.0f)); + }); + adaptor.CreateSyncPoint(); + + application.SendNotification(); + application.Render(); + // Should trigger an Update + + { + auto accessor = adaptor.GetPhysicsAccessor(); + + btVector3 origin = body->getWorldTransform().getOrigin(); + DALI_TEST_EQUALS(origin.x(), 100.0f, 0.001f, TEST_LOCATION); + DALI_TEST_EQUALS(origin.y(), 20.0f, 0.001f, TEST_LOCATION); + DALI_TEST_EQUALS(origin.z(), 20.0f, 0.001f, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsAdaptorCreateSyncPoint(void) +{ + tet_infoline("Test that a delayed CreateSyncPoint delays update"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 2.0f)); + Uint16Pair size(640, 480); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + btRigidBody* body{nullptr}; + { + auto accessor = adaptor.GetPhysicsAccessor(); + auto bulletWorld = accessor->GetNative().Get(); + body = CreateBody(bulletWorld); + Dali::Actor ballActor = Toolkit::ImageView::New("gallery-small-1.jpg"); + auto physicsActor = adaptor.AddActorBody(ballActor, body); + + tet_infoline("Test that Queue works with accessor"); + adaptor.Queue([body]() { + body->getWorldTransform().setOrigin(btVector3(100.0f, 20.0f, 20.0f)); + }); + } + + // Should trigger an Update without processing queue + application.SendNotification(); + application.Render(); + + { + auto accessor = adaptor.GetPhysicsAccessor(); + + btVector3 origin = body->getWorldTransform().getOrigin(); + DALI_TEST_EQUALS(origin.x(), 0.0f, 0.001f, TEST_LOCATION); + DALI_TEST_EQUALS(origin.y(), 0.0f, 0.001f, TEST_LOCATION); + DALI_TEST_EQUALS(origin.z(), 0.0f, 0.001f, TEST_LOCATION); + } + + // Should now execute queue + adaptor.CreateSyncPoint(); + application.SendNotification(); + application.Render(); + + { + auto accessor = adaptor.GetPhysicsAccessor(); + + btVector3 origin = body->getWorldTransform().getOrigin(); + DALI_TEST_EQUALS(origin.x(), 100.0f, 0.001f, TEST_LOCATION); + DALI_TEST_EQUALS(origin.y(), 20.0f, 0.001f, TEST_LOCATION); + DALI_TEST_EQUALS(origin.z(), 20.0f, 0.001f, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliPhysicsAdaptorHitTestP(void) +{ + tet_infoline("Test that hit testing finds a body"); + + ToolkitTestApplication application; + Matrix transform(false); + transform.SetIdentityAndScale(Vector3(1.0f, 1.0f, 1.0f)); + Uint16Pair size(TestApplication::DEFAULT_SURFACE_WIDTH, TestApplication::DEFAULT_SURFACE_HEIGHT); + const Vector2 center(TestApplication::DEFAULT_SURFACE_WIDTH * 0.5f, TestApplication::DEFAULT_SURFACE_HEIGHT * 0.5f); + PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size); + Actor rootActor = adaptor.GetRootActor(); + auto scene = application.GetScene(); + scene.Add(rootActor); + + { + auto accessor = adaptor.GetPhysicsAccessor(); // Prevent integration + auto bulletWorld = accessor->GetNative().Get(); + Dali::Actor ballActor = Toolkit::ImageView::New(TEST_RESOURCE_DIR "/gallery-small-1.jpg"); + btRigidBody* body = CreateBody(bulletWorld); + body->getWorldTransform().setOrigin(btVector3(0.f, 0.f, 0.f)); + + ballActor[Actor::Property::PARENT_ORIGIN] = ParentOrigin::CENTER; + ballActor[Actor::Property::ANCHOR_POINT] = AnchorPoint::CENTER; + + auto physicsActor = adaptor.AddActorBody(ballActor, body); + } + Test::WaitForEventThreadTrigger(1); + + // Should trigger an Update without processing queue + application.SendNotification(); + application.Render(); + + Vector3 origin, direction; + Dali::HitTestAlgorithm::BuildPickingRay(scene.GetRenderTaskList().GetTask(0), center, origin, direction); + Vector3 from, to; + adaptor.BuildPickingRay(origin, direction, from, to); // Hit test centre of screen + + { + auto accessor = adaptor.GetPhysicsAccessor(); + Vector3 localPivot; + float distanceFromCamera; + auto body = accessor->HitTest(from, to, localPivot, distanceFromCamera); + + DALI_TEST_CHECK(!body.Empty()); + } + + END_TEST; +} + +// todo: +// Hit Test +// PhysicsDebugRenderer.... Elide?! diff --git a/automated-tests/src/dali-toolkit/CMakeLists.txt b/automated-tests/src/dali-toolkit/CMakeLists.txt index f11bfbf..ec10a33 100755 --- a/automated-tests/src/dali-toolkit/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit/CMakeLists.txt @@ -7,7 +7,6 @@ SET(CAPI_LIB "dali-toolkit") # List of test case sources (Only these get parsed for test cases) SET(TC_SOURCES - toolkit-direct-rendering-egl.cpp utc-Dali-Alignment.cpp utc-Dali-AnimatedImageVisual.cpp utc-Dali-AnimatedVectorImageVisual.cpp @@ -103,6 +102,7 @@ SET(TEST_HARNESS_SOURCES dali-toolkit-test-utils/toolkit-application.cpp dali-toolkit-test-utils/toolkit-canvas-renderer.cpp dali-toolkit-test-utils/toolkit-clipboard.cpp + dali-toolkit-test-utils/toolkit-direct-rendering-egl.cpp dali-toolkit-test-utils/toolkit-event-thread-callback.cpp dali-toolkit-test-utils/toolkit-environment-variable.cpp dali-toolkit-test-utils/toolkit-input-method-context.cpp diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-harness.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-harness.cpp index 31080a4..4237327 100644 --- a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-harness.cpp +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-harness.cpp @@ -510,7 +510,11 @@ int32_t RunAllInParallel(const char* processName, ::testcase tc_array[], std::st std::chrono::steady_clock::duration timeSpan = endTime - tc.second.startTime; double seconds = double(timeSpan.count()) * std::chrono::steady_clock::period::num / std::chrono::steady_clock::period::den; - if(seconds > MAXIMUM_CHILD_LIFETIME) + if(4.9999 < seconds && seconds < 5 && !tc.second.finished) + { + printf("Child process %s is delayed: WCHAN:%s\n", tc.second.name, GetWChan(tc.first).c_str()); + } + else if(seconds > MAXIMUM_CHILD_LIFETIME) { // Kill the child process. A subsequent call to waitpid will process signal result below. if(!tc.second.finished) diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-direct-rendering-egl.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-direct-rendering-egl.cpp new file mode 100644 index 0000000..198c849 --- /dev/null +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-direct-rendering-egl.cpp @@ -0,0 +1,138 @@ +/* +* 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 +extern "C" +{ + // Flag to be set when we want shader compilation fail + bool gDirectRenderingFailCreateShader = false; + + // Flag to be set when we want program linking fail + bool gDirectRenderingFailCreateProgram = false; + + /** + * To test the multithreaded variant we need override EGL api + * + * The Direct Rendering uses GL directly and it's needed to override certain funtions in order + * to force code execution. + */ + EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint* attrib_list) + { + return EGLContext(0x12345678); + } + + EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig* configs, EGLint config_size, EGLint* num_config) + { + static EGLConfig config; + if(num_config) + { + *num_config = 1; + } + if(configs) + { + configs[0] = config; + } + + return EGL_TRUE; + } + + GLuint glCreateProgram(void) + { + static uint32_t programId = 1; + return programId++; + } + + GLuint glCreateShader(GLenum type) + { + static uint32_t shaderId = 1; + return shaderId++; + } + + void glCompileShader(GLuint shader) + { + } + + void glLinkProgram(GLuint program) + { + } + + void glGenTextures(GLsizei n, GLuint* textures) + { + static GLuint texId = 1u; + for(auto i = 0; i < n; ++i) + { + textures[i] = texId++; + } + } + + void glGetShaderiv(GLuint shader, GLenum pname, GLint* params) + { + if(pname == GL_COMPILE_STATUS) + { + params[0] = gDirectRenderingFailCreateShader ? GL_FALSE : GL_TRUE; + } + else if(pname == GL_INFO_LOG_LENGTH) + { + params[0] = 4; + } + } + + void glGetProgramiv(GLuint shader, GLenum pname, GLint* params) + { + if(pname == GL_LINK_STATUS) + { + params[0] = gDirectRenderingFailCreateProgram ? GL_FALSE : GL_TRUE; + } + else if(pname == GL_INFO_LOG_LENGTH) + { + params[0] = 4; + } + } + + void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog) + { + infoLog[0] = '0'; + infoLog[1] = '\n'; + } + + void glGetProgramInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog) + { + infoLog[0] = '0'; + infoLog[1] = '\n'; + } + + void glDeleteSync(GLsync sync) + { + } + + GLenum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) + { + return GL_CONDITION_SATISFIED; + } + + GLsync glFenceSync(GLenum condition, GLbitfield flags) + { + static uint32_t syncId = 0; + return reinterpret_cast(++syncId); + } + + GLenum glCheckFramebufferStatus(GLenum target) + { + return GL_FRAMEBUFFER_COMPLETE; + } +} \ No newline at end of file diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.cpp index 1e36828..ac967f8 100644 --- a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.cpp +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.cpp @@ -33,7 +33,7 @@ using AdaptorImpl = Dali::Internal::Adaptor::Adaptor; ToolkitTestApplication::ToolkitTestApplication(size_t surfaceWidth, size_t surfaceHeight, float horizontalDpi, float verticalDpi) : TestApplication(surfaceWidth, surfaceHeight, horizontalDpi, verticalDpi, false /* Do not Initialize Core */), - mMainWindow(new Dali::Window), + mMainWindow(), mAdaptor(&AdaptorImpl::New()) // Need to create Adaptor first as many singletons in dali-adaptor need it { // Create Core next @@ -41,8 +41,8 @@ ToolkitTestApplication::ToolkitTestApplication(size_t surfaceWidth, size_t surfa // Override Scene creation in TestApplication by creating a window. // The window will create a Scene & surface and set up the scene's surface appropriately. - *mMainWindow = Window::New(PositionSize(0, 0, surfaceWidth, surfaceHeight), ""); - mScene = AdaptorImpl::GetScene(*mMainWindow); + mMainWindow = Window::New(PositionSize(0, 0, surfaceWidth, surfaceHeight), ""); + mScene = AdaptorImpl::GetScene(mMainWindow); mScene.SetDpi(Vector2(horizontalDpi, verticalDpi)); // Create render target for the scene @@ -57,7 +57,7 @@ ToolkitTestApplication::ToolkitTestApplication(size_t surfaceWidth, size_t surfa Accessibility::Accessible::SetObjectRegistry(mCore->GetObjectRegistry()); // This will also emit the window created signals - AdaptorImpl::GetImpl(*mAdaptor).Start(*mMainWindow); + AdaptorImpl::GetImpl(*mAdaptor).Start(mMainWindow); AdaptorImpl::GetImpl(*mAdaptor).SetApplication(*this); Dali::LifecycleController lifecycleController = Dali::LifecycleController::Get(); diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.h b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.h index 49a4c6f..6d3c511 100644 --- a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.h +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEST_APPLICATION_H /* - * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * 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. @@ -61,9 +61,13 @@ public: */ void RunIdles(); -private: + Dali::Window GetWindow() + { + return mMainWindow; + } - std::unique_ptr mMainWindow; +private: + Dali::Window mMainWindow; std::unique_ptr< Adaptor > mAdaptor; }; diff --git a/automated-tests/src/dali-toolkit/toolkit-direct-rendering-egl.cpp b/automated-tests/src/dali-toolkit/toolkit-direct-rendering-egl.cpp deleted file mode 100644 index 35ee43e..0000000 --- a/automated-tests/src/dali-toolkit/toolkit-direct-rendering-egl.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* -* Copyright (c) 2022 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 -extern "C" -{ - -// Flag to be set when we want shader compilation fail -bool gDirectRenderingFailCreateShader = false; - -// Flag to be set when we want program linking fail -bool gDirectRenderingFailCreateProgram = false; - -/** - * To test the multithreaded variant we need override EGL api - * - * The Direct Rendering uses GL directly and it's needed to override certain funtions in order - * to force code execution. - */ -EGLContext eglCreateContext (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list) -{ - return EGLContext(0x12345678); -} - -EGLBoolean eglGetConfigs (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config) -{ - static EGLConfig config; - if(num_config) - { - *num_config = 1; - } - if(configs) - { - configs[0] = config; - } - - return EGL_TRUE; -} - -GLuint glCreateProgram (void) -{ - static uint32_t programId = 1; - return programId++; -} - -GLuint glCreateShader(GLenum type) -{ - static uint32_t shaderId = 1; - return shaderId++; -} - -void glCompileShader(GLuint shader) -{ -} - -void glLinkProgram (GLuint program) -{ -} - -void glGenTextures(GLsizei n, GLuint *textures) -{ - static GLuint texId = 1u; - for(auto i = 0; i < n; ++i) - { - textures[i] = texId++; - } -} - -void glGetShaderiv(GLuint shader, GLenum pname, GLint *params) -{ - if(pname == GL_COMPILE_STATUS) - { - params[0] = gDirectRenderingFailCreateShader ? GL_FALSE : GL_TRUE; - } - else if(pname == GL_INFO_LOG_LENGTH) - { - params[0] = 4; - } -} - -void glGetProgramiv(GLuint shader, GLenum pname, GLint *params) -{ - if(pname == GL_LINK_STATUS) - { - params[0] = gDirectRenderingFailCreateProgram ? GL_FALSE : GL_TRUE; - } - else if(pname == GL_INFO_LOG_LENGTH) - { - params[0] = 4; - } -} - -void glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) -{ - infoLog[0] = '0'; - infoLog[1] = '\n'; -} - -void glGetProgramInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) -{ - infoLog[0] = '0'; - infoLog[1] = '\n'; -} - -void glDeleteSync (GLsync sync) -{ -} - -GLenum glClientWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout) -{ - return GL_CONDITION_SATISFIED; -} - -GLsync glFenceSync (GLenum condition, GLbitfield flags) -{ - static uint32_t syncId = 0; - return reinterpret_cast(++syncId); -} - -GLenum glCheckFramebufferStatus (GLenum target) -{ - return GL_FRAMEBUFFER_COMPLETE; -} - -} \ No newline at end of file diff --git a/build/tizen/.gitignore b/build/tizen/.gitignore index f09a06e..26782ba 100644 --- a/build/tizen/.gitignore +++ b/build/tizen/.gitignore @@ -4,3 +4,7 @@ dali.info dali2-*-config.cmake libdali2-scene3d.so* dali-shader-generator +build.ninja +rules.ninja +.ninja_deps +.ninja_log diff --git a/build/tizen/dali-physics/CMakeLists.txt b/build/tizen/dali-physics/CMakeLists.txt index d6dbad2..5bf2ff8 100644 --- a/build/tizen/dali-physics/CMakeLists.txt +++ b/build/tizen/dali-physics/CMakeLists.txt @@ -70,25 +70,86 @@ endif() add_subdirectory("${physics_dir}/third-party/chipmunk2d" chipmunk2d) add_subdirectory("${physics_dir}/third-party/bullet3" bullet3) -if (ENABLE_PKG_CONFIGURE) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${core_pkg_cfg_file_2d} ${CMAKE_CURRENT_BINARY_DIR}/${core_pkg_cfg_file_3d} - DESTINATION ${LIB_DIR}/pkgconfig +# TODO: Split into 2 separate targets +set(physics3d_src_files "") +include("${physics_dir}/public-api/file.list") +include("${physics_dir}/internal/file.list") + +set(prefix_include_dir "${prefix}/include") +include_directories(${repo_root_dir} + "${prefix_include_dir}" + "${repo_root_dir}/dali-physics/third-party/bullet3/src" +) + +MESSAGE( STATUS "SOURCES: " ${physics3d_src_files}) + +ADD_LIBRARY("${name}-3d" SHARED ${physics3d_src_files} ) +TARGET_LINK_LIBRARIES("${name}-3d" ${DALICORE_LDFLAGS} + dali2-toolkit + bullet3 + ${COVERAGE}) + +IF (ENABLE_PKG_CONFIGURE) + INSTALL(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${core_pkg_cfg_file_2d} + ${CMAKE_CURRENT_BINARY_DIR}/${core_pkg_cfg_file_3d} + DESTINATION ${LIB_DIR}/pkgconfig ) +ENDIF() + + +IF( INSTALL_CMAKE_MODULES ) + MESSAGE(STATUS "Installing cmake modules & libs") + SET_TARGET_PROPERTIES( ${name}-3d + PROPERTIES + VERSION ${DALI_PHYSICS_VERSION} + SOVERSION ${${name}_VERSION_MAJOR} + CLEAN_DIRECT_OUPUT 1 ) -endif() - -if( INSTALL_CMAKE_MODULES ) - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${name}-2d-config.cmake " + IF( ENABLE_DEBUG ) + SET( BIN_DIR "${BIN_DIR}/debug" ) + SET( LIB_DIR "${LIB_DIR}/debug" ) + ENDIF() + + # Install library + INSTALL( TARGETS ${name}-3d + EXPORT ${name}-3d-targets + LIBRARY DESTINATION ${LIB_DIR} + ARCHIVE DESTINATION ${LIB_DIR} + RUNTIME DESTINATION ${BIN_DIR} + ) + + # Install the cmake modules. + INSTALL( + EXPORT ${name}-3d-targets + NAMESPACE ${name}-3d:: + FILE ${name}-3d-targets.cmake + DESTINATION share/${name}-3d + ) + + FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${name}-2d-config.cmake " include(CMakeFindDependencyMacro) include(\${CMAKE_CURRENT_LIST_DIR}/${name}-2d-targets.cmake) ") - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${name}-2d-config.cmake DESTINATION share/${name}-2d) + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${name}-2d-config.cmake DESTINATION share/${name}-2d) - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${name}-3d-config.cmake " + FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${name}-3d-config.cmake " include(CMakeFindDependencyMacro) include(\${CMAKE_CURRENT_LIST_DIR}/${name}-3d-targets.cmake) ") - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${name}-3d-config.cmake DESTINATION share/${name}-3d) -endif() + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${name}-3d-config.cmake DESTINATION share/${name}-3d) + +ELSE() + MESSAGE(STATUS "Installing libs") + INSTALL( TARGETS ${name}-3d DESTINATION ${LIB_DIR} ) + +ENDIF() +# Install headers +install( FILES ${physics3d_public_api_header_files} + DESTINATION "${INCLUDE_DIR}/dali-physics/public-api" + ) +install( FILES ${physics_dir}/dali-physics.h + DESTINATION "${INCLUDE_DIR}/dali-physics" + ) diff --git a/build/tizen/dali-physics/dali2-physics-3d.pc.in b/build/tizen/dali-physics/dali2-physics-3d.pc.in index 739026b..b5e7001 100644 --- a/build/tizen/dali-physics/dali2-physics-3d.pc.in +++ b/build/tizen/dali-physics/dali2-physics-3d.pc.in @@ -8,5 +8,5 @@ Name: DALi Engine Physics Library Description: Dali Physics 3D library Version: ${apiversion} Requires: bullet3 -Libs: -L${libdir} +Libs: -L${libdir} -ldali2-physics-3d Cflags: -I${includedir} diff --git a/dali-physics/README.md b/dali-physics/README.md new file mode 100644 index 0000000..a473a61 --- /dev/null +++ b/dali-physics/README.md @@ -0,0 +1,13 @@ +# Overview + +The physics library is a small frontend to Chipmunk or Bullet libraries. It +provides a safe mechanism to initialize and access the physics world from the +event thread, whilst running the integration step in the Update thread. + +A queueing mechanism is provided to execute native functions on the Update +thread prior to running the integration step. (This enables DALi properties +and physics properties (e.g. position or orientation) to be synced in the same frame) + +DALi actors that are mapped to physics bodies are automatically updated after +the integration step. + diff --git a/dali-physics/dali-physics.h b/dali-physics/dali-physics.h new file mode 100644 index 0000000..7e8135b --- /dev/null +++ b/dali-physics/dali-physics.h @@ -0,0 +1,20 @@ +#pragma once + +/* + * 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 diff --git a/dali-physics/internal/file.list b/dali-physics/internal/file.list new file mode 100644 index 0000000..9b2cf6e --- /dev/null +++ b/dali-physics/internal/file.list @@ -0,0 +1,8 @@ +set(physics3d_internal_dir "${physics_dir}/internal") + +set(physics3d_src_files ${physics3d_src_files} + ${physics3d_internal_dir}/physics-actor-impl.cpp + ${physics3d_internal_dir}/physics-adaptor-impl.cpp + ${physics3d_internal_dir}/physics-debug-renderer.cpp + ${physics3d_internal_dir}/physics-world-impl.cpp +) diff --git a/dali-physics/internal/physics-actor-impl.cpp b/dali-physics/internal/physics-actor-impl.cpp new file mode 100644 index 0000000..9f9d467 --- /dev/null +++ b/dali-physics/internal/physics-actor-impl.cpp @@ -0,0 +1,111 @@ +/* + * 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. + */ + +// Class Header +#include +#include + +namespace Dali::Toolkit::Physics::Internal +{ +namespace +{ +inline btVector3 fromVec3(Dali::Vector3 vec3) +{ + return btVector3(vec3.x, vec3.y, vec3.z); +} +inline Dali::Vector3 toVec3(btVector3 vec3) +{ + return Dali::Vector3(vec3.x(), vec3.y(), vec3.z()); +} + +inline btQuaternion fromQuat(Dali::Quaternion q) +{ + return btQuaternion(q.mVector.x, q.mVector.y, q.mVector.z, q.mVector.w); +} +inline Dali::Quaternion toQuat(btQuaternion q) +{ + return Dali::Quaternion(q.w(), q.x(), q.y(), q.z()); +} + +} //Anonymous namespace + +PhysicsActorPtr PhysicsActor::New(Dali::Actor actor, Dali::Any body, Dali::Toolkit::Physics::Internal::PhysicsAdaptor& adaptor) +{ + PhysicsActorPtr physicsActor(new Internal::PhysicsActor(actor, body, adaptor)); + physicsActor->Initialize(); + return physicsActor; +} + +PhysicsActor::PhysicsActor(Dali::Actor actor, Dali::Any body, PhysicsAdaptor& adaptor) +: mAdaptor(adaptor), + mActorId(actor.GetProperty(Dali::Actor::Property::ID)), + mBody(body) +{ +} + +PhysicsActor::~PhysicsActor() = default; + +void PhysicsActor::Initialize(void) +{ + // RegisterObject? +} + +void PhysicsActor::AsyncSetPhysicsPosition(Dali::Vector3 actorPosition) +{ + // Queue task + btRigidBody* body = mBody.Get(); + auto pos = fromVec3(mAdaptor.TranslateToPhysicsSpace(actorPosition)); + mAdaptor.Queue([body, pos] { body->getWorldTransform().setOrigin(pos); }); +} + +void PhysicsActor::AsyncSetPhysicsRotation(Dali::Quaternion rotation) +{ + // Queue task + btRigidBody* body = mBody.Get(); + auto q = fromQuat(mAdaptor.TranslateToPhysicsSpace(rotation)); + mAdaptor.Queue([body, q]() { body->getWorldTransform().setRotation(q); }); +} + +Dali::Vector3 PhysicsActor::GetPhysicsPosition() const +{ + btRigidBody* body = mBody.Get(); + return toVec3(body->getWorldTransform().getOrigin()); +} + +Dali::Quaternion PhysicsActor::GetPhysicsRotation() const +{ + btRigidBody* body = mBody.Get(); + return toQuat(body->getWorldTransform().getRotation()); +} + +Dali::Vector3 PhysicsActor::GetActorPosition() const +{ + btRigidBody* body = mBody.Get(); + const btTransform& transform = body->getWorldTransform(); + const btVector3& position = transform.getOrigin(); + return mAdaptor.TranslateFromPhysicsSpace(Vector3(position.getX(), position.getY(), position.getZ())); +} + +Dali::Quaternion PhysicsActor::GetActorRotation() const +{ + btRigidBody* body = mBody.Get(); + const btTransform& transform = body->getWorldTransform(); + btQuaternion q = transform.getRotation(); + return mAdaptor.TranslateFromPhysicsSpace(Quaternion(q.w(), q.x(), q.y(), q.z())); + +} + +} // namespace Dali::Toolkit::Physics::Internal diff --git a/dali-physics/internal/physics-actor-impl.h b/dali-physics/internal/physics-actor-impl.h new file mode 100644 index 0000000..583bbfa --- /dev/null +++ b/dali-physics/internal/physics-actor-impl.h @@ -0,0 +1,123 @@ +#ifndef DALI_TOOLKIT_PHYSICS_INTERNAL_ACTOR_H +#define DALI_TOOLKIT_PHYSICS_INTERNAL_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. + */ + +// EXTERNAL INCLUDES +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include + +namespace Dali::Toolkit::Physics +{ +namespace Internal +{ +class PhysicsActor; +class PhysicsAdaptor; + +using PhysicsActorPtr = Dali::IntrusivePtr; + +class DALI_TOOLKIT_API PhysicsActor : public Dali::BaseObject +{ +public: + PhysicsActor(Dali::Actor actor, Dali::Any body, PhysicsAdaptor& adaptor); + ~PhysicsActor() override; + PhysicsActor(const PhysicsActor& handle) = delete; + PhysicsActor& operator=(const PhysicsActor& handle) = delete; + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::New() + */ + static PhysicsActorPtr New(Dali::Actor actor, Dali::Any body, PhysicsAdaptor& adaptor); + + void Initialize(void); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::GetId + */ + uint32_t GetId() const + { + return mActorId; + } + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::GetBody + */ + Dali::Any GetBody() const + { + return mBody; + } + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::AsyncSetPhysicsPosition + */ + void AsyncSetPhysicsPosition(Dali::Vector3 actorPosition); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::AsyncSetPhysicsRotation + */ + void AsyncSetPhysicsRotation(Dali::Quaternion actorRotation); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::GetPhysicsPosition + */ + Dali::Vector3 GetPhysicsPosition() const; + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::GetPhysicsRotation + */ + Dali::Quaternion GetPhysicsRotation() const; + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::GetActorPosition + */ + Dali::Vector3 GetActorPosition() const; + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsActor::GetActorRotation + */ + Dali::Quaternion GetActorRotation() const; + +private: + PhysicsAdaptor& mAdaptor; + uint32_t mActorId{0}; + Dali::Any mBody; +}; + +} // namespace Internal + +inline Internal::PhysicsActor& GetImplementation(Physics::PhysicsActor& physics) +{ + DALI_ASSERT_ALWAYS(physics && "Physics actor handle is empty"); + BaseObject& handle = physics.GetBaseObject(); + return static_cast(handle); +} + +inline const Internal::PhysicsActor& GetImplementation(const Physics::PhysicsActor& physics) +{ + DALI_ASSERT_ALWAYS(physics && "Physics actor handle is empty"); + const BaseObject& handle = physics.GetBaseObject(); + return static_cast(handle); +} + +} // namespace Dali::Toolkit::Physics + +#endif //DALI_TOOLKIT_PHYSICS_INTERNAL_ACTOR_H diff --git a/dali-physics/internal/physics-adaptor-impl.cpp b/dali-physics/internal/physics-adaptor-impl.cpp new file mode 100644 index 0000000..dd20c0b --- /dev/null +++ b/dali-physics/internal/physics-adaptor-impl.cpp @@ -0,0 +1,347 @@ +/* + * 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. + */ + +// Class Header +#include + +// External Headers +#include +#include +#include +#include + +// Internal Headers +#include +#include +#include +#include +#include + +namespace +{ +#if defined(DEBUG_ENABLED) +Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_PHYSICS"); +#endif + +inline btVector3 ConvertVector(Dali::Vector3 vector) +{ + return btVector3(vector.x, vector.y, vector.z); +} + +} // namespace + +namespace Dali::Toolkit::Physics +{ +struct PhysicsAdaptor::ScopedPhysicsAccessor::Impl +{ + Impl(Internal::PhysicsWorld& world) + : mLock(world.GetMutex()), + mPhysicsWorld(world) + { + } + Impl(Impl&) = delete; + const Impl& operator=(const Impl&) = delete; + + Dali::Mutex::ScopedLock mLock; + Internal::PhysicsWorld& mPhysicsWorld; + friend Internal::PhysicsAdaptor; +}; + +PhysicsAdaptor::ScopedPhysicsAccessor::ScopedPhysicsAccessor(Internal::PhysicsWorld& world) +: mImpl(new Impl(world)) +{ +} +PhysicsAdaptor::ScopedPhysicsAccessor::~ScopedPhysicsAccessor() +{ + delete mImpl; +} + +Dali::Any PhysicsAdaptor::ScopedPhysicsAccessor::GetNative() +{ + return mImpl->mPhysicsWorld.GetNative(); +} + +Dali::Any PhysicsAdaptor::ScopedPhysicsAccessor::HitTest( + Dali::Vector3 rayFromWorld, Dali::Vector3 rayToWorld, Dali::Vector3& localPivot, float& distanceFromCamera) +{ + return mImpl->mPhysicsWorld.HitTest(rayFromWorld, rayToWorld, localPivot, distanceFromCamera); +} + +namespace Internal +{ +PhysicsAdaptorPtr PhysicsAdaptor::New(const Dali::Matrix& transform, Uint16Pair worldSize) +{ + PhysicsAdaptorPtr adaptor(new PhysicsAdaptor()); + adaptor->Initialize(transform, worldSize); + return adaptor; +} + +PhysicsAdaptor::PhysicsAdaptor() +: mSlotDelegate(this) +{ +} + +PhysicsAdaptor::~PhysicsAdaptor() +{ + // @todo Ensure physics bodies don't leak +} + +void PhysicsAdaptor::Initialize(const Dali::Matrix& transform, Uint16Pair worldSize) +{ + mTransform = transform; + mInverseTransform = transform; + mInverseTransform.Invert(); + mSize = worldSize; + + // Create an actor that can handle mouse events. + // @todo Enable this to be fully configured / provided + mRootActor = Layer::New(); + mRootActor[Layer::Property::BEHAVIOR] = Layer::LAYER_3D; + mRootActor[Layer::Property::DEPTH_TEST] = true; + mRootActor[Actor::Property::SIZE] = Vector2(mSize.GetWidth(), mSize.GetHeight()); + mRootActor[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; + mRootActor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER; + + mPhysicsWorld = PhysicsWorld::New(mRootActor, + Dali::MakeCallback(mSlotDelegate.GetSlot(), + &PhysicsAdaptor::OnUpdateActors)); +} + +void PhysicsAdaptor::SetTimestep(float timestep) +{ + mPhysicsWorld->SetTimestep(timestep); +} + +float PhysicsAdaptor::GetTimestep() +{ + return mPhysicsWorld->GetTimestep(); +} + +Physics::PhysicsAdaptor::ScopedPhysicsAccessorPtr PhysicsAdaptor::GetPhysicsAccessor() +{ + return std::unique_ptr(new Physics::PhysicsAdaptor::ScopedPhysicsAccessor(*mPhysicsWorld.get())); +} + +Layer PhysicsAdaptor::CreateDebugLayer(Dali::Window window) +{ + Layer debugLayer; + + auto renderTaskList = window.GetRenderTaskList(); + auto renderTask = renderTaskList.GetTask(0); + auto windowSize = window.GetSize(); + + debugLayer = Layer::New(); + debugLayer[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; + debugLayer[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER; + + Constraint positionConstraint = Constraint::New(debugLayer, Actor::Property::POSITION, EqualToConstraint()); + positionConstraint.AddSource(Source(mRootActor, Actor::Property::POSITION)); + positionConstraint.Apply(); + Constraint sizeConstraint = Constraint::New(debugLayer, Actor::Property::SIZE, EqualToConstraint()); + sizeConstraint.AddSource(Source(mRootActor, Actor::Property::SIZE)); + sizeConstraint.Apply(); + + mDebugRenderer = PhysicsDebugRenderer::New(windowSize.GetWidth(), windowSize.GetHeight(), renderTask.GetCameraActor(), this); + mDebugActor = DrawableActor::New(*(mDebugRenderer->GetCallback().get())); + mDebugActor[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; + mDebugActor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER; + + Constraint sizeConstraint2 = Constraint::New(mDebugActor, Actor::Property::SIZE, EqualToConstraint()); + sizeConstraint2.AddSource(ParentSource(Actor::Property::SIZE)); + sizeConstraint2.Apply(); + + debugLayer.Add(mDebugActor); + + auto bulletWorld = mPhysicsWorld->GetNative().Get(); + + bulletWorld->setDebugDrawer(mDebugRenderer.get()); + mDebugRenderer->setDebugMode(btIDebugDraw::DBG_DrawWireframe | + btIDebugDraw::DBG_DrawContactPoints | + btIDebugDraw::DBG_DrawNormals); + + window.Add(debugLayer); + return debugLayer; +} + +void PhysicsAdaptor::SetTransformAndSize(const Dali::Matrix& transform, Uint16Pair worldSize) +{ + mTransform = transform; + mInverseTransform = transform; + mInverseTransform.Invert(); + mSize = worldSize; + + GetRootActor()[Actor::Property::SIZE] = Vector3(worldSize.GetWidth(), worldSize.GetHeight(), 0); + + if(mDebugRenderer) + { + Actor layer = mDebugActor.GetParent(); + layer[Actor::Property::SIZE] = Vector3(worldSize); + mDebugRenderer->UpdateWindowSize(worldSize); + } +} + +void PhysicsAdaptor::SetIntegrationState(Physics::PhysicsAdaptor::IntegrationState state) +{ + mPhysicsWorld->SetIntegrationState(state); +} + +Physics::PhysicsAdaptor::IntegrationState PhysicsAdaptor::GetIntegrationState() +{ + return mPhysicsWorld->GetIntegrationState(); +} + +void PhysicsAdaptor::SetDebugState(Physics::PhysicsAdaptor::DebugState state) +{ + mPhysicsWorld->SetDebugState(state); +} + +Physics::PhysicsAdaptor::DebugState PhysicsAdaptor::GetDebugState() +{ + return mPhysicsWorld->GetDebugState(); +} + +PhysicsActorPtr PhysicsAdaptor::AddActorBody(Dali::Actor actor, Dali::Any body) +{ + uint32_t id = static_cast(actor.GetProperty(Actor::Property::ID)); + btRigidBody* btBody = body.Get(); + + btBody->setUserIndex(id); + mPhysicsActors.insert(std::make_pair(id, PhysicsActor::New(actor, body, *this))); + actor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER; + actor[Actor::Property::ANCHOR_POINT] = Dali::AnchorPoint::CENTER; + mRootActor.Add(actor); + return mPhysicsActors.at(id); +} + +PhysicsActorPtr PhysicsAdaptor::GetPhysicsActor(Dali::Any body) +{ + btRigidBody* btBody = body.Get(); + if(btBody) + { + int id = btBody->getUserIndex(); + auto iter = mPhysicsActors.find(id); + if(iter != mPhysicsActors.end()) + { + return iter->second; + } + } + DALI_LOG_ERROR("Body not found in physics actors"); + return nullptr; +} + +Dali::Actor PhysicsAdaptor::GetRootActor() +{ + return mRootActor; +} + +// Convert a position from root actor local space to physics space +Vector3 PhysicsAdaptor::TranslateToPhysicsSpace(Vector3 vector) +{ + Vector4 position = mTransform * Vector4(vector.x, vector.y, vector.z, 1.0f); + return Vector3(position); +} + +// Convert a position from physics space to root actor local space +Vector3 PhysicsAdaptor::TranslateFromPhysicsSpace(Vector3 vector) +{ + Vector4 position = mInverseTransform * Vector4(vector.x, vector.y, vector.z, 1.0f); + return Vector3(position); +} + +Quaternion PhysicsAdaptor::TranslateToPhysicsSpace(Quaternion orientation) +{ + // Naive check for speed. Should pass scale in constructor/transform setter + + if(std::signbit(mTransform.AsFloat()[0])) // mirrored in x + { + return Quaternion(orientation.mVector.w, orientation.mVector.x, -orientation.mVector.y, -orientation.mVector.z); + } + else if(std::signbit(mTransform.AsFloat()[5])) // mirrored in y + { + return Quaternion(orientation.mVector.w, -orientation.mVector.x, orientation.mVector.y, -orientation.mVector.z); + } + else if(std::signbit(mTransform.AsFloat()[10])) // mirrored in z + { + return Quaternion(orientation.mVector.w, -orientation.mVector.x, -orientation.mVector.y, orientation.mVector.z); + } + + // No mirror, so rotation is invariant. + return orientation; +} + +Quaternion PhysicsAdaptor::TranslateFromPhysicsSpace(Quaternion orientation) +{ + // Mirroring conversion is identical in both transforms + return TranslateToPhysicsSpace(orientation); +} + +// Convert a vector from dali space to physics space +Vector3 PhysicsAdaptor::ConvertVectorToPhysicsSpace(Vector3 vector) +{ + Vector4 otherVector(mTransform * Vector4(vector.x, vector.y, vector.z, 0.0f)); + return Vector3(otherVector); +} + +// Convert a vector from physics space to root actor local space +Vector3 PhysicsAdaptor::ConvertVectorFromPhysicsSpace(Vector3 vector) +{ + Vector4 otherVector(mInverseTransform * Vector4(vector.x, vector.y, vector.z, 0.0f)); + return Vector3(otherVector); +} + +void PhysicsAdaptor::BuildPickingRay(Vector3 origin, Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld) +{ + rayFromWorld = TranslateToPhysicsSpace(origin); + // direction from DALI touch is normalized. + // Multiply it up so that it goes through the visible world. (@todo use Space config?) + rayToWorld = TranslateToPhysicsSpace(origin + direction * 10000.0f); +} + +Vector3 PhysicsAdaptor::ProjectPoint(Vector3 origin, Vector3 direction, float distance) +{ + Vector3 rayFromWorld = TranslateToPhysicsSpace(origin); + Vector3 rayToWorld = TranslateToPhysicsSpace(origin + direction * 10000.0f); + + Vector3 dir = rayToWorld - rayFromWorld; + dir.Normalize(); + dir *= distance; // Should compute with scale? + return (rayFromWorld + dir); +} + +void PhysicsAdaptor::OnUpdateActors(Dali::UpdateProxy* updateProxy) +{ + for(auto&& actor : 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); + } +} + +void PhysicsAdaptor::Queue(std::function function) +{ + mPhysicsWorld->Queue(function); +} + +void PhysicsAdaptor::CreateSyncPoint() +{ + mPhysicsWorld->CreateSyncPoint(); +} + +} // namespace Internal +} // namespace Dali::Toolkit::Physics diff --git a/dali-physics/internal/physics-adaptor-impl.h b/dali-physics/internal/physics-adaptor-impl.h new file mode 100644 index 0000000..9c1644a --- /dev/null +++ b/dali-physics/internal/physics-adaptor-impl.h @@ -0,0 +1,207 @@ +#ifndef DALI_TOOLKIT_PHYSICS_INTERNAL_ADAPTOR_H +#define DALI_TOOLKIT_PHYSICS_INTERNAL_ADAPTOR_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 adaptoried. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// EXTERNAL INCLUDES +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include +#include + +namespace Dali::Toolkit::Physics +{ +namespace Internal +{ +class PhysicsAdaptor; +using PhysicsAdaptorPtr = IntrusivePtr; + +class PhysicsAdaptor : public BaseObject +{ +public: + PhysicsAdaptor(); + + /** + * A reference counted object may only be deleted by calling Unreference() + */ + ~PhysicsAdaptor() override; + + // Remove copy constructor and copy assignment + PhysicsAdaptor(const PhysicsAdaptor& handle) = delete; + PhysicsAdaptor& operator=(const PhysicsAdaptor& handle) = delete; + + static PhysicsAdaptorPtr New(const Dali::Matrix& transform, Uint16Pair size); + + /** + * 2nd stage initialization + */ + void Initialize(const Dali::Matrix& transform, Uint16Pair size); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetTimestep + */ + void SetTimestep(float timestep); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetTimestep + */ + float GetTimestep(); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetPhysicsAccessor + */ + Physics::PhysicsAdaptor::ScopedPhysicsAccessorPtr GetPhysicsAccessor(); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::CreateDebugLayer + */ + Dali::Layer CreateDebugLayer(Dali::Window window); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateToPhysicsSpace + */ + Dali::Vector3 TranslateToPhysicsSpace(Dali::Vector3 vector); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateToPhysicsSpace + */ + Dali::Quaternion TranslateToPhysicsSpace(Dali::Quaternion rotation); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateFromPhysicsSpace + */ + Dali::Vector3 TranslateFromPhysicsSpace(Dali::Vector3 vector); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateFromPhysicsSpace + */ + Dali::Quaternion TranslateFromPhysicsSpace(Quaternion rotation); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ConvertVectorToPhysicsSpace + */ + Dali::Vector3 ConvertVectorToPhysicsSpace(Dali::Vector3 vector); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ConvertVectorFromPhysicsSpace + */ + Dali::Vector3 ConvertVectorFromPhysicsSpace(Dali::Vector3 vector); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetTransformAndSize + */ + void SetTransformAndSize(const Dali::Matrix& transform, Uint16Pair size); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetIntegrationState + */ + void SetIntegrationState(Physics::PhysicsAdaptor::IntegrationState state); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetIntegrationState + */ + Physics::PhysicsAdaptor::IntegrationState GetIntegrationState(); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetDebugState + */ + void SetDebugState(Physics::PhysicsAdaptor::DebugState state); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetDebugState + */ + Physics::PhysicsAdaptor::DebugState GetDebugState(); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::AddActorBody + */ + PhysicsActorPtr AddActorBody(Dali::Actor actor, Dali::Any body); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetPhysicsActor + */ + PhysicsActorPtr GetPhysicsActor(Dali::Any body); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetRootActor + */ + Dali::Actor GetRootActor(); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::BuildPickingRay + */ + void BuildPickingRay(Dali::Vector3 origin, Dali::Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ProjectPoint + */ + Dali::Vector3 ProjectPoint(Dali::Vector3 origin, Dali::Vector3 direction, float distance); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::Queue + */ + void Queue(std::function function); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::Queue + */ + void CreateSyncPoint(); + +private: + /** + * Handle the update of all of the known bound actors + */ + void OnUpdateActors(Dali::UpdateProxy* updateProxy); + +private: + std::unique_ptr mPhysicsWorld; + std::unordered_map mPhysicsActors; + Dali::Actor mRootActor; + Dali::Actor mDebugActor; + + Dali::Matrix mTransform; + Dali::Matrix mInverseTransform; + Dali::Uint16Pair mSize; + + std::unique_ptr mDebugRenderer; + Dali::SlotDelegate mSlotDelegate; +}; + +} //namespace Internal + +inline Internal::PhysicsAdaptor& GetImplementation(Dali::Toolkit::Physics::PhysicsAdaptor& handle) +{ + DALI_ASSERT_ALWAYS(handle && "Physics adaptor handle is empty"); + BaseObject& object = handle.GetBaseObject(); + return static_cast(object); +} + +inline const Internal::PhysicsAdaptor& GetImplementation(const Dali::Toolkit::Physics::PhysicsAdaptor& handle) +{ + DALI_ASSERT_ALWAYS(handle && "Physics adaptor handle is empty"); + const BaseObject& object = handle.GetBaseObject(); + return static_cast(object); +} + +} // namespace Dali::Toolkit::Physics + +#endif //DALI_TOOLKIT_PHYSICS_INTERNAL_ADAPTOR_H diff --git a/dali-physics/internal/physics-debug-renderer.cpp b/dali-physics/internal/physics-debug-renderer.cpp new file mode 100644 index 0000000..c6a1421 --- /dev/null +++ b/dali-physics/internal/physics-debug-renderer.cpp @@ -0,0 +1,237 @@ +/* + * 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-debug-renderer.h" + +#include +#include "physics-adaptor-impl.h" + +using Dali::Degree; +using Dali::Matrix; +using Dali::Quaternion; +using Dali::Radian; +using Dali::Vector3; +using Dali::Vector4; + +namespace +{ +GLuint LoadShader(GLenum shaderType, const char* shaderSource) +{ + GLuint shader = glCreateShader(shaderType); + if(shader != 0) + { + glShaderSource(shader, 1, &shaderSource, NULL); + glCompileShader(shader); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if(compiled != GL_TRUE) + { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + + if(infoLen > 0) + { + std::vector logBuffer; + logBuffer.resize(infoLen + 1); + glGetShaderInfoLog(shader, infoLen, NULL, &logBuffer[0]); + fprintf(stderr, "%s\n", &logBuffer[0]); + fflush(stderr); + + glDeleteShader(shader); + shader = 0; + } + } + } + return shader; +} + +GLuint CreateProgram(const char* vertexSource, const char* fragmentSource) +{ + GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vertexSource); + if(!vertexShader) + { + return 0; + } + GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fragmentSource); + if(!fragmentShader) + { + return 0; + } + GLuint program = glCreateProgram(); + if(program) + { + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if(linkStatus != GL_TRUE) + { + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if(bufLength) + { + std::vector logBuffer; + logBuffer.resize(bufLength + 1); + glGetProgramInfoLog(program, bufLength, NULL, &logBuffer[0]); + fprintf(stderr, "%s\n", &logBuffer[0]); + fflush(stderr); + } + glDeleteProgram(program); + program = 0; + } + } + return program; +} +} // namespace + +namespace Dali::Toolkit::Physics::Internal +{ +std::unique_ptr PhysicsDebugRenderer::New(uint32_t width, uint32_t height, Dali::CameraActor camera, PhysicsAdaptor* adaptor) +{ + auto renderer = std::make_unique(width, height, camera, adaptor); + renderer->mRenderCallback = Dali::RenderCallback::New(renderer.get(), &PhysicsDebugRenderer::OnRender); + return renderer; +} + +PhysicsDebugRenderer::PhysicsDebugRenderer(uint32_t width, uint32_t height, Dali::CameraActor camera, PhysicsAdaptor* adaptor) +: mCamera(camera), + mWidth(width), + mHeight(height), + mAdaptor(*adaptor) +{ +} + +bool PhysicsDebugRenderer::OnRender(const Dali::RenderCallbackInput& input) +{ + if(mState == State::INIT) + { + Setup(); + mState = State::RENDER; + } + glViewport(0, 0, mWidth, mHeight); + + RenderLines(input); + + return false; +} + +// Run on first invocation of callback +void PhysicsDebugRenderer::Setup() +{ + PrepareShader(); + mVertexLocation = glGetAttribLocation(mProgramId, "vertexPosition"); + mVertexColourLocation = glGetAttribLocation(mProgramId, "vertexColour"); + mProjectionLocation = glGetUniformLocation(mProgramId, "projection"); + mModelViewLocation = glGetUniformLocation(mProgramId, "modelView"); + + glEnable(GL_DEPTH_TEST); + glViewport(0, 0, mWidth, mHeight); + + glGenBuffers(1, &mBufferId); +} + +void PhysicsDebugRenderer::UpdateWindowSize(Dali::Vector2 size) +{ + mWidth = size.width; + mHeight = size.height; +} + +void PhysicsDebugRenderer::PrepareShader() +{ + static const char glVertexShader[] = + "attribute vec4 vertexPosition;\n" + "attribute vec3 vertexColour;\n" + "varying vec3 fragColour;\n" + "uniform mat4 projection;\n" + "uniform mat4 modelView;\n" + "void main()\n" + "{\n" + " gl_Position = projection * modelView * vertexPosition;\n" + " fragColour = vertexColour;\n" + "}\n"; + + static const char glFragmentShader[] = + "precision mediump float;\n" + "varying vec3 fragColour;\n" + "void main()\n" + "{\n" + " gl_FragColor = vec4(fragColour, 1.0);\n" + "}\n"; + + mProgramId = CreateProgram(glVertexShader, glFragmentShader); +} + +void PhysicsDebugRenderer::RenderLines(const Dali::RenderCallbackInput& input) +{ + mModelViewMatrix.SetIdentity(); + mProjectionMatrix = input.projection; + + Matrix::Multiply(mModelViewMatrix, mModelViewMatrix, input.view); + glUseProgram(mProgramId); + + // In theory, input.clippingBox should tell us the actor position in clip-space. + // But, it appears to be bugged. + + glBindBuffer(GL_ARRAY_BUFFER, mBufferId); + glBufferData(GL_ARRAY_BUFFER, mLines.size() * sizeof(VertexLine), &mLines[0], GL_STATIC_DRAW); + + glVertexAttribPointer(mVertexLocation, 3, GL_FLOAT, GL_FALSE, 24, 0); + glEnableVertexAttribArray(mVertexLocation); + glVertexAttribPointer(mVertexColourLocation, 3, GL_FLOAT, GL_FALSE, 24, reinterpret_cast(12)); + glEnableVertexAttribArray(mVertexColourLocation); + glUniformMatrix4fv(mProjectionLocation, 1, GL_FALSE, mProjectionMatrix.AsFloat()); + glUniformMatrix4fv(mModelViewLocation, 1, GL_FALSE, mModelViewMatrix.AsFloat()); + + glDrawArrays(GL_LINES, 0, mLines.size()); + mLines.clear(); +} + +void PhysicsDebugRenderer::drawLine(const btVector3& from, const btVector3& to, const btVector3& color) +{ + mLines.push_back(VertexLine{mAdaptor.TranslateFromPhysicsSpace(Vector3(from.x(), from.y(), from.z())), + Vector3(color.x(), color.y(), color.z())}); + mLines.push_back(VertexLine{mAdaptor.TranslateFromPhysicsSpace(Vector3(to.x(), to.y(), to.z())), Vector3(color.x(), color.y(), color.z())}); +} + +void PhysicsDebugRenderer::drawLine(const btVector3& from, const btVector3& to, const btVector3& fromColor, const btVector3& toColor) +{ + mLines.push_back(VertexLine{mAdaptor.TranslateFromPhysicsSpace(Vector3(from.x(), from.y(), from.z())), Vector3(fromColor.x(), fromColor.y(), fromColor.z())}); + mLines.push_back(VertexLine{mAdaptor.TranslateFromPhysicsSpace(Vector3(to.x(), to.y(), to.z())), Vector3(toColor.x(), toColor.y(), toColor.z())}); +} + +void PhysicsDebugRenderer::drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color) +{ +} + +void PhysicsDebugRenderer::reportErrorWarning(const char* warningString) +{ +} + +void PhysicsDebugRenderer::draw3dText(const btVector3& location, const char* textString) +{ +} + +void PhysicsDebugRenderer::setDebugMode(int debugMode) +{ +} + +int PhysicsDebugRenderer::getDebugMode() const +{ + return true; +} + +} // namespace Dali::Toolkit::Physics::Internal diff --git a/dali-physics/internal/physics-debug-renderer.h b/dali-physics/internal/physics-debug-renderer.h new file mode 100644 index 0000000..b7909e2 --- /dev/null +++ b/dali-physics/internal/physics-debug-renderer.h @@ -0,0 +1,106 @@ +#pragma once + +/* + * 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 + +using Dali::Actor; +using Dali::CameraActor; +using Dali::Geometry; +using Dali::Renderer; +using Dali::Shader; +using Dali::TextureSet; + +namespace Dali::Toolkit::Physics::Internal +{ +class PhysicsAdaptor; + +class PhysicsDebugRenderer : public btIDebugDraw +{ +public: + // Creates and initializes a new renderer + static std::unique_ptr New(uint32_t width, uint32_t height, Dali::CameraActor camera, PhysicsAdaptor* adaptor); + + /** + * Get the callback (for actor creation) + */ + std::unique_ptr& GetCallback() + { + return mRenderCallback; + } + + void UpdateWindowSize(Dali::Vector2 size); + + /** + * Constructor. + * @param[in] width Width of the renderer - viewport + * @param[in] height Height of the renderer - viewport + */ + PhysicsDebugRenderer(uint32_t width, uint32_t height, Dali::CameraActor camera, PhysicsAdaptor* adaptor); + +public: // Inherited from btIDebugDraw + // Assume this is called during FrameCallback (i.e. in update manager, rather than during render...) + // Generate stack of lines... render, then clear stack. + void drawLine(const btVector3& from, const btVector3& to, const btVector3& color) override; + void drawLine(const btVector3& from, const btVector3& to, const btVector3& fromColor, const btVector3& toColor) override; + void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color) override; + void reportErrorWarning(const char* warningString) override; + void draw3dText(const btVector3& location, const char* textString) override; + void setDebugMode(int debugMode) override; + int getDebugMode() const override; + +private: + bool OnRender(const Dali::RenderCallbackInput& input); + void Setup(); + void PrepareShader(); + void RenderLines(const Dali::RenderCallbackInput& input); + +private: + CameraActor mCamera; + Renderer mDebugRenderer; + std::unique_ptr mRenderCallback; + + enum class State + { + INIT, + RENDER + } mState{State::INIT}; + + struct VertexLine + { + Dali::Vector3 position; + Dali::Vector3 color; + }; + std::vector mLines; + + Dali::Matrix mModelViewMatrix; + Dali::Matrix mViewMatrix; + Dali::Matrix mProjectionMatrix; + int mWidth; + int mHeight; + PhysicsAdaptor& mAdaptor; + GLint mVertexLocation; + GLint mVertexColourLocation; + GLint mProjectionLocation; + GLint mModelViewLocation; + GLuint mBufferId; + GLuint mProgramId; +}; + +} // namespace Dali::Toolkit::Physics::Internal diff --git a/dali-physics/internal/physics-world-impl.cpp b/dali-physics/internal/physics-world-impl.cpp new file mode 100644 index 0000000..d8cd6af --- /dev/null +++ b/dali-physics/internal/physics-world-impl.cpp @@ -0,0 +1,289 @@ +/* + * 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. + */ + +// Class Header +#include + +// External Headers +#include +#include +#include +#include +#include +#include + +// Internal Headers +#include +#include +#include + +namespace Dali::Toolkit::Physics::Internal +{ +/** + * FrameCallback implementation. Will run the OnUpdate method. + */ +class FrameCallback : public Dali::FrameCallbackInterface +{ +public: + /** + * Constructor + */ + explicit FrameCallback(PhysicsWorld& physicsWorld) + : mPhysicsWorld(physicsWorld) + { + } + +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 + { + return mPhysicsWorld.OnUpdate(updateProxy, elapsedSeconds); + } + +private: // Member variables + PhysicsWorld& mPhysicsWorld; +}; + +using NativeWorld = btDiscreteDynamicsWorld*; + +std::unique_ptr PhysicsWorld::New(Dali::Actor rootActor, Dali::CallbackBase* updateCallback) +{ + std::unique_ptr world = std::make_unique(rootActor, updateCallback); + world->Initialize(); + return world; +} + +PhysicsWorld::PhysicsWorld(Dali::Actor rootActor, Dali::CallbackBase* updateCallback) +: mUpdateCallback(updateCallback), + mRootActor(rootActor) +{ + Initialize(); +} + +void PhysicsWorld::Initialize(/*void* dynamicsWorld*/) +{ + // @todo Should enable developer to supply their own created DynamicsWorld. + + mCollisionConfiguration = new btDefaultCollisionConfiguration(); + mDispatcher = new btCollisionDispatcher(mCollisionConfiguration); + mBroadphase = new btDbvtBroadphase(); + mSolver = new btSequentialImpulseConstraintSolver; + mDynamicsWorld = new btDiscreteDynamicsWorld(mDispatcher, mBroadphase, mSolver, mCollisionConfiguration); + + // Automatically start the frame callback. This means everything should + // be accessed with a mutex lock, which is automatically locked when + // ScopedAccessor is used. + mFrameCallback = std::make_unique(*this); + Dali::DevelStage::AddFrameCallback(Dali::Stage::GetCurrent(), *mFrameCallback, mRootActor); + Dali::Stage::GetCurrent().KeepRendering(30); // @todo Remove! +} + +Dali::Any PhysicsWorld::GetNative() +{ + return mDynamicsWorld; +} + +PhysicsWorld::~PhysicsWorld() +{ + Dali::DevelStage::RemoveFrameCallback(Dali::Stage::GetCurrent(), *mFrameCallback); + + Dali::Mutex::ScopedLock lock(mMutex); + + if(mDynamicsWorld) + { + int i; + for(i = mDynamicsWorld->getNumConstraints() - 1; i >= 0; i--) + { + mDynamicsWorld->removeConstraint(mDynamicsWorld->getConstraint(i)); + } + for(i = mDynamicsWorld->getNumCollisionObjects() - 1; i >= 0; i--) + { + btCollisionObject* obj = mDynamicsWorld->getCollisionObjectArray()[i]; + btRigidBody* body = btRigidBody::upcast(obj); + if(body && body->getMotionState()) + { + delete body->getMotionState(); + } + mDynamicsWorld->removeCollisionObject(obj); + delete obj; + } + } + + /* + for (int j = 0; j < m_collisionShapes.size(); j++) + { + btCollisionShape* shape = mCollisionShapes[j]; + delete shape; + } + mCollisionShapes.clear(); + */ + + delete mDynamicsWorld; + delete mSolver; + delete mBroadphase; + delete mDispatcher; + delete mCollisionConfiguration; +} + +Dali::Mutex& PhysicsWorld::GetMutex() +{ + return mMutex; +} + +bool PhysicsWorld::OnUpdate(Dali::UpdateProxy& updateProxy, float elapsedSeconds) +{ + Dali::Mutex::ScopedLock lock(mMutex); + + // Process command queue + if(mNotifySyncPoint != Dali::UpdateProxy::INVALID_SYNC && + mNotifySyncPoint == updateProxy.PopSyncPoint()) + { + do + { + commandQueue.front()(); // Execute the queued methods + commandQueue.pop(); + } while(!commandQueue.empty()); + + mNotifySyncPoint = Dali::UpdateProxy::INVALID_SYNC; + } + + // Perform as many integration steps as needed to handle elapsed time + static float frameTime = 0; + frameTime += elapsedSeconds; + do + { + Integrate(mPhysicsTimeStep); + frameTime -= mPhysicsTimeStep; + } while(frameTime > 0); + + // Update the corresponding actors to their physics spaces + if(mUpdateCallback) + { + Dali::CallbackBase::Execute(*mUpdateCallback, &updateProxy); // Don't care about actor update return + } + + // @todo Check physics world to see if everything is at rest + return true; +} + +void PhysicsWorld::Integrate(float timestep) +{ + if(mPhysicsIntegrateState == Physics::PhysicsAdaptor::IntegrationState::ON) + { + mDynamicsWorld->stepSimulation(timestep); + } + if(mDynamicsWorld->getDebugDrawer() && mPhysicsDebugState == Physics::PhysicsAdaptor::DebugState::ON) + { + mDynamicsWorld->debugDrawWorld(); + } +} + +void PhysicsWorld::SetTimestep(float timeStep) +{ + mPhysicsTimeStep = timeStep; +} + +float PhysicsWorld::GetTimestep() +{ + return mPhysicsTimeStep; +} + +void PhysicsWorld::Queue(std::function function) +{ + if(!mMutex.IsLocked()) // Annoyingly, the dali mutex scoped lock doesn't prevent relocking in the same thread. + { + Dali::Mutex::ScopedLock lock(mMutex); + commandQueue.push(function); + } + else + { + commandQueue.push(function); + } +} + +void PhysicsWorld::CreateSyncPoint() +{ + mNotifySyncPoint = Dali::DevelStage::NotifyFrameCallback(Dali::Stage::GetCurrent(), *mFrameCallback); +} + +inline btVector3 ConvertVector(Dali::Vector3 vector) +{ + return btVector3(vector.x, vector.y, vector.z); +} + +Dali::Any PhysicsWorld::HitTest(Dali::Vector3 rayFromWorld, Dali::Vector3 rayToWorld, Dali::Vector3& localPivot, float& distanceFromCamera) +{ + btRigidBody* hitBody{nullptr}; + + btVector3 origin = ConvertVector(rayFromWorld); + btVector3 end = ConvertVector(rayToWorld); + btCollisionWorld::ClosestRayResultCallback rayResultCallback(origin, end); + rayResultCallback.m_flags |= btTriangleRaycastCallback::kF_UseGjkConvexCastRaytest; + + mDynamicsWorld->rayTest(origin, end, rayResultCallback); + if(rayResultCallback.hasHit()) + { + auto pickPos = rayResultCallback.m_hitPointWorld; + btRigidBody* body = (btRigidBody*)btRigidBody::upcast(rayResultCallback.m_collisionObject); + if(body) + { + if(!(body->isStaticObject() || body->isKinematicObject())) + { + hitBody = body; // Found a dynamic body. + distanceFromCamera = (pickPos - origin).length(); + + btVector3 pivot = body->getCenterOfMassTransform().inverse() * pickPos; + localPivot.x = pivot.x(); + localPivot.y = pivot.y(); + localPivot.z = pivot.z(); + } + } + } + Dali::Any bodyPtr; + if(hitBody) + { + bodyPtr = hitBody; + } + + return bodyPtr; +} + +void PhysicsWorld::SetIntegrationState(Physics::PhysicsAdaptor::IntegrationState state) +{ + mPhysicsIntegrateState = state; +} + +Physics::PhysicsAdaptor::IntegrationState PhysicsWorld::GetIntegrationState() +{ + return mPhysicsIntegrateState; +} + +void PhysicsWorld::SetDebugState(Physics::PhysicsAdaptor::DebugState state) +{ + mPhysicsDebugState = state; +} + +Physics::PhysicsAdaptor::DebugState PhysicsWorld::GetDebugState() +{ + return mPhysicsDebugState; +} + +} // namespace Dali::Toolkit::Physics::Internal diff --git a/dali-physics/internal/physics-world-impl.h b/dali-physics/internal/physics-world-impl.h new file mode 100644 index 0000000..fa6108f --- /dev/null +++ b/dali-physics/internal/physics-world-impl.h @@ -0,0 +1,140 @@ +#ifndef DALI_TOOLKIT_PHYSICS_INTERNAL_PHYSICS_WORLD_H +#define DALI_TOOLKIT_PHYSICS_INTERNAL_PHYSICS_WORLD_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 +#include +#include + +namespace Dali::Toolkit::Physics::Internal +{ +class PhysicsWorld; +class FrameCallback; + +class PhysicsWorld /* : public BaseObject */ +{ +public: + static std::unique_ptr New(Dali::Actor rootActor, Dali::CallbackBase* updateCallback); + + PhysicsWorld(Dali::Actor rootActor, Dali::CallbackBase* updateCallback); + ~PhysicsWorld(); + + void Initialize(/*void* dynamicsWorld*/); + + Dali::Any GetNative(); + + /** + * Set how long the integration should take. + * @param[in] timestep The length of time that the physics integration should take. + */ + void SetTimestep(float timestep); + + /** + * Get the current physics integration timestep + * @return the current physics integration timestep + */ + float GetTimestep(); + + /** + * Queue a function for execution in the update thread, prior to the physics integration. + * Enables syncronization of DALi properties and physics controlled properties. + */ + + void Queue(std::function function); + + /** + * Create a sync point for queued functions. + * + * Ensures that any queued functions are processed after this sync + * point is seen in the Update::FrameCallback, which will be in the + * same frame as any other DALi properties set during this event + * handler invocation. + * + * @param[in] None + */ + void CreateSyncPoint(); + + /** + * Hit test the physics world and return the nearest body. + * + * @param[in] rayFromWorld The origin in physics world space + * @param[in] rayToWorld A point along the direction on the far side of the physics world + * @param[out] localPivot The hit point local to the body + * @param[out] distanceFromCamera The distance of the pick point from the camera + * @return Empty value if no dynamic body found, otherwise a valid ptr to the hit body. + */ + Dali::Any HitTest(Dali::Vector3 rayFromWorld, Dali::Vector3 rayToWorld, Dali::Vector3& localPivot, float& distanceFromCamera); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetIntegrationState + */ + void SetIntegrationState(Physics::PhysicsAdaptor::IntegrationState state); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetIntegrationState + */ + Physics::PhysicsAdaptor::IntegrationState GetIntegrationState(); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetDebugState + */ + void SetDebugState(Physics::PhysicsAdaptor::DebugState state); + + /** + * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetDebugState + */ + Physics::PhysicsAdaptor::DebugState GetDebugState(); + +public: + Dali::Mutex& GetMutex(); // Only for use by adaptor in creating scoped accessor + + bool OnUpdate(Dali::UpdateProxy& updateProxy, float elapsedSeconds); + +private: + void Integrate(float timestep); + +private: + Dali::Mutex mMutex; + + btDiscreteDynamicsWorld* mDynamicsWorld{nullptr}; + btCollisionDispatcher* mDispatcher{nullptr}; + btDefaultCollisionConfiguration* mCollisionConfiguration{nullptr}; + btBroadphaseInterface* mBroadphase{nullptr}; + btSequentialImpulseConstraintSolver* mSolver{nullptr}; + + std::queue> commandQueue; + Dali::UpdateProxy::NotifySyncPoint mNotifySyncPoint; + Dali::CallbackBase* mUpdateCallback{nullptr}; + std::unique_ptr mFrameCallback; + Dali::Actor mRootActor; + float mPhysicsTimeStep{1.0 / 180.0}; + + Physics::PhysicsAdaptor::IntegrationState mPhysicsIntegrateState{Physics::PhysicsAdaptor::IntegrationState::ON}; + Physics::PhysicsAdaptor::DebugState mPhysicsDebugState{Physics::PhysicsAdaptor::DebugState::OFF}; +}; + +} // namespace Dali::Toolkit::Physics::Internal + +#endif //DALI_TOOLKIT_PHYSICS_INTERNAL_PHYSICS_WORLD_H diff --git a/dali-physics/public-api/file.list b/dali-physics/public-api/file.list new file mode 100644 index 0000000..0f454c7 --- /dev/null +++ b/dali-physics/public-api/file.list @@ -0,0 +1,11 @@ +set(physics3d_public_api_dir "${physics_dir}/public-api") + +set(physics3d_src_files ${physics3d_src_files} + ${physics3d_public_api_dir}/physics-actor.cpp + ${physics3d_public_api_dir}/physics-adaptor.cpp +) + +set(physics3d_public_api_header_files + ${physics3d_public_api_dir}/physics-actor.h + ${physics3d_public_api_dir}/physics-adaptor.h +) diff --git a/dali-physics/public-api/physics-actor.cpp b/dali-physics/public-api/physics-actor.cpp new file mode 100644 index 0000000..5058520 --- /dev/null +++ b/dali-physics/public-api/physics-actor.cpp @@ -0,0 +1,89 @@ +/* + * 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. + */ + +// Class Header +#include + +// Internal headers +#include +#include + +namespace Dali::Toolkit::Physics +{ +PhysicsActor::PhysicsActor() = default; +PhysicsActor::~PhysicsActor() = default; +PhysicsActor::PhysicsActor(const PhysicsActor& handle) = default; +PhysicsActor::PhysicsActor(PhysicsActor&& rhs) noexcept = default; +PhysicsActor& PhysicsActor::operator=(const PhysicsActor& handle) = default; +PhysicsActor& PhysicsActor::operator=(PhysicsActor&& handle) noexcept = default; + +PhysicsActor PhysicsActor::New(Dali::Actor actor, Dali::Any body, PhysicsAdaptor adaptor) +{ + Dali::Toolkit::Physics::Internal::PhysicsActorPtr internal = Internal::PhysicsActor::New(actor, body, GetImplementation(adaptor)); + return PhysicsActor(internal.Get()); +} + +PhysicsActor PhysicsActor::DownCast(BaseHandle handle) +{ + return PhysicsActor(dynamic_cast(handle.GetObjectPtr())); +} + +uint32_t PhysicsActor::GetId() const +{ + return GetImplementation(*this).GetId(); +} + +Dali::Any PhysicsActor::GetBody() const +{ + return GetImplementation(*this).GetBody(); +} + +void PhysicsActor::AsyncSetPhysicsPosition(Dali::Vector3 actorPosition) +{ + GetImplementation(*this).AsyncSetPhysicsPosition(actorPosition); +} + +void PhysicsActor::AsyncSetPhysicsRotation(Dali::Quaternion actorRotation) +{ + GetImplementation(*this).AsyncSetPhysicsRotation(actorRotation); +} + +Dali::Vector3 PhysicsActor::GetPhysicsPosition() const +{ + return GetImplementation(*this).GetPhysicsPosition(); +} + +Dali::Quaternion PhysicsActor::GetPhysicsRotation() const +{ + return GetImplementation(*this).GetPhysicsRotation(); +} + +Dali::Vector3 PhysicsActor::GetActorPosition() const +{ + return GetImplementation(*this).GetActorPosition(); +} + +Dali::Quaternion PhysicsActor::GetActorRotation() const +{ + return GetImplementation(*this).GetActorRotation(); +} + +PhysicsActor::PhysicsActor(Internal::PhysicsActor* impl) +: BaseHandle(impl) +{ +} + +} // namespace Dali::Toolkit::Physics diff --git a/dali-physics/public-api/physics-actor.h b/dali-physics/public-api/physics-actor.h new file mode 100644 index 0000000..e4514bd --- /dev/null +++ b/dali-physics/public-api/physics-actor.h @@ -0,0 +1,163 @@ +#ifndef DALI_TOOLKIT_PHYSICS_ACTOR_H +#define DALI_TOOLKIT_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. + */ + +// EXTERNAL INCLUDES +#include +#include +#include + +// INTERNAL INCLUDES +#include + +namespace Dali::Toolkit::Physics +{ +class PhysicsAdaptor; + +namespace Internal +{ +class PhysicsActor; +} + +/** + * Class to associate a physics body (such as btRigidBody*) with a DALi + * actor for rendering. + * + * This object offers methods to modify basic physics properties + * asynchronously, that is, on the Update thread. It is also possible + * for the developer to queue other physics functions on the body by + * using PhysicsWorld::Queue() and passing in a lambda that captures the + * body. + * + * For example: + * btRigidBody* body = physicsActor.GetBody().Get(); + * mPhysicsImpl.Queue([body](){ body->clearForces(); }); + * This enables the developer to synchronize setting physics properties + * and setting DALi actor properties. + */ +class DALI_TOOLKIT_API PhysicsActor : public Dali::BaseHandle +{ +public: + PhysicsActor(); + ~PhysicsActor(); + PhysicsActor(const PhysicsActor& handle); + PhysicsActor(PhysicsActor&& rhs) noexcept; + PhysicsActor& operator=(const PhysicsActor& handle); + PhysicsActor& operator=(PhysicsActor&& handle) noexcept; + + /** + * New method. + * @SINCE_2.2.40 + * + * Binds the actor to the given body. This should be a body that has + * been added to the physics world, and has physical postion and + * rotation in that space. The Actor is used to render that object in + * DALi space. + * + * @note This object only stores the actor ID, which is used + * internally in the FrameCallback. It is up to the caller to ensure + * there is a reference to the actor (e.g. by parenting into the scene) + * + * @note This API is used internally, and is of little benefit to the developer. + * + * @param[in] actor The DALi actor used to render this object + * @param[in] body The physics body used in the physics simulation + * @param[in] adaptor The physics adaptor to use. @todo remove? + */ + static PhysicsActor New(Dali::Actor actor, Dali::Any body, PhysicsAdaptor adaptor); + + /** + * @brief Downcasts a handle to PhysicsActor handle. + * + * If handle points to an PhysicsActor object, the downcast produces valid handle. + * If not, the returned handle is left uninitialized. + * + * @SINCE_2.2.40 + * @param[in] handle to an object + * @return handle to a PhysicsActor object or an uninitialized handle + */ + static PhysicsActor DownCast(BaseHandle handle); + + /** + * @brief Get the actor ID of the associated actor. + */ + uint32_t GetId() const; + + /** + * Using ANY wrapper to enable this interface to be used for any + * types of physics bodies from either 2d or 3d physics. + * @return The physics body. It can be cast to an appropriate type, + * for example: + * btRigidBody* body = actor.GetBody().Get(); + * + * @note Please ensure to get a scoped accessor first to avoid modifying + * physics properties during integration step, or use the Async methods + * or the Queue. + */ + Dali::Any GetBody() const; + + /** + * Queue a method to set the position on the associated physics body + * in the update thread before the next integration. + * @param[in] actorPosition The position of the actor in DALi space + */ + void AsyncSetPhysicsPosition(Dali::Vector3 actorPosition); + + /** + * Queue a method to set the rotation of the associated physics body + * in the update thread before the next integration. + * @param[in] actorRotation The orientation of the actor in DALi space + */ + void AsyncSetPhysicsRotation(Dali::Quaternion actorRotation); + + /** + * Get the current position of the physics body in Physics space. + * @return the current position of the physics body in Physics space. + */ + Dali::Vector3 GetPhysicsPosition() const; + + /** + * Get the current rotation of the physics body in Physics space. + * @return the current rotation of the physics body in Physics space. + */ + Dali::Quaternion GetPhysicsRotation() const; + + /** + * Get the current position of the physics body in DALi space. + * @return the current position of the physics body in DALi space. + */ + Dali::Vector3 GetActorPosition() const; + + /** + * Get the current rotation of the physics body in DALi space. + * @return the current rotation of the physics body in DALi space. + */ + Dali::Quaternion GetActorRotation() const; + +public: // Not intended for developer use + /// @cond internal + /** + * @note Not intented for application developers + */ + explicit DALI_INTERNAL PhysicsActor(Internal::PhysicsActor* impl); + /// @endcond +}; + +} // namespace Dali::Toolkit::Physics + +#endif //DALI_TOOLKIT_PHYSICS_ACTOR_H diff --git a/dali-physics/public-api/physics-adaptor.cpp b/dali-physics/public-api/physics-adaptor.cpp new file mode 100644 index 0000000..c5cf51f --- /dev/null +++ b/dali-physics/public-api/physics-adaptor.cpp @@ -0,0 +1,162 @@ +/* + * 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. + */ + +// Class Header +#include + +// Internal headers +#include +#include +#include + +namespace Dali::Toolkit::Physics +{ +PhysicsAdaptor::PhysicsAdaptor() = default; + +PhysicsAdaptor::~PhysicsAdaptor() = default; + +PhysicsAdaptor::PhysicsAdaptor(const PhysicsAdaptor& handle) = default; + +PhysicsAdaptor::PhysicsAdaptor(PhysicsAdaptor&& rhs) noexcept = default; + +PhysicsAdaptor& PhysicsAdaptor::operator=(const PhysicsAdaptor& handle) = default; + +PhysicsAdaptor& PhysicsAdaptor::operator=(PhysicsAdaptor&& handle) noexcept = default; + +PhysicsAdaptor PhysicsAdaptor::New(const Dali::Matrix& transform, Uint16Pair size) +{ + Internal::PhysicsAdaptorPtr internal = Internal::PhysicsAdaptor::New(transform, size); + return PhysicsAdaptor(internal.Get()); +} + +PhysicsAdaptor PhysicsAdaptor::DownCast(BaseHandle handle) +{ + return PhysicsAdaptor(dynamic_cast(handle.GetObjectPtr())); +} + +void PhysicsAdaptor::SetTimestep(float timestep) +{ + GetImplementation(*this).SetTimestep(timestep); +} + +float PhysicsAdaptor::GetTimestep() +{ + return GetImplementation(*this).GetTimestep(); +} + +PhysicsAdaptor::ScopedPhysicsAccessorPtr PhysicsAdaptor::GetPhysicsAccessor() +{ + return GetImplementation(*this).GetPhysicsAccessor(); +} + +Dali::Layer PhysicsAdaptor::CreateDebugLayer(Dali::Window window) +{ + return GetImplementation(*this).CreateDebugLayer(window); +} + +Dali::Vector3 PhysicsAdaptor::TranslateToPhysicsSpace(Dali::Vector3 vector) +{ + return GetImplementation(*this).TranslateToPhysicsSpace(vector); +} + +Dali::Quaternion PhysicsAdaptor::TranslateToPhysicsSpace(Dali::Quaternion rotation) +{ + return GetImplementation(*this).TranslateToPhysicsSpace(rotation); +} + +Dali::Vector3 PhysicsAdaptor::TranslateFromPhysicsSpace(Dali::Vector3 vector) +{ + return GetImplementation(*this).TranslateFromPhysicsSpace(vector); +} + +Dali::Vector3 PhysicsAdaptor::ConvertVectorToPhysicsSpace(Dali::Vector3 vector) +{ + return GetImplementation(*this).ConvertVectorToPhysicsSpace(vector); +} + +Dali::Vector3 PhysicsAdaptor::ConvertVectorFromPhysicsSpace(Dali::Vector3 vector) +{ + return GetImplementation(*this).ConvertVectorFromPhysicsSpace(vector); +} + +void PhysicsAdaptor::SetTransformAndSize(const Dali::Matrix& transform, Uint16Pair size) +{ + GetImplementation(*this).SetTransformAndSize(transform, size); +} + +void PhysicsAdaptor::SetIntegrationState(Physics::PhysicsAdaptor::IntegrationState state) +{ + GetImplementation(*this).SetIntegrationState(state); +} + +Physics::PhysicsAdaptor::IntegrationState PhysicsAdaptor::GetIntegrationState() +{ + return GetImplementation(*this).GetIntegrationState(); +} + +void PhysicsAdaptor::SetDebugState(Physics::PhysicsAdaptor::DebugState state) +{ + GetImplementation(*this).SetDebugState(state); +} + +Physics::PhysicsAdaptor::DebugState PhysicsAdaptor::GetDebugState() +{ + return GetImplementation(*this).GetDebugState(); +} + +PhysicsActor PhysicsAdaptor::AddActorBody(Dali::Actor actor, Dali::Any body) +{ + Internal::PhysicsActorPtr physicsActor = GetImplementation(*this).AddActorBody(actor, body); + return PhysicsActor(physicsActor.Get()); +} + +PhysicsActor PhysicsAdaptor::GetPhysicsActor(Dali::Any body) +{ + Internal::PhysicsActorPtr physicsActor = GetImplementation(*this).GetPhysicsActor(body); + return PhysicsActor(physicsActor.Get()); +} + +Dali::Actor PhysicsAdaptor::GetRootActor() +{ + return GetImplementation(*this).GetRootActor(); +} + +void PhysicsAdaptor::BuildPickingRay(Dali::Vector3 origin, Dali::Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld) +{ + GetImplementation(*this).BuildPickingRay(origin, direction, rayFromWorld, rayToWorld); +} + +Dali::Vector3 PhysicsAdaptor::ProjectPoint(Dali::Vector3 origin, Dali::Vector3 direction, float distance) +{ + return GetImplementation(*this).ProjectPoint(origin, direction, distance); +} + +void PhysicsAdaptor::Queue(std::function function) +{ + GetImplementation(*this).Queue(function); +} + +void PhysicsAdaptor::CreateSyncPoint() +{ + GetImplementation(*this).CreateSyncPoint(); +} + +PhysicsAdaptor::PhysicsAdaptor(Internal::PhysicsAdaptor* impl) +: BaseHandle(impl) +{ +} + +} // namespace Dali::Toolkit::Physics diff --git a/dali-physics/public-api/physics-adaptor.h b/dali-physics/public-api/physics-adaptor.h new file mode 100644 index 0000000..e39945b --- /dev/null +++ b/dali-physics/public-api/physics-adaptor.h @@ -0,0 +1,339 @@ +#ifndef DALI_TOOLKIT_PHYSICS_ADAPTOR_H +#define DALI_TOOLKIT_PHYSICS_ADAPTOR_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 adaptoried. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// EXTERNAL INCLUDES +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include + +namespace Dali::Toolkit::Physics +{ +class PhysicsActor; + +namespace Internal +{ +class PhysicsAdaptor; +class PhysicsWorld; +} // namespace Internal + +/** + * Adaptor to manage access to the physics world and pairing actors and physics + * bodies, plus some translation methods to/from the physics space and dali space. + * + * Also manages a debug renderer that may utilize the physics engine debug. + * It is up to the developer to retrieve the root actor and parent it into the scene. + */ +class DALI_TOOLKIT_API PhysicsAdaptor : public BaseHandle +{ +public: + enum class IntegrationState + { + OFF, + ON + }; + enum class DebugState + { + OFF, + ON + }; + + /** + * Scoped accessor to the physics world. Automatically locks the physics world + * with a mutex to prevent the integration step from running whilst the + * developer is accessing the world, e.g. to add/remove bodies or constraints, + * or to perform hit-test. + * + * When it goes out of scope, the mutex is unlocked, and the integration step + * can resume. + */ + struct ScopedPhysicsAccessor + { + public: + /** + * Get a pointer to the native world. This uses DALi::Any wrapper to ensure + * that the same interface can be used for both 2d and 3d physics. It can be + * cast to the right type using the following construct: + * auto accessor = PhysicsAdaptor.GetPhysicsAccessor(); + * auto bulletWorld = accessor->GetNative().Get(); + */ + Dali::Any GetNative(); + + /** + * Hit test the physics world and return the nearest body. + * + * @param[in] rayFromWorld The origin in physics world space + * @param[in] rayToWorld A point along the direction on the far side of the physics world + * @param[out] localPivot The hit point local to the body + * @param[out] distanceFromCamera The distance of the pick point from the camera + * @return Empty value if no dynamic body found, otherwise a valid ptr to the hit body. + */ + Dali::Any HitTest(Dali::Vector3 rayFromWorld, Dali::Vector3 rayToWorld, Dali::Vector3& localPivot, float& distanceFromCamera); + + // Not copyable + ScopedPhysicsAccessor(ScopedPhysicsAccessor&) = delete; + const ScopedPhysicsAccessor& operator=(const ScopedPhysicsAccessor&) = delete; + + /** + * Destructor + */ + ~ScopedPhysicsAccessor(); + + private: + ScopedPhysicsAccessor(Internal::PhysicsWorld& world); + friend Internal::PhysicsAdaptor; + + struct Impl; + Impl* mImpl; + }; + + PhysicsAdaptor(); + ~PhysicsAdaptor(); + PhysicsAdaptor(const PhysicsAdaptor& handle); + PhysicsAdaptor(PhysicsAdaptor&& rhs) noexcept; + PhysicsAdaptor& operator=(const PhysicsAdaptor& handle); + PhysicsAdaptor& operator=(PhysicsAdaptor&& handle) noexcept; + + /** + * Initialize the physics system. + * + * @todo Consider allowing developer to create the physics world and pass it in here. + * + * @param[in] transform The transform matrix for DALi to Physics world space + * @param[in] size The size of the layer the physics actors will be drawn in + */ + static PhysicsAdaptor New(const Dali::Matrix& transform, Uint16Pair size); + + /** + * @brief Downcasts a handle to PhysicsAdaptor handle. + * + * If handle points to an PhysicsAdaptor object, the downcast produces valid handle. + * If not, the returned handle is left uninitialized. + * + * @SINCE_2.2.40 + * @param[in] handle to an object + * @return handle to a PhysicsAdaptor object or an uninitialized handle + */ + static PhysicsAdaptor DownCast(BaseHandle handle); + + /** + * Set how long the integration should take. + * @param[in] timestep The length of time that the physics integration should take. + */ + void SetTimestep(float timestep); + + /** + * Get the current physics integration timestep + * @return the current physics integration timestep + */ + float GetTimestep(); + + /** + * Returns an accessor pointer to the physics world. It automatically locks a mutex + * to prevent the integration step from running whilst the world is being modified. + * + * When the pointer goes out of scope, the mutex is unlocked and the physics world + * can run again. + */ + using ScopedPhysicsAccessorPtr = std::unique_ptr; + ScopedPhysicsAccessorPtr GetPhysicsAccessor(); + + /** + * Create a layer & debug renderer + * The debug renderer may utilize the debug features of the native physics + * engine. + * + * @param[in] window The window to draw in (requires camera) + */ + Dali::Layer CreateDebugLayer(Dali::Window window); + + /** + * 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); + + /** + * Convert a rotation in DALi coordinate system into physics space. + * @param[in] rotation The rotation to convert + * @return the converted rotation. + */ + Dali::Quaternion TranslateToPhysicsSpace(Dali::Quaternion rotation); + + /** + * 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); + + /** + * Convert a rotation in physics coordinate system into DALi space. + * @param[in] rotation The rotation to convert + * @return the converted rotation. + */ + Dali::Quaternion TranslateFromPhysicsSpace(Dali::Quaternion rotation); + + /** + * Converts a vector (not a point) 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 (not a point) 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] transform The transform matrix for DALi to Physics world space + * @param[in] size The size of the layer the physics actors will be drawn in + */ + void SetTransformAndSize(const Dali::Matrix& transform, Uint16Pair size); + + /** + * Set the integration state. If it's turned on, physics will run + * during the update frame callback. + * @note This is ON by default + * @param[in] state the new integration state + */ + void SetIntegrationState(IntegrationState state); + + /** + * Get the integration state. + * @return the new integration state + */ + IntegrationState GetIntegrationState(); + + /** + * Set the debug state. If debug is turned on, use the physics engine + * debug to show wireframes in a layer above the root actor. + * @note This is OFF by default + * @param[in] state the new debug state + */ + void SetDebugState(DebugState state); + + /** + * Get the debug state. + * @return the new debug state + */ + DebugState GetDebugState(); + + /** + * Add an actor / body pair. + * @pre It's expected that the client has added the body to the physics world. + * + * @param[in] actor The actor used for rendering the physics object + * @param[in] body The physics object + * @return a handle to the actor / body pair. + */ + PhysicsActor AddActorBody(Dali::Actor actor, Dali::Any body); + + /** + * Get the physics actor associated with the given body + * @param[in] body The physics body + * @return the associated physics actor + */ + PhysicsActor GetPhysicsActor(Dali::Any body); + + /** + * Get the root actor (which holds all the actor/body pairs) + */ + Dali::Actor GetRootActor(); + + /** + * Convert DALi touch point into a picking ray in the physics world. + * + * These can then be used to hit test the PhysicsWorld + * + * @param[in] origin The origin in DALi world space + * @param[in] direction The direction of the picking ray + * @param[out] rayFromWorld The origin in physics world space + * @param[out] rayToWorld A point along the direction on the far side of the + * physics world + * + * Example: + * OnTouched(Dali::Actor actor, Dali::TouchEvent& touch) + * { + * .. + * Vector3 origin, direction; + * Dali::HitTestAlgorithm::BuildPickingRay(renderTask, touch.GetScreenPosition(0), origin, direction); + * btVector3 rayFromWorld, rayToWorld; + * physicsAdaptor.BuildPickingRay(origin, direction, rayFromWorld, rayToWorld); + * auto scopedAccessor = physicsAdaptor.GetPhysicsAccessor(); + * body = scopedAccessor->Get().HitTest(rayFromWorld, rayToWorld, ..); + * } + */ + void BuildPickingRay(Dali::Vector3 origin, Dali::Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld); + + /** + * Project a point from the origin (in DALi space) a distance along + * the direction vector (in DALi space), and return the projected + * point in Physics space. + * + * @param[in] origin Origin in DALi world space + * @param[in] direction Direction in DALi world space + * @param[in] distance Distance along the direction vector + * @return the projected point, converted to the Physics space. + */ + Dali::Vector3 ProjectPoint(Dali::Vector3 origin, Dali::Vector3 direction, float distance); + + /** + * Queue a function to be executed before the physics integration in the update thread. + * + * @param[in] function to execute. This can be any method, and it can work fine with + * physics bodies etc, but it must not be used with DALI event side objects, as this + * will very likely cause the update thread to crash. + */ + void Queue(std::function function); + + /** + * Create a sync point for queued functions. + * + * Ensures that any queued functions are processed after this sync + * point is seen in the Update::FrameCallback, which will be in the + * same frame as any other DALi properties set during this event + * handler invocation. + * + * @param[in] None + */ + void CreateSyncPoint(); + +public: // Not intended for developer use + /// @cond internal + /** + * @note Not intented for application developers + */ + explicit DALI_INTERNAL PhysicsAdaptor(Internal::PhysicsAdaptor* impl); + /// @endcond +}; + +} // namespace Dali::Toolkit::Physics + +#endif //DALI_TOOLKIT_PHYSICS_ADAPTOR_H diff --git a/dali-scene3d/internal/loader/gltf2-util.cpp b/dali-scene3d/internal/loader/gltf2-util.cpp index ef3f0ce..8cdbecf 100644 --- a/dali-scene3d/internal/loader/gltf2-util.cpp +++ b/dali-scene3d/internal/loader/gltf2-util.cpp @@ -948,6 +948,8 @@ void ConvertCamera(const gltf2::Camera& camera, CameraParameters& cameraParamete cameraParameters.zNear = ortho.mZNear; cameraParameters.zFar = ortho.mZFar; } + + cameraParameters.name = std::string(camera.mName); } void ConvertNode(gltf2::Node const& node, const Index gltfIndex, Index parentIndex, ConversionContext& context, bool isMRendererModel) @@ -1472,4 +1474,4 @@ void ConvertGltfToContext(gt::Document& document, Gltf2Util::ConversionContext& } // namespace Gltf2Util -} // namespace Dali::Scene3D::Loader::Internal \ No newline at end of file +} // namespace Dali::Scene3D::Loader::Internal diff --git a/dali-scene3d/public-api/loader/camera-parameters.cpp b/dali-scene3d/public-api/loader/camera-parameters.cpp index 05bcef1..cf7e498 100644 --- a/dali-scene3d/public-api/loader/camera-parameters.cpp +++ b/dali-scene3d/public-api/loader/camera-parameters.cpp @@ -197,6 +197,8 @@ void CameraParameters::CalculateTransformComponents(Vector3& position, Quaternio bool CameraParameters::ConfigureCamera(CameraActor& camera, bool invertY) const { + camera[Actor::Property::NAME] = name; + if(isPerspective) { if(Dali::Equals(zNear, gltf2::UNDEFINED_FLOAT_VALUE) || @@ -266,4 +268,4 @@ bool CameraParameters::ConfigureCamera(CameraActor& camera, bool invertY) const return true; } -} // namespace Dali::Scene3D::Loader \ No newline at end of file +} // namespace Dali::Scene3D::Loader diff --git a/dali-scene3d/public-api/loader/camera-parameters.h b/dali-scene3d/public-api/loader/camera-parameters.h index 9fde071..b0b88b3 100644 --- a/dali-scene3d/public-api/loader/camera-parameters.h +++ b/dali-scene3d/public-api/loader/camera-parameters.h @@ -25,6 +25,7 @@ #include #include #include +#include namespace Dali { @@ -36,6 +37,8 @@ namespace Loader { struct DALI_SCENE3D_API CameraParameters { + std::string name; + // TODO : Is these default value has is meaning? Matrix matrix = Matrix::IDENTITY; float orthographicSize = 1.f; diff --git a/packaging/dali-toolkit.spec b/packaging/dali-toolkit.spec index 203dffd..78611e8 100644 --- a/packaging/dali-toolkit.spec +++ b/packaging/dali-toolkit.spec @@ -544,10 +544,12 @@ esac %endif %defattr(-,root,root,-) %{_libdir}/libchipmunk.so* +#%{_libdir}/libdali2-physics-2d.so* %license LICENSE %files -n %{dali2_physics2d}-devel %defattr(-,root,root,-) +%{_includedir}/dali-physics/public-api/* %{_includedir}/chipmunk/* %{_libdir}/pkgconfig/dali2-physics-2d.pc %{_libdir}/pkgconfig/chipmunk2d.pc @@ -560,11 +562,12 @@ esac %endif %defattr(-,root,root,-) %{_libdir}/libbullet3.so* +%{_libdir}/libdali2-physics-3d.so* %license LICENSE %files -n %{dali2_physics3d}-devel %defattr(-,root,root,-) +%{_includedir}/dali-physics/public-api/* %{_includedir}/bullet/* %{_libdir}/pkgconfig/dali2-physics-3d.pc %{_libdir}/pkgconfig/bullet3.pc -