[dali_2.2.43] Merge branch 'devel/master' 38/298538/1
authorRichard Huang <r.huang@samsung.com>
Fri, 8 Sep 2023 09:47:04 +0000 (10:47 +0100)
committerRichard Huang <r.huang@samsung.com>
Fri, 8 Sep 2023 09:47:04 +0000 (10:47 +0100)
Change-Id: I6376734dfe1bb8c4bffa5765561c5fb8c1ecde3d

64 files changed:
.gitignore
automated-tests/patch-coverage.pl
automated-tests/src/dali-physics2d/CMakeLists.txt [new file with mode: 0644]
automated-tests/src/dali-physics2d/tct-dali-physics2d-core.cpp [new file with mode: 0644]
automated-tests/src/dali-physics2d/utc-Dali-PhysicsActor.cpp [new file with mode: 0644]
automated-tests/src/dali-physics2d/utc-Dali-PhysicsAdaptor.cpp [new file with mode: 0644]
automated-tests/src/dali-physics3d/CMakeLists.txt [new file with mode: 0644]
automated-tests/src/dali-physics3d/tct-dali-physics3d-core.cpp [new file with mode: 0644]
automated-tests/src/dali-physics3d/utc-Dali-PhysicsActor.cpp [new file with mode: 0644]
automated-tests/src/dali-physics3d/utc-Dali-PhysicsAdaptor.cpp [new file with mode: 0644]
automated-tests/src/dali-toolkit/CMakeLists.txt
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-compare-types.h
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-harness.cpp
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-direct-rendering-egl.cpp [new file with mode: 0644]
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.cpp
automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-test-application.h
automated-tests/src/dali-toolkit/toolkit-direct-rendering-egl.cpp [deleted file]
automated-tests/src/dali-toolkit/utc-Dali-AnimatedVectorImageVisual.cpp
automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp
build/tizen/.gitignore
build/tizen/dali-physics/CMakeLists.txt
build/tizen/dali-physics/dali2-physics-2d.pc.in
build/tizen/dali-physics/dali2-physics-3d.pc.in
dali-physics/README.md [new file with mode: 0644]
dali-physics/dali-physics.h [new file with mode: 0644]
dali-physics/internal/bullet-impl/bullet-physics-actor-impl.cpp [new file with mode: 0644]
dali-physics/internal/bullet-impl/bullet-physics-adaptor-impl.cpp [new file with mode: 0644]
dali-physics/internal/bullet-impl/bullet-physics-adaptor-impl.h [new file with mode: 0644]
dali-physics/internal/bullet-impl/bullet-physics-debug-renderer.cpp [new file with mode: 0644]
dali-physics/internal/bullet-impl/bullet-physics-debug-renderer.h [new file with mode: 0644]
dali-physics/internal/bullet-impl/bullet-physics-world-impl.cpp [new file with mode: 0644]
dali-physics/internal/bullet-impl/bullet-physics-world-impl.h [new file with mode: 0644]
dali-physics/internal/chipmunk-impl/chipmunk-physics-actor-impl.cpp [new file with mode: 0644]
dali-physics/internal/chipmunk-impl/chipmunk-physics-adaptor-impl.cpp [new file with mode: 0644]
dali-physics/internal/chipmunk-impl/chipmunk-physics-adaptor-impl.h [new file with mode: 0644]
dali-physics/internal/chipmunk-impl/chipmunk-physics-world-impl.cpp [new file with mode: 0644]
dali-physics/internal/chipmunk-impl/chipmunk-physics-world-impl.h [new file with mode: 0644]
dali-physics/internal/file.list [new file with mode: 0644]
dali-physics/internal/physics-actor-impl.h [new file with mode: 0644]
dali-physics/internal/physics-adaptor-impl.cpp [new file with mode: 0644]
dali-physics/internal/physics-adaptor-impl.h [new file with mode: 0644]
dali-physics/internal/physics-world-impl.cpp [new file with mode: 0644]
dali-physics/internal/physics-world-impl.h [new file with mode: 0644]
dali-physics/public-api/file.list [new file with mode: 0644]
dali-physics/public-api/physics-actor.cpp [new file with mode: 0644]
dali-physics/public-api/physics-actor.h [new file with mode: 0644]
dali-physics/public-api/physics-adaptor.cpp [new file with mode: 0644]
dali-physics/public-api/physics-adaptor.h [new file with mode: 0644]
dali-physics/public-api/scoped-physics-accessor.cpp [new file with mode: 0644]
dali-scene3d/internal/loader/dli-loader-impl.cpp
dali-scene3d/internal/loader/gltf2-util.cpp
dali-scene3d/public-api/loader/camera-parameters.cpp
dali-scene3d/public-api/loader/camera-parameters.h
dali-toolkit/devel-api/visuals/animated-vector-image-visual-actions-devel.h
dali-toolkit/internal/visuals/animated-vector-image/animated-vector-image-visual.cpp
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.cpp
dali-toolkit/internal/visuals/animated-vector-image/vector-animation-task.h
dali-toolkit/internal/visuals/npatch-data.cpp
dali-toolkit/internal/visuals/npatch-data.h
dali-toolkit/internal/visuals/npatch-loader.cpp
dali-toolkit/internal/visuals/npatch-loader.h
dali-toolkit/internal/visuals/npatch/npatch-visual.cpp
dali-toolkit/public-api/dali-toolkit-version.cpp
packaging/dali-toolkit.spec

index 2c8d0d1..4555b92 100644 (file)
@@ -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
index 2f8bdb4..38fc2b0 100755 (executable)
@@ -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-physics2d/CMakeLists.txt b/automated-tests/src/dali-physics2d/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ea0f140
--- /dev/null
@@ -0,0 +1,97 @@
+SET(PKG_NAME "dali-physics2d")
+
+SET(EXEC_NAME "tct-${PKG_NAME}-core")
+SET(RPM_NAME "core-${PKG_NAME}-tests")
+
+# 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(${PKG_NAME} REQUIRED
+  dali2-core
+  dali2-adaptor
+  dali2-toolkit
+  dali2-physics-2d
+  chipmunk2d
+)
+
+ADD_COMPILE_OPTIONS( -O0 -ggdb --coverage -Wall -Werror -DDEBUG_ENABLED)
+ADD_COMPILE_OPTIONS( ${${PKG_NAME}_CFLAGS_OTHER} )
+
+ADD_DEFINITIONS(-DTEST_RESOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../resources\" )
+
+FOREACH(directory ${${PKG_NAME}_LIBRARY_DIRS})
+    SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -L${directory}")
+ENDFOREACH(directory ${PKG_NAME_LIBRARY_DIRS})
+
+INCLUDE_DIRECTORIES(
+    ../../../
+    ${${PKG_NAME}_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}
+    ${${PKG_NAME}_LIBRARIES}
+    -lpthread -ldl --coverage
+)
+
+INSTALL(PROGRAMS ${EXEC_NAME}
+    DESTINATION ${BIN_DIR}/${EXEC_NAME}
+)
diff --git a/automated-tests/src/dali-physics2d/tct-dali-physics2d-core.cpp b/automated-tests/src/dali-physics2d/tct-dali-physics2d-core.cpp
new file mode 100644 (file)
index 0000000..b3297ce
--- /dev/null
@@ -0,0 +1,8 @@
+#include <test-harness.h>
+
+#include "tct-dali-physics2d-core.h"
+
+int main(int argc, char* const argv[])
+{
+  return TestHarness::RunTests(argc, argv, tc_array);
+}
diff --git a/automated-tests/src/dali-physics2d/utc-Dali-PhysicsActor.cpp b/automated-tests/src/dali-physics2d/utc-Dali-PhysicsActor.cpp
new file mode 100644 (file)
index 0000000..855d686
--- /dev/null
@@ -0,0 +1,595 @@
+/*
+ * 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 <chipmunk/chipmunk.h>
+#include <stdlib.h>
+#include <iostream>
+
+// Need to override adaptor classes for toolkit test harness, so include
+// test harness headers before dali headers.
+#include <dali-toolkit-test-suite-utils.h>
+#include <toolkit-event-thread-callback.h>
+
+#include <dali-physics/dali-physics.h>
+#include <dali-toolkit/dali-toolkit.h>
+#include <dali-toolkit/devel-api/controls/alignment/alignment.h>
+
+using namespace Dali;
+using namespace Dali::Toolkit::Physics;
+
+extern cpBody* CreateBody(cpSpace* space);
+
+const char* BALL_IMAGE = TEST_RESOURCE_DIR "/gallery-small-1.jpg";
+
+int UtcDaliPhysics2DActorNew(void)
+{
+  ToolkitTestApplication application;
+
+  cpBody* body{nullptr};
+
+  Matrix         transform(true);
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  auto accessor         = adaptor.GetPhysicsAccessor();
+  auto space            = accessor->GetNative().Get<cpSpace*>();
+  body                  = CreateBody(space);
+  Dali::Actor ballActor = Toolkit::ImageView::New(BALL_IMAGE);
+
+  PhysicsActor physicsActor = PhysicsActor::New(ballActor, body, adaptor);
+
+  DALI_TEST_CHECK(physicsActor);
+  END_TEST;
+}
+
+int UtcDaliPhysics2DActorDownCastP(void)
+{
+  ToolkitTestApplication application;
+
+  Matrix         transform(true);
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  cpBody* body{nullptr};
+  auto    accessor          = adaptor.GetPhysicsAccessor();
+  auto    space             = accessor->GetNative().Get<cpSpace*>();
+  body                      = CreateBody(space);
+  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 UtcDaliPhysics2DActorDownCastN(void)
+{
+  BaseHandle   uninitializedHandle;
+  PhysicsActor actor = PhysicsActor::DownCast(uninitializedHandle);
+  DALI_TEST_CHECK(!actor);
+  END_TEST;
+}
+
+int UtcDaliPhysics2DActorMoveConstructor(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("Testing the move constructor");
+
+  Matrix         transform(true);
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  cpBody* body{nullptr};
+  auto    accessor          = adaptor.GetPhysicsAccessor();
+  auto    space             = accessor->GetNative().Get<cpSpace*>();
+  body                      = CreateBody(space);
+  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 UtcDaliPhysics2DActorCopyConstructor(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("Testing the move constructor");
+
+  Matrix         transform(true);
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  cpBody* body{nullptr};
+  auto    accessor          = adaptor.GetPhysicsAccessor();
+  auto    space             = accessor->GetNative().Get<cpSpace*>();
+  body                      = CreateBody(space);
+  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 UtcDaliPhysics2DActorCopyAssign(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("Testing the copy assign");
+
+  Matrix         transform(true);
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  cpBody* body{nullptr};
+  auto    accessor          = adaptor.GetPhysicsAccessor();
+  auto    space             = accessor->GetNative().Get<cpSpace*>();
+  body                      = CreateBody(space);
+  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 UtcDaliPhysics2DActorMoveAssignment(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("Testing the move constructor");
+
+  Matrix         transform(true);
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  cpBody* body{nullptr};
+  auto    accessor          = adaptor.GetPhysicsAccessor();
+  auto    space             = accessor->GetNative().Get<cpSpace*>();
+  body                      = CreateBody(space);
+  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 UtcDaliPhysics2DActorGetIdP(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("Testing the ID Getter");
+
+  Matrix         transform(true);
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  cpBody* body{nullptr};
+  auto    accessor          = adaptor.GetPhysicsAccessor();
+  auto    space             = accessor->GetNative().Get<cpSpace*>();
+  body                      = CreateBody(space);
+  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 UtcDaliPhysics2DActorGetIdN(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 UtcDaliPhysics2DActorGetBodyP(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;
+  cpBody*      body{nullptr};
+  {
+    auto accessor         = adaptor.GetPhysicsAccessor();
+    auto space            = accessor->GetNative().Get<cpSpace*>();
+    body                  = CreateBody(space);
+    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<cpBody*>(), body, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DActorGetBodyN(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 UtcDaliPhysics2DActorSetPosition(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);
+
+  cpBody*      body{nullptr};
+  PhysicsActor physicsActor;
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+    body          = CreateBody(space);
+    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<Vector3>(Actor::Property::POSITION), Vector3(10, 20, 0), 0.01f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DActorSetRotation1(void)
+{
+  tet_infoline("Test the AsyncSetPhysicsRotation() function");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, -2.0f, 1.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);
+
+  cpBody*      body{nullptr};
+  PhysicsActor physicsActor;
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+    body          = CreateBody(space);
+    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<Quaternion>(Actor::Property::ORIENTATION);
+    Quaternion expected(Degree(30), Vector3::ZAXIS);
+    DALI_TEST_EQUALS(q, expected, 0.0001f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DActorSetRotation2(void)
+{
+  tet_infoline("Test the AsyncSetPhysicsRotation() function");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, -2.0f, 1.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);
+
+  cpBody*      body{nullptr};
+  PhysicsActor physicsActor;
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+    body          = CreateBody(space);
+    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<Quaternion>(Actor::Property::ORIENTATION);
+    Quaternion expected(Degree(30), Vector3::ZAXIS);
+    DALI_TEST_EQUALS(q, expected, 0.001f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DActorGetActorPosition(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);
+
+  cpBody*      body{nullptr};
+  PhysicsActor physicsActor;
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+    body          = CreateBody(space);
+    physicsActor  = adaptor.AddActorBody(ballActor, body);
+    tet_infoline("Test that Z is ignored");
+    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();
+    tet_infoline("Test that Z is ignored. Note, error is quite high, so make epsilon low");
+    DALI_TEST_EQUALS(physicsActor.GetActorPosition(), Vector3(10, 20, 0), 0.01f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DActorGetActorRotation(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);
+
+  cpBody*      body{nullptr};
+  PhysicsActor physicsActor;
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+    body          = CreateBody(space);
+    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 UtcDaliPhysics2DActorGetPhysicsPosition(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);
+
+  cpBody*      body{nullptr};
+  PhysicsActor physicsActor;
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+    body          = CreateBody(space);
+    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, 0, 1);
+    DALI_TEST_EQUALS(physicsActor.GetPhysicsPosition(), Vector3(pos), 0.01f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DActorGetPhysicsRotation(void)
+{
+  tet_infoline("Test the GetPhysicsRotation() function");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, -2.0f, 1.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);
+
+  cpBody*      body{nullptr};
+  PhysicsActor physicsActor;
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+    body          = CreateBody(space);
+    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();
+  {
+    tet_infoline("Check that actor and physics rotations are identical");
+    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-physics2d/utc-Dali-PhysicsAdaptor.cpp b/automated-tests/src/dali-physics2d/utc-Dali-PhysicsAdaptor.cpp
new file mode 100644 (file)
index 0000000..b6ca868
--- /dev/null
@@ -0,0 +1,935 @@
+/*
+ * 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 <stdlib.h>
+#include <iostream>
+#include <typeinfo>
+
+// Need to override adaptor classes for toolkit test harness, so include
+// test harness headers before dali headers.
+#include <dali-physics/dali-physics.h>
+#include <dali-toolkit-test-suite-utils.h>
+#include <toolkit-event-thread-callback.h>
+
+#include <dali-toolkit/devel-api/controls/alignment/alignment.h>
+#include <dali-toolkit/public-api/controls/image-view/image-view.h>
+#include <dali/devel-api/adaptor-framework/window-devel.h>
+#include <dali/devel-api/events/hit-test-algorithm.h>
+
+#include <chipmunk/chipmunk.h>
+
+using namespace Dali;
+using namespace Dali::Toolkit::Physics;
+
+void utc_dali_physics2d_startup(void)
+{
+  test_return_value = TET_UNDEF;
+}
+
+void utc_dali_physics2d_cleanup(void)
+{
+  test_return_value = TET_PASS;
+}
+
+cpBody* CreateBody(cpSpace* space)
+{
+  const float BALL_MASS       = 10.0f;
+  const float BALL_RADIUS     = 26.0f;
+  const float BALL_ELASTICITY = 0.5f;
+  const float BALL_FRICTION   = 0.5f;
+
+  cpBody* body = cpSpaceAddBody(space, cpBodyNew(BALL_MASS, cpMomentForCircle(BALL_MASS, 0.0f, BALL_RADIUS, cpvzero)));
+
+  cpShape* shape = cpSpaceAddShape(space, cpCircleShapeNew(body, BALL_RADIUS, cpvzero));
+  cpShapeSetElasticity(shape, BALL_ELASTICITY);
+  cpShapeSetFriction(shape, BALL_FRICTION);
+
+  return body;
+}
+
+int UtcDaliPhysics2DCreateAdaptorP1(void)
+{
+  ToolkitTestApplication application;
+
+  Matrix     transform(true);
+  Uint16Pair size(640, 480);
+
+  PhysicsAdaptor handle = PhysicsAdaptor::New(transform, size);
+  DALI_TEST_CHECK(handle);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DCreateAdaptorN1(void)
+{
+  ToolkitTestApplication application;
+
+  PhysicsAdaptor handle;
+  DALI_TEST_CHECK(!handle);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DDowncastP1(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 UtcDaliPhysics2DDowncastN1(void)
+{
+  BaseHandle handle;
+  auto       adaptor = PhysicsAdaptor::DownCast(handle);
+  DALI_TEST_CHECK(!adaptor);
+
+  DALI_TEST_CHECK(typeid(PhysicsAdaptor) == typeid(decltype(adaptor)));
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorMoveConstructor(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 UtcDaliPhysics2DAdaptorCopyConstructor(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 UtcDaliPhysics2DAdaptorCopyAssign(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 UtcDaliPhysics2DAdaptorMoveAssignment(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 UtcDaliPhysics2DSetTimestep(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 UtcDaliPhysics2DGetTimestep(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 UtcDaliPhysics2DGetPhysicsAccessorP1(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 UtcDaliPhysics2DGetPhysicsAccessorN1(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 UtcDaliPhysics2DAdaptorGetRootActor(void)
+{
+  tet_infoline("Test that the root actor can be retrieved");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.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<Vector2>(Actor::Property::SIZE), Vector2(640.0f, 480.0f), 0.001f, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorCreateDebugLayer(void)
+{
+  ToolkitTestApplication application;
+  Matrix                 transform(true);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.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);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorTranslateToPhysicsSpace1(void)
+{
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.0f));
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  Vector3 a(30, 20, 10);
+  Vector3 expected(60, 40, 10);
+  DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(a), expected, 0.0001f, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorTranslateToPhysicsSpace2(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, 1.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 UtcDaliPhysics2DAdaptorTranslateToPhysicsSpace3(void)
+{
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  tet_infoline("Test that using an inverted Y scale does nothing to rotation");
+
+  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);
+
+  DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(q), qp, 0.0001f, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorTranslateToPhysicsSpace4(void)
+{
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  tet_infoline("Test that using an inverted Y scale does nothing to rotation");
+
+  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);
+
+  DALI_TEST_EQUALS(adaptor.TranslateToPhysicsSpace(q), qp, 0.0001f, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorTranslateToPhysicsSpace5(void)
+{
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  tet_infoline("Test that using an inverted Y scale does nothing to rotation");
+
+  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 UtcDaliPhysics2DAdaptorTranslateFromPhysicsSpace1(void)
+{
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  tet_infoline("Test that using a double scale halves position");
+
+  transform.SetIdentityAndScale(Vector3(2.0f, -2.0f, 1.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 UtcDaliPhysics2DAdaptorConvertVectorToPhysicsSpace01(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 UtcDaliPhysics2DAdaptorConvertVectorToPhysicsSpace02(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 UtcDaliPhysics2DAdaptorConvertVectorFromPhysicsSpace01(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 UtcDaliPhysics2DAdaptorConvertVectorFromPhysicsSpace02(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 UtcDaliPhysics2DAdaptorSetTransformAndSize(void)
+{
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.0f));
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  Vector3 a(30, 20, 10);
+  Vector3 expected(60, 40, 10);
+  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 UtcDaliPhysics2DAdaptorSetIntegrationState(void)
+{
+  tet_infoline("Test that changing the integration state is reflected");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.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 UtcDaliPhysics2DAdaptorGetIntegrationState(void)
+{
+  tet_infoline("Test that changing the integration state is reflected");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.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 UtcDaliPhysics2DAdaptorSetDebugState(void)
+{
+  tet_infoline("Test that changing the debug state is reflected");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.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 UtcDaliPhysics2DAdaptorGetDebugState(void)
+{
+  tet_infoline("Test that changing the debug state is reflected");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.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 UtcDaliPhysics2DAdaptorAddActorBody(void)
+{
+  tet_infoline("Test that an actor/body pair can be added");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.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 space    = accessor->GetNative().Get<cpSpace*>();
+
+  cpBody*     body         = CreateBody(space);
+  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<cpBody*>(), body, TEST_LOCATION);
+
+  END_TEST;
+}
+
+void removeShape(cpBody* body, cpShape* shape, void* data)
+{
+  cpSpace* space = static_cast<cpSpace*>(data);
+  cpSpaceRemoveShape(space, shape);
+  cpShapeSetBody(shape, nullptr);
+  cpShapeFree(shape);
+}
+
+int UtcDaliPhysics2DAdaptorRemoveActorBodyP01(void)
+{
+  tet_infoline("Test that an actor/body pair can be removed");
+
+  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);
+
+  cpBody* body;
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+
+    body = CreateBody(space);
+  }
+  Dali::Actor ballActor    = Toolkit::ImageView::New("gallery-small-1.jpg");
+  auto        physicsActor = adaptor.AddActorBody(ballActor, body);
+
+  application.SendNotification();
+  application.Render();
+  application.SendNotification();
+  application.Render();
+
+  adaptor.RemoveActorBody(physicsActor);
+  DALI_TEST_CHECK(!ballActor.GetParent());
+
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+    auto space    = accessor->GetNative().Get<cpSpace*>();
+
+    try
+    {
+      cpBodyEachShape(body, removeShape, space);
+      cpSpaceRemoveBody(space, body);
+      tet_result(TET_PASS);
+    }
+    catch(std::exception& e)
+    {
+      tet_result(TET_FAIL);
+    }
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorRemoveActorBodyN01(void)
+{
+  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);
+
+  tet_infoline("Test that removing a physics actor that hasn't been created with AddActorBody does nothing");
+  Dali::Actor  actor        = Dali::Actor::New();
+  cpBody*      body         = cpBodyNew(1.0f, 1.0f);
+  PhysicsActor physicsActor = PhysicsActor::New(actor, body, adaptor);
+  ;
+  try
+  {
+    adaptor.RemoveActorBody(physicsActor);
+    tet_result(TET_PASS);
+  }
+  catch(std::exception& e)
+  {
+    // Should fail silently, without exception!
+    tet_result(TET_FAIL);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorRemoveActorBodyN02(void)
+{
+  tet_infoline("Test that an empty actor/body pair doesn't break adaptor");
+
+  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);
+
+  PhysicsActor physicsActor;
+  try
+  {
+    adaptor.RemoveActorBody(physicsActor);
+    tet_result(TET_FAIL);
+  }
+  catch(DaliException& e)
+  {
+    DALI_TEST_ASSERT(e, "Physics actor handle is empty", TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorGetPhysicsActor(void)
+{
+  tet_infoline("Test that an actor/body pair can be retrieved");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.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 space    = accessor->GetNative().Get<cpSpace*>();
+
+  cpBody*     body         = CreateBody(space);
+  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 UtcDaliPhysics2DAdaptorBuildPickingRay(void)
+{
+  tet_infoline("Test that picking ray converts screen coords");
+
+  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);
+
+  Vector3 from, to;
+  adaptor.BuildPickingRay(Vector3(center), Vector3(center), from, to); // Hit test centre of screen
+  Vector3 physCenter = adaptor.TranslateToPhysicsSpace(Vector3(center));
+  DALI_TEST_EQUALS(from, physCenter, 0.001f, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorProjectPoint(void)
+{
+  tet_infoline("Test that a point is projected into physics space");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.0f));
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor = PhysicsAdaptor::New(transform, size);
+
+  Vector3 projectedPoint = adaptor.ProjectPoint(Vector3(), -Vector3::ZAXIS, 200);
+
+  DALI_TEST_EQUALS(projectedPoint, Vector3(0.0f, 0.0f, 0.0f), 0.001f, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorQueue(void)
+{
+  tet_infoline("Test that Queue and CreateSyncPoint both work");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.0f));
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor   = PhysicsAdaptor::New(transform, size);
+  Actor          rootActor = adaptor.GetRootActor();
+  auto           scene     = application.GetScene();
+  scene.Add(rootActor);
+
+  cpBody* body{nullptr};
+  {
+    auto accessor            = adaptor.GetPhysicsAccessor();
+    auto space               = accessor->GetNative().Get<cpSpace*>();
+    body                     = CreateBody(space);
+    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]() {
+    cpBodySetPosition(body, cpv(100.0f, 20.0f));
+  });
+  adaptor.CreateSyncPoint();
+
+  application.SendNotification();
+  application.Render();
+  // Should trigger an Update
+
+  {
+    auto   accessor = adaptor.GetPhysicsAccessor();
+    cpVect origin   = cpBodyGetPosition(body);
+
+    DALI_TEST_EQUALS(origin.x, cpFloat(100.0f), 0.001f, TEST_LOCATION);
+    DALI_TEST_EQUALS(origin.y, cpFloat(20.0f), 0.001f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorCreateSyncPoint(void)
+{
+  tet_infoline("Test that a delayed CreateSyncPoint delays update");
+
+  ToolkitTestApplication application;
+  Matrix                 transform(false);
+  transform.SetIdentityAndScale(Vector3(2.0f, 2.0f, 1.0f));
+  Uint16Pair     size(640, 480);
+  PhysicsAdaptor adaptor   = PhysicsAdaptor::New(transform, size);
+  Actor          rootActor = adaptor.GetRootActor();
+  auto           scene     = application.GetScene();
+  scene.Add(rootActor);
+
+  cpBody* body{nullptr};
+  {
+    auto accessor            = adaptor.GetPhysicsAccessor();
+    auto space               = accessor->GetNative().Get<cpSpace*>();
+    body                     = CreateBody(space);
+    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]() {
+      cpBodySetPosition(body, cpv(100.0f, 20.0f));
+    });
+  }
+
+  // Should trigger an Update without processing queue
+  application.SendNotification();
+  application.Render();
+
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+
+    cpVect origin = cpBodyGetPosition(body);
+    DALI_TEST_EQUALS(origin.x, cpFloat(0.0f), 0.01f, TEST_LOCATION);
+    DALI_TEST_EQUALS(origin.y, cpFloat(0.0f), 0.01f, TEST_LOCATION);
+  }
+
+  // Should now execute queue
+  adaptor.CreateSyncPoint();
+  application.SendNotification();
+  application.Render();
+
+  {
+    auto accessor = adaptor.GetPhysicsAccessor();
+
+    cpVect origin = cpBodyGetPosition(body);
+    DALI_TEST_EQUALS(origin.x, cpFloat(100.0f), 0.01f, TEST_LOCATION);
+    DALI_TEST_EQUALS(origin.y, cpFloat(20.0f), 0.01f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics2DAdaptorHitTestP(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        space     = accessor->GetNative().Get<cpSpace*>();
+    Dali::Actor ballActor = Toolkit::ImageView::New(TEST_RESOURCE_DIR "/gallery-small-1.jpg");
+    cpBody*     body      = CreateBody(space);
+    cpBodySetPosition(body, cpv(center.x, center.y));
+
+    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 from, to;
+  adaptor.BuildPickingRay(Vector3(center), Vector3(center), from, to); // Hit test centre of screen
+
+  {
+    auto    accessor = adaptor.GetPhysicsAccessor();
+    Vector3 localPivot;
+    float   distanceFromCamera;
+    auto    body = accessor->HitTest(from, from, localPivot, distanceFromCamera);
+
+    DALI_TEST_CHECK(!body.Empty());
+  }
+
+  END_TEST;
+}
diff --git a/automated-tests/src/dali-physics3d/CMakeLists.txt b/automated-tests/src/dali-physics3d/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a863fd8
--- /dev/null
@@ -0,0 +1,97 @@
+SET(PKG_NAME "dali-physics3d")
+
+SET(EXEC_NAME "tct-${PKG_NAME}-core")
+SET(RPM_NAME "core-${PKG_NAME}-tests")
+
+# 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(${PKG_NAME} REQUIRED
+  dali2-core
+  dali2-adaptor
+  dali2-toolkit
+  dali2-physics-3d
+  bullet3
+)
+
+ADD_COMPILE_OPTIONS( -O0 -ggdb --coverage -Wall -Werror -DDEBUG_ENABLED)
+ADD_COMPILE_OPTIONS( ${${PKG_NAME}_CFLAGS_OTHER} )
+
+ADD_DEFINITIONS(-DTEST_RESOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../resources\" )
+
+FOREACH(directory ${${PKG_NAME}_LIBRARY_DIRS})
+    SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -L${directory}")
+ENDFOREACH(directory ${PKG_NAME_LIBRARY_DIRS})
+
+INCLUDE_DIRECTORIES(
+    ../../../
+    ${${PKG_NAME}_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}
+    ${${PKG_NAME}_LIBRARIES}
+    -lpthread -ldl --coverage
+)
+
+INSTALL(PROGRAMS ${EXEC_NAME}
+    DESTINATION ${BIN_DIR}/${EXEC_NAME}
+)
diff --git a/automated-tests/src/dali-physics3d/tct-dali-physics3d-core.cpp b/automated-tests/src/dali-physics3d/tct-dali-physics3d-core.cpp
new file mode 100644 (file)
index 0000000..a87b21c
--- /dev/null
@@ -0,0 +1,9 @@
+#include <test-harness.h>
+
+// Must come second
+#include "tct-dali-physics3d-core.h"
+
+int main(int argc, char* const argv[])
+{
+  return TestHarness::RunTests(argc, argv, tc_array);
+}
diff --git a/automated-tests/src/dali-physics3d/utc-Dali-PhysicsActor.cpp b/automated-tests/src/dali-physics3d/utc-Dali-PhysicsActor.cpp
new file mode 100644 (file)
index 0000000..2c31d11
--- /dev/null
@@ -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 <bullet/btBulletDynamicsCommon.h>
+#include <stdlib.h>
+#include <iostream>
+
+// Need to override adaptor classes for toolkit test harness, so include
+// test harness headers before dali headers.
+#include <dali-toolkit-test-suite-utils.h>
+#include <toolkit-event-thread-callback.h>
+
+#include <dali-physics/dali-physics.h>
+#include <dali-toolkit/dali-toolkit.h>
+#include <dali-toolkit/devel-api/controls/alignment/alignment.h>
+
+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 UtcDaliPhysics3DActorNew(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<btDiscreteDynamicsWorld*>();
+  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 UtcDaliPhysics3DActorDownCastP(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<btDiscreteDynamicsWorld*>();
+  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 UtcDaliPhysics3DActorDownCastN(void)
+{
+  BaseHandle   uninitializedHandle;
+  PhysicsActor actor = PhysicsActor::DownCast(uninitializedHandle);
+  DALI_TEST_CHECK(!actor);
+  END_TEST;
+}
+
+int UtcDaliPhysics3DActorMoveConstructor(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<btDiscreteDynamicsWorld*>();
+  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 UtcDaliPhysics3DActorCopyConstructor(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<btDiscreteDynamicsWorld*>();
+  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 UtcDaliPhysics3DActorCopyAssign(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<btDiscreteDynamicsWorld*>();
+  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 UtcDaliPhysics3DActorMoveAssignment(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<btDiscreteDynamicsWorld*>();
+  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 UtcDaliPhysics3DActorGetIdP(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<btDiscreteDynamicsWorld*>();
+  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 UtcDaliPhysics3DActorGetIdN(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 UtcDaliPhysics3DActorGetBodyP(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<btDiscreteDynamicsWorld*>();
+    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<btRigidBody*>(), body, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DActorGetBodyN(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 UtcDaliPhysics3DActorSetPosition(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<btDiscreteDynamicsWorld*>();
+    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<Vector3>(Actor::Property::POSITION), Vector3(10, 20, -30), 0.0001f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DActorSetRotation1(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<btDiscreteDynamicsWorld*>();
+    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<Quaternion>(Actor::Property::ORIENTATION);
+    Quaternion expected(Degree(30), Vector3::YAXIS);
+    DALI_TEST_EQUALS(q, expected, 0.0001f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DActorSetRotation2(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<btDiscreteDynamicsWorld*>();
+    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<Quaternion>(Actor::Property::ORIENTATION);
+    Quaternion expected(Degree(30), Vector3::ZAXIS);
+    DALI_TEST_EQUALS(q, expected, 0.0001f, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DActorGetActorPosition(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<btDiscreteDynamicsWorld*>();
+    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 UtcDaliPhysics3DActorGetActorRotation(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<btDiscreteDynamicsWorld*>();
+    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 UtcDaliPhysics3DActorGetPhysicsPosition(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<btDiscreteDynamicsWorld*>();
+    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 UtcDaliPhysics3DActorGetPhysicsRotation(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<btDiscreteDynamicsWorld*>();
+    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-physics3d/utc-Dali-PhysicsAdaptor.cpp b/automated-tests/src/dali-physics3d/utc-Dali-PhysicsAdaptor.cpp
new file mode 100644 (file)
index 0000000..b09c8fc
--- /dev/null
@@ -0,0 +1,963 @@
+/*
+ * 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 <stdlib.h>
+#include <iostream>
+#include <typeinfo>
+
+// Need to override adaptor classes for toolkit test harness, so include
+// test harness headers before dali headers.
+#include <dali-physics/dali-physics.h>
+#include <dali-toolkit-test-suite-utils.h>
+#include <toolkit-event-thread-callback.h>
+
+#include <dali-toolkit/devel-api/controls/alignment/alignment.h>
+#include <dali-toolkit/public-api/controls/image-view/image-view.h>
+#include <dali/devel-api/adaptor-framework/window-devel.h>
+#include <dali/devel-api/events/hit-test-algorithm.h>
+
+#include <bullet/btBulletDynamicsCommon.h>
+
+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 UtcDaliPhysics3DCreateAdaptorP1(void)
+{
+  ToolkitTestApplication application;
+
+  Matrix     transform(true);
+  Uint16Pair size(640, 480);
+
+  PhysicsAdaptor handle = PhysicsAdaptor::New(transform, size);
+  DALI_TEST_CHECK(handle);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DCreateAdaptorN1(void)
+{
+  ToolkitTestApplication application;
+
+  PhysicsAdaptor handle;
+  DALI_TEST_CHECK(!handle);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DDowncastP1(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 UtcDaliPhysics3DDowncastN1(void)
+{
+  BaseHandle handle;
+  auto       adaptor = PhysicsAdaptor::DownCast(handle);
+  DALI_TEST_CHECK(!adaptor);
+
+  DALI_TEST_CHECK(typeid(PhysicsAdaptor) == typeid(decltype(adaptor)));
+  END_TEST;
+}
+
+int UtcDaliPhysics3DAdaptorMoveConstructor(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 UtcDaliPhysics3DAdaptorCopyConstructor(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 UtcDaliPhysics3DAdaptorCopyAssign(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 UtcDaliPhysics3DAdaptorMoveAssignment(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 UtcDaliPhysics3DSetTimestep(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 UtcDaliPhysics3DGetTimestep(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 UtcDaliPhysics3DGetPhysicsAccessorP1(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 UtcDaliPhysics3DGetPhysicsAccessorN1(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 UtcDaliPhysics3DAdaptorGetRootActor(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<Vector2>(Actor::Property::SIZE), Vector2(640.0f, 480.0f), 0.001f, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DAdaptorCreateDebugLayer(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<btDiscreteDynamicsWorld*>();
+    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 UtcDaliPhysics3DAdaptorTranslateToPhysicsSpace1(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 UtcDaliPhysics3DAdaptorTranslateToPhysicsSpace2(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 UtcDaliPhysics3DAdaptorTranslateToPhysicsSpace3(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 UtcDaliPhysics3DAdaptorTranslateToPhysicsSpace4(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 UtcDaliPhysics3DAdaptorTranslateToPhysicsSpace5(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 UtcDaliPhysics3DAdaptorTranslateFromPhysicsSpace1(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 UtcDaliPhysics3DAdaptorConvertVectorToPhysicsSpace01(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 UtcDaliPhysics3DAdaptorConvertVectorToPhysicsSpace02(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 UtcDaliPhysics3DAdaptorConvertVectorFromPhysicsSpace01(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 UtcDaliPhysics3DAdaptorConvertVectorFromPhysicsSpace02(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 UtcDaliPhysics3DAdaptorSetTransformAndSize(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 UtcDaliPhysics3DAdaptorSetIntegrationState(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 UtcDaliPhysics3DAdaptorGetIntegrationState(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 UtcDaliPhysics3DAdaptorSetDebugState(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 UtcDaliPhysics3DAdaptorGetDebugState(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 UtcDaliPhysics3DAdaptorAddActorBody(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<btDiscreteDynamicsWorld*>();
+
+  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<btRigidBody*>(), body, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DAdaptorRemoveActorBodyP01(void)
+{
+  tet_infoline("Test that an actor/body pair can be removed");
+
+  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);
+
+  btRigidBody* body;
+  {
+    auto accessor    = adaptor.GetPhysicsAccessor();
+    auto bulletWorld = accessor->GetNative().Get<btDiscreteDynamicsWorld*>();
+
+    body = CreateBody(bulletWorld);
+  }
+  Dali::Actor ballActor    = Toolkit::ImageView::New("gallery-small-1.jpg");
+  auto        physicsActor = adaptor.AddActorBody(ballActor, body);
+
+  application.SendNotification();
+  application.Render();
+  application.SendNotification();
+  application.Render();
+
+  adaptor.RemoveActorBody(physicsActor);
+  DALI_TEST_CHECK(!ballActor.GetParent());
+
+  {
+    auto accessor    = adaptor.GetPhysicsAccessor();
+    auto bulletWorld = accessor->GetNative().Get<btDiscreteDynamicsWorld*>();
+
+    bulletWorld->removeRigidBody(body);
+    try
+    {
+      delete body;
+      tet_result(TET_PASS);
+    }
+    catch(std::exception& e)
+    {
+      tet_result(TET_FAIL);
+    }
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DAdaptorRemoveActorBodyN01(void)
+{
+  tet_infoline("Test that an empty actor/body pair doesn't break adaptor");
+
+  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);
+
+  tet_infoline("Test that removing a physics actor that hasn't been created with AddActorBody does nothing");
+
+  Dali::Actor  actor = Dali::Actor::New();
+  btRigidBody* body;
+  {
+    auto accessor    = adaptor.GetPhysicsAccessor();
+    auto bulletWorld = accessor->GetNative().Get<btDiscreteDynamicsWorld*>();
+
+    body = CreateBody(bulletWorld);
+  }
+
+  PhysicsActor physicsActor = PhysicsActor::New(actor, body, adaptor);
+  ;
+  try
+  {
+    adaptor.RemoveActorBody(physicsActor);
+    tet_result(TET_PASS);
+  }
+  catch(std::exception& e)
+  {
+    tet_result(TET_FAIL);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DAdaptorRemoveActorBodyN02(void)
+{
+  tet_infoline("Test that an empty actor/body pair doesn't break adaptor");
+
+  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);
+
+  PhysicsActor physicsActor;
+  try
+  {
+    adaptor.RemoveActorBody(physicsActor);
+    tet_result(TET_FAIL);
+  }
+  catch(DaliException& e)
+  {
+    DALI_TEST_ASSERT(e, "Physics actor handle is empty", TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliPhysics3DAdaptorGetPhysicsActor(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<btDiscreteDynamicsWorld*>();
+
+  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 UtcDaliPhysics3DAdaptorBuildPickingRay(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 UtcDaliPhysics3DAdaptorProjectPoint(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 UtcDaliPhysics3DAdaptorQueue(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<btDiscreteDynamicsWorld*>();
+    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 UtcDaliPhysics3DAdaptorCreateSyncPoint(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<btDiscreteDynamicsWorld*>();
+    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 UtcDaliPhysics3DAdaptorHitTestP(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<btDiscreteDynamicsWorld*>();
+    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?!
index f11bfbf..ec10a33 100755 (executable)
@@ -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
index fcadd6f..a31f162 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TEST_COMPARE_TYPES_H
 
 /*
- * Copyright (c) 2022 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.
@@ -27,9 +27,9 @@ inline bool CompareType(Type value1, Type value2, float epsilon)
 }
 
 /**
- * A helper for fuzzy-comparing Vector2 objects
- * @param[in] vector1 the first object
- * @param[in] vector2 the second object
+ * A helper for matching floats
+ * @param[in] value1 the first object
+ * @param[in] value2 the second object
  * @param[in] epsilon difference threshold
  * @returns true if difference is smaller than epsilon threshold, false otherwise
  */
@@ -40,6 +40,19 @@ inline bool CompareType<float>(float value1, float value2, float epsilon)
 }
 
 /**
+ * A helper for matching doubles
+ * @param[in] value1 the first object
+ * @param[in] value2 the second object
+ * @param[in] epsilon difference threshold
+ * @returns true if difference is smaller than epsilon threshold, false otherwise
+ */
+template<>
+inline bool CompareType<double>(double value1, double value2, float epsilon)
+{
+  return fabs(value1 - value2) < double(epsilon);
+}
+
+/**
  * A helper for fuzzy-comparing Vector2 objects
  * @param[in] vector1 the first object
  * @param[in] vector2 the second object
index 31080a4..4237327 100644 (file)
@@ -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 (file)
index 0000000..198c849
--- /dev/null
@@ -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 <EGL/egl.h>
+#include <GLES3/gl3.h>
+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<GLsync>(++syncId);
+  }
+
+  GLenum glCheckFramebufferStatus(GLenum target)
+  {
+    return GL_FRAMEBUFFER_COMPLETE;
+  }
+}
\ No newline at end of file
index 1e36828..ac967f8 100644 (file)
@@ -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();
index 49a4c6f..6d3c511 100644 (file)
@@ -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<Dali::Window> 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 (file)
index 35ee43e..0000000
+++ /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 <EGL/egl.h>
-#include <GLES3/gl3.h>
-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<GLsync>(++syncId);
-}
-
-GLenum glCheckFramebufferStatus (GLenum target)
-{
-  return GL_FRAMEBUFFER_COMPLETE;
-}
-
-}
\ No newline at end of file
index 6a5a089..732d6b1 100644 (file)
@@ -2126,3 +2126,153 @@ int UtcDaliAnimatedVectorImageVisualDesiredSize(void)
 
   END_TEST;
 }
+
+int UtcDaliAnimatedVectorImageVisualFlushAction(void)
+{
+  ToolkitTestApplication application;
+
+  tet_infoline("UtcDaliAnimatedVectorImageVisualFlushAction");
+
+  int startFrame = 1;
+  int endFrame   = 2;
+
+  int totalFrameCount = 0;
+
+  Property::Array playRange;
+  playRange.PushBack(startFrame);
+  playRange.PushBack(endFrame);
+
+  Property::Map    resultMap;
+  Property::Value* value = nullptr;
+
+  // request AnimatedVectorImageVisual with a property map
+  VisualFactory factory = VisualFactory::Get();
+  Visual::Base  visual  = factory.CreateVisual(
+    Property::Map()
+      .Add(Toolkit::Visual::Property::TYPE, DevelVisual::ANIMATED_VECTOR_IMAGE)
+      .Add(ImageVisual::Property::URL, TEST_VECTOR_IMAGE_FILE_NAME)
+      .Add(DevelImageVisual::Property::PLAY_RANGE, playRange)
+      .Add(ImageVisual::Property::SYNCHRONOUS_LOADING, true));
+
+  DummyControl        dummyControl = DummyControl::New(true);
+  Impl::DummyControl& dummyImpl    = static_cast<Impl::DummyControl&>(dummyControl.GetImplementation());
+  dummyImpl.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual);
+  dummyControl.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
+
+  application.GetScene().Add(dummyControl);
+
+  // Retry function to get playrange until expect values comes.
+  auto CheckAndRetryPlayRange = [&](int expectStartFrame, int expectEndFrame, std::vector<std::pair<int, int>> retrialFrames) {
+    int tryCount    = 0;
+    int tryCountMax = 30;
+    while(++tryCount <= tryCountMax)
+    {
+      Property::Map resultMap = dummyControl.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
+
+      Property::Value* value = resultMap.Find(DevelImageVisual::Property::PLAY_RANGE, Property::ARRAY);
+      DALI_TEST_CHECK(value);
+
+      Property::Array* result = value->GetArray();
+      DALI_TEST_CHECK(result);
+      DALI_TEST_EQUALS(result->Count(), 2, TEST_LOCATION);
+
+      bool tryAgain = false;
+      for(auto& framePair : retrialFrames)
+      {
+        if(result->GetElementAt(0).Get<int>() == framePair.first && result->GetElementAt(1).Get<int>() == framePair.second)
+        {
+          tryAgain = true;
+          break;
+        }
+      }
+      if(tryAgain)
+      {
+        tet_printf("Retry to get value again! [%d]\n", tryCount);
+        // Dummy sleep 1 second.
+        Test::WaitForEventThreadTrigger(1, 1);
+        continue;
+      }
+
+      DALI_TEST_EQUALS(result->GetElementAt(0).Get<int>(), expectStartFrame, TEST_LOCATION);
+      DALI_TEST_EQUALS(result->GetElementAt(1).Get<int>(), expectEndFrame, TEST_LOCATION);
+      break;
+    }
+    DALI_TEST_CHECK(tryCount <= tryCountMax);
+  };
+
+  tet_printf("Pause lottie first.\n");
+
+  Property::Map attributes;
+  DevelControl::DoAction(dummyControl, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::PAUSE, attributes);
+
+  application.SendNotification();
+  application.Render(16);
+
+  do
+  {
+    resultMap = dummyControl.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
+
+    value = resultMap.Find(DevelImageVisual::Property::TOTAL_FRAME_NUMBER, Property::INTEGER);
+    DALI_TEST_CHECK(value);
+    totalFrameCount = value->Get<int>();
+  } while(totalFrameCount == 0);
+
+  // Ensure that vector data sended well.
+  CheckAndRetryPlayRange(startFrame, endFrame, {{0, 0}, {0, totalFrameCount - 1}});
+
+  resultMap = dummyControl.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
+
+  value = resultMap.Find(DevelImageVisual::Property::CURRENT_FRAME_NUMBER, Property::INTEGER);
+  DALI_TEST_CHECK(value);
+  DALI_TEST_EQUALS(value->Get<int>(), startFrame, TEST_LOCATION);
+
+  tet_printf("Now logically, range : [%d~%d], current : %d\n", startFrame, endFrame, startFrame);
+
+  int changedStartFrame1 = startFrame + 2;
+  int changedEndFrame1   = endFrame + 2;
+
+  playRange.Clear();
+  playRange.Add(changedStartFrame1);
+  playRange.Add(changedEndFrame1);
+
+  tet_printf("Change play range\n");
+  attributes.Add(DevelImageVisual::Property::PLAY_RANGE, playRange);
+  DevelControl::DoAction(dummyControl, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelVisual::Action::UPDATE_PROPERTY, attributes);
+
+  tet_printf("Jump to changedEndFrame!\n");
+  DevelControl::DoAction(dummyControl, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::JUMP_TO, changedEndFrame1);
+
+  attributes.Clear();
+  tet_infoline("Flush Action!");
+  tet_printf("Now logically, range : [%d~%d], current : %d\n", changedStartFrame1, changedEndFrame1, changedEndFrame1);
+  DevelControl::DoAction(dummyControl, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelAnimatedVectorImageVisual::Action::FLUSH, attributes);
+
+  int changedStartFrame2 = startFrame + 1;
+  int changedEndFrame2   = endFrame + 1;
+
+  playRange.Clear();
+  playRange.Add(changedStartFrame2);
+  playRange.Add(changedEndFrame2);
+
+  tet_printf("Change play range again\n");
+  tet_printf("Now logically, range : [%d~%d], current : %d\n", changedStartFrame2, changedEndFrame2, changedEndFrame2);
+  attributes.Add(DevelImageVisual::Property::PLAY_RANGE, playRange);
+  DevelControl::DoAction(dummyControl, DummyControl::Property::TEST_VISUAL, Dali::Toolkit::DevelVisual::Action::UPDATE_PROPERTY, attributes);
+
+  application.SendNotification();
+  application.Render(16);
+
+  // Ensure that vector data sended well.
+  CheckAndRetryPlayRange(changedStartFrame2, changedEndFrame2, {{changedStartFrame1, changedEndFrame1}, {startFrame, endFrame}});
+
+  resultMap = dummyControl.GetProperty<Property::Map>(DummyControl::Property::TEST_VISUAL);
+
+  tet_printf("Test whether current frame number changed well. If Flush not works, current frame become startFrame.");
+  value = resultMap.Find(DevelImageVisual::Property::CURRENT_FRAME_NUMBER, Property::INTEGER);
+  DALI_TEST_CHECK(value);
+  DALI_TEST_EQUALS(value->Get<int>(), changedEndFrame2, TEST_LOCATION);
+
+  dummyControl.Unparent();
+
+  END_TEST;
+}
\ No newline at end of file
index ccc8eae..6fb05ab 100644 (file)
@@ -71,6 +71,8 @@ const char* TEST_BROKEN_IMAGE_L       = TEST_RESOURCE_DIR "/broken_l.9.png";
 const char* TEST_BROKEN_IMAGE_01      = TEST_RESOURCE_DIR "/button-up.9.png";
 const char* TEST_BROKEN_IMAGE_02      = TEST_RESOURCE_DIR "/heartsframe.9.png";
 
+const char* TEST_INVALID_NPATCH_FILE_NAME_01 = "invalid1.9.png";
+
 // resolution: 34*34, pixel format: RGBA8888
 static const char* gImage_34_RGBA = TEST_RESOURCE_DIR "/icon-edit.png";
 // resolution: 600*600, pixel format: RGB888
@@ -3672,6 +3674,45 @@ void OnResourceReadySignal07(Control control)
   }
 }
 
+void OnResourceReadySignal08(Control control)
+{
+  gResourceReadySignalCounter++;
+
+  if(gImageView1)
+  {
+    gImageView1.Unparent();
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Unparent();
+    gImageView2.Reset();
+  }
+}
+
+std::size_t gResourceReadySignal09Emitted = false;
+
+void OnResourceReadySignal09(Control control)
+{
+  gResourceReadySignalCounter++;
+
+  if(gImageView1 && !gResourceReadySignal09Emitted)
+  {
+    gResourceReadySignal09Emitted = true;
+    gImageView1.ResourceReadySignal().Disconnect(&OnResourceReadySignal09);
+
+    // Try to load cached invalid nine patch image. It will request load now.
+    gImageView1.SetImage(TEST_INVALID_NPATCH_FILE_NAME_01);
+    gImageView2.SetImage(TEST_INVALID_NPATCH_FILE_NAME_01);
+
+    // Destroy all visuals immediatly.
+    gImageView1.Unparent();
+    gImageView1.Reset();
+    gImageView2.Unparent();
+    gImageView2.Reset();
+  }
+}
+
 } // namespace
 
 int UtcDaliImageViewSetImageOnResourceReadySignal01(void)
@@ -4208,6 +4249,239 @@ int UtcDaliImageViewSetImageOnResourceReadySignal07(void)
   END_TEST;
 }
 
+int UtcDaliImageViewSetImageOnResourceReadySignal08(void)
+{
+  tet_infoline("Test remove npatch images during resource ready");
+
+  ToolkitTestApplication application;
+
+  gResourceReadySignalCounter = 0;
+
+  Property::Map map;
+  map[Toolkit::ImageVisual::Property::URL] = TEST_BROKEN_IMAGE_M;
+
+  // Clear image view for clear test
+
+  if(gImageView1)
+  {
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Reset();
+  }
+
+  // Case 1 : Remove all images during resource ready.
+  try
+  {
+    gImageView1 = ImageView::New();
+    gImageView1.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView1.ResourceReadySignal().Connect(&OnResourceReadySignal08);
+    application.GetScene().Add(gImageView1);
+
+    application.SendNotification();
+    application.Render();
+
+    // Load gImageView1
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+    DALI_TEST_EQUALS(gResourceReadySignalCounter, 1, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    // Exception should not happened
+    DALI_TEST_CHECK(false);
+  }
+
+  // Clear cache.
+  application.SendNotification();
+  application.Render();
+
+  gResourceReadySignalCounter = 0;
+
+  // Case 2 : Remove all images when we use cached resource.
+  try
+  {
+    gImageView1 = ImageView::New();
+    gImageView1.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView1.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+    application.GetScene().Add(gImageView1);
+
+    application.SendNotification();
+    application.Render();
+
+    // Load gImageView1
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+    DALI_TEST_EQUALS(gResourceReadySignalCounter, 1, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    gImageView2 = ImageView::New();
+    gImageView2.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView2.ResourceReadySignal().Connect(&OnResourceReadySignal08);
+    application.GetScene().Add(gImageView2);
+    DALI_TEST_EQUALS(gResourceReadySignalCounter, 2, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    // Exception should not happened
+    DALI_TEST_CHECK(false);
+  }
+  gResourceReadySignalCounter = 0;
+
+  // Clear image view for clear test
+
+  if(gImageView1)
+  {
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Reset();
+  }
+
+  END_TEST;
+}
+
+int UtcDaliImageViewSetImageOnResourceReadySignal09(void)
+{
+  tet_infoline("Test load invalid npatch images during invalid resource ready");
+
+  ToolkitTestApplication application;
+
+  gResourceReadySignalCounter = 0;
+
+  Property::Map map;
+  map[Toolkit::ImageVisual::Property::URL] = TEST_INVALID_NPATCH_FILE_NAME_01;
+
+  // Clear image view for clear test
+
+  if(gImageView1)
+  {
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Reset();
+  }
+  if(gImageView3)
+  {
+    gImageView3.Reset();
+  }
+
+  // Dummy view with npatch image
+  ImageView dummyView = ImageView::New(TEST_BROKEN_IMAGE_M);
+  application.GetScene().Add(dummyView);
+
+  application.SendNotification();
+  application.Render();
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+  application.SendNotification();
+  application.Render();
+
+  // Case 1 : Reload images during resource ready.
+  try
+  {
+    gResourceReadySignal09Emitted = false;
+
+    gImageView1 = ImageView::New();
+    gImageView1.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView1.ResourceReadySignal().Connect(&OnResourceReadySignal09);
+    application.GetScene().Add(gImageView1);
+
+    gImageView2 = ImageView::New();
+    application.GetScene().Add(gImageView2);
+
+    // Load TEST_INVALID_NPATCH_FILE_NAME_01
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    // Load TEST_INVALID_NPATCH_FILE_NAME_01 one more times.
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    // Exception should not happened
+    DALI_TEST_CHECK(false);
+  }
+
+  // Clear cache.
+  application.SendNotification();
+  application.Render();
+
+  gResourceReadySignalCounter = 0;
+
+  // Case 2 : Remove all images when we use cached resource.
+  try
+  {
+    gResourceReadySignal09Emitted = false;
+
+    gImageView3 = ImageView::New();
+    gImageView3.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView3.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+    application.GetScene().Add(gImageView3);
+
+    gImageView2 = ImageView::New();
+    application.GetScene().Add(gImageView2);
+
+    // Load gImageView2
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    gImageView1 = ImageView::New();
+    gImageView1.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
+    gImageView1.ResourceReadySignal().Connect(&OnResourceReadySignal09);
+    application.GetScene().Add(gImageView1);
+
+    // Load gImageView1
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    // Load TEST_INVALID_NPATCH_FILE_NAME_01 one more times.
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render();
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    // Exception should not happened
+    DALI_TEST_CHECK(false);
+  }
+  gResourceReadySignalCounter = 0;
+
+  // Clear image view for clear test
+
+  if(gImageView1)
+  {
+    gImageView1.Reset();
+  }
+  if(gImageView2)
+  {
+    gImageView2.Reset();
+  }
+  if(gImageView3)
+  {
+    gImageView3.Reset();
+  }
+
+  END_TEST;
+}
+
 int UtcDaliImageViewUseSameUrlWithAnimatedImageVisual(void)
 {
   tet_infoline("Test multiple views with same image in animated image visual");
@@ -4247,6 +4521,11 @@ int UtcDaliImageViewNpatchImageCacheTest01(void)
     {
       imageView[index].Unparent();
     }
+
+    // Ensure remove npatch cache if required.
+    application.SendNotification();
+    application.Render();
+
     imageView[index] = ImageView::New(nPatchImageUrl);
     imageView[index].SetProperty(Actor::Property::SIZE, Vector2(100.0f, 200.0f));
     application.GetScene().Add(imageView[index]);
index f09a06e..26782ba 100644 (file)
@@ -4,3 +4,7 @@ dali.info
 dali2-*-config.cmake
 libdali2-scene3d.so*
 dali-shader-generator
+build.ninja
+rules.ninja
+.ninja_deps
+.ninja_log
index d6dbad2..00c482b 100644 (file)
@@ -33,7 +33,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${extra_flags}")
 
 set(prefix ${CMAKE_INSTALL_PREFIX})
 
-set(repo_root_dir "${CMAKE_CURRENT_LIST_DIR}/../../../")
+set(repo_root_dir "${CMAKE_CURRENT_LIST_DIR}/../../..")
 set(physics_dir "${repo_root_dir}/dali-physics")
 
 option(ENABLE_PKG_CONFIGURE "Use pkgconfig" ON)
@@ -70,25 +70,116 @@ 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
+set(physics_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 "2D sources: ${physics2d_src_files}")
+MESSAGE(STATUS "3D sources: ${physics3d_src_files}")
+
+ADD_LIBRARY("${name}-2d" SHARED ${physics2d_src_files} )
+TARGET_LINK_LIBRARIES("${name}-2d" ${DALICORE_LDFLAGS}
+  dali2-toolkit
+  chipmunk
+  ${COVERAGE})
+TARGET_COMPILE_OPTIONS("${name}-2d" PUBLIC "-I${repo_root_dir}/dali-physics/third-party/chipmunk2d/include")
+
+ADD_LIBRARY("${name}-3d" SHARED ${physics3d_src_files} )
+TARGET_LINK_LIBRARIES("${name}-3d" ${DALICORE_LDFLAGS}
+  dali2-toolkit
+  bullet3
+  ${COVERAGE})
+TARGET_COMPILE_OPTIONS("${name}-3d" PUBLIC "-I${repo_root_dir}/dali-physics/third-party/bullet3/src")
+
+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}-2d
+    PROPERTIES
+    VERSION ${DALI_PHYSICS_VERSION}
+    SOVERSION ${${name}_VERSION_MAJOR}
+    CLEAN_DIRECT_OUPUT 1
     )
-endif()
 
-if( INSTALL_CMAKE_MODULES )
+  SET_TARGET_PROPERTIES( ${name}-3d
+    PROPERTIES
+    VERSION ${DALI_PHYSICS_VERSION}
+    SOVERSION ${${name}_VERSION_MAJOR}
+    CLEAN_DIRECT_OUPUT 1
+    )
 
-  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}-2d
+    EXPORT ${name}-2d-targets
+    LIBRARY DESTINATION ${LIB_DIR}
+    ARCHIVE DESTINATION ${LIB_DIR}
+    RUNTIME DESTINATION ${BIN_DIR}
+  )
+
+  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}-2d-targets
+    NAMESPACE ${name}-2d::
+    FILE ${name}-2d-targets.cmake
+    DESTINATION share/${name}-2d
+  )
+
+  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}-2d DESTINATION ${LIB_DIR} )
+  INSTALL( TARGETS ${name}-3d DESTINATION ${LIB_DIR} )
+
+ENDIF()
 
+# Install headers
+install( FILES ${physics_public_api_header_files}
+  DESTINATION "${INCLUDE_DIR}/dali-physics/public-api"
+  )
 
+install( FILES ${physics_dir}/dali-physics.h
+  DESTINATION "${INCLUDE_DIR}/dali-physics"
+  )
index 75f9216..29dcbc1 100644 (file)
@@ -8,5 +8,5 @@ Name: DALi Engine Physics Library
 Description: Dali Physics 2D library
 Version: ${apiversion}
 Requires: chipmunk2d
-Libs: -L${libdir}
+Libs: -L${libdir} -ldali2-physics-2d
 Cflags: -I${includedir}
index 739026b..b5e7001 100644 (file)
@@ -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 (file)
index 0000000..a473a61
--- /dev/null
@@ -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 (file)
index 0000000..7e8135b
--- /dev/null
@@ -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 <dali-physics/public-api/physics-actor.h>
+#include <dali-physics/public-api/physics-adaptor.h>
diff --git a/dali-physics/internal/bullet-impl/bullet-physics-actor-impl.cpp b/dali-physics/internal/bullet-impl/bullet-physics-actor-impl.cpp
new file mode 100644 (file)
index 0000000..eec3929
--- /dev/null
@@ -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 <dali-physics/internal/physics-actor-impl.h>
+
+#include <btBulletDynamicsCommon.h>
+#include <dali-physics/internal/physics-adaptor-impl.h>
+
+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<int>(Dali::Actor::Property::ID)),
+  mBody(body)
+{
+}
+
+PhysicsActor::~PhysicsActor() = default;
+
+void PhysicsActor::Initialize(void)
+{
+}
+
+void PhysicsActor::AsyncSetPhysicsPosition(Dali::Vector3 actorPosition)
+{
+  // Queue task
+  btRigidBody* body = mBody.Get<btRigidBody*>();
+  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<btRigidBody*>();
+  auto         q    = fromQuat(mAdaptor.TranslateToPhysicsSpace(rotation));
+  mAdaptor.Queue([body, q]() { body->getWorldTransform().setRotation(q); });
+}
+
+Dali::Vector3 PhysicsActor::GetPhysicsPosition() const
+{
+  btRigidBody* body = mBody.Get<btRigidBody*>();
+  return toVec3(body->getWorldTransform().getOrigin());
+}
+
+Dali::Quaternion PhysicsActor::GetPhysicsRotation() const
+{
+  btRigidBody* body = mBody.Get<btRigidBody*>();
+  return toQuat(body->getWorldTransform().getRotation());
+}
+
+Dali::Vector3 PhysicsActor::GetActorPosition() const
+{
+  btRigidBody*       body      = mBody.Get<btRigidBody*>();
+  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<btRigidBody*>();
+  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/bullet-impl/bullet-physics-adaptor-impl.cpp b/dali-physics/internal/bullet-impl/bullet-physics-adaptor-impl.cpp
new file mode 100644 (file)
index 0000000..9e36059
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * 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 <dali-physics/internal/bullet-impl/bullet-physics-adaptor-impl.h>
+
+// External Headers
+#include <btBulletDynamicsCommon.h>
+#include <utility>
+
+// Internal Headers
+#include <dali-physics/internal/bullet-impl/bullet-physics-world-impl.h>
+#include <dali/dali.h>
+#include <dali/devel-api/threading/mutex.h>
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/actors/drawable-actor.h>
+
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_PHYSICS");
+#endif
+
+} // namespace
+
+namespace Dali::Toolkit::Physics::Internal
+{
+PhysicsAdaptorPtr CreateNewPhysicsAdaptor(const Dali::Matrix& transform, Uint16Pair worldSize)
+{
+  PhysicsAdaptorPtr adaptor(new BulletPhysicsAdaptor());
+  adaptor->Initialize(transform, worldSize);
+  return adaptor;
+}
+
+BulletPhysicsAdaptor::BulletPhysicsAdaptor()
+: PhysicsAdaptor()
+{
+}
+
+BulletPhysicsAdaptor::~BulletPhysicsAdaptor()
+{
+  // @todo Ensure physics bodies don't leak
+}
+
+void BulletPhysicsAdaptor::OnInitialize(const Dali::Matrix& transform, Uint16Pair worldSize)
+{
+  mTransform        = transform;
+  mInverseTransform = transform;
+  mInverseTransform.Invert();
+  mSize = worldSize;
+
+  mPhysicsWorld = BulletPhysicsWorld::New(mRootActor,
+                                          Dali::MakeCallback(mSlotDelegate.GetSlot(),
+                                                             &PhysicsAdaptor::OnUpdateActors));
+}
+
+Layer BulletPhysicsAdaptor::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::NAME]          = "PhysicsDebugLayer";
+  debugLayer[Actor::Property::ANCHOR_POINT]  = Dali::AnchorPoint::CENTER;
+  debugLayer[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER;
+
+  Constraint positionConstraint = Constraint::New<Vector3>(debugLayer, Actor::Property::POSITION, EqualToConstraint());
+  positionConstraint.AddSource(Source(mRootActor, Actor::Property::POSITION));
+  positionConstraint.Apply();
+  Constraint sizeConstraint = Constraint::New<Vector2>(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<Vector2>(mDebugActor, Actor::Property::SIZE, EqualToConstraint());
+  sizeConstraint2.AddSource(ParentSource(Actor::Property::SIZE));
+  sizeConstraint2.Apply();
+
+  debugLayer.Add(mDebugActor);
+
+  auto bulletWorld = mPhysicsWorld->GetNative().Get<btDiscreteDynamicsWorld*>();
+
+  bulletWorld->setDebugDrawer(mDebugRenderer.get());
+  mDebugRenderer->setDebugMode(btIDebugDraw::DBG_DrawWireframe |
+                               btIDebugDraw::DBG_DrawContactPoints |
+                               btIDebugDraw::DBG_DrawNormals);
+
+  window.Add(debugLayer);
+  return debugLayer;
+}
+
+void BulletPhysicsAdaptor::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);
+  }
+}
+
+PhysicsActorPtr BulletPhysicsAdaptor::AddActorBody(Dali::Actor actor, Dali::Any body)
+{
+  uint32_t     id     = static_cast<uint32_t>(actor.GetProperty<int>(Actor::Property::ID));
+  btRigidBody* btBody = body.Get<btRigidBody*>();
+
+  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);
+}
+
+void BulletPhysicsAdaptor::RemoveActorBody(PhysicsActor& physicsActor)
+{
+  auto iter = mPhysicsActors.find(physicsActor.GetId());
+  if(iter != mPhysicsActors.end())
+  {
+    mPhysicsActors.erase(iter);
+  }
+  Dali::Actor actor = mRootActor.FindChildById(physicsActor.GetId());
+  if(actor)
+  {
+    actor.Unparent();
+  }
+
+  auto         body   = physicsActor.GetBody();
+  btRigidBody* btBody = body.Get<btRigidBody*>();
+  if(btBody)
+  {
+    btBody->setUserIndex(-1);
+  }
+}
+
+PhysicsActorPtr BulletPhysicsAdaptor::GetPhysicsActor(Dali::Any body) const
+{
+  btRigidBody* btBody = body.Get<btRigidBody*>();
+  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;
+}
+
+// Convert a position from root actor local space to physics space
+Vector3 BulletPhysicsAdaptor::TranslateToPhysicsSpace(Vector3 vector) const
+{
+  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 BulletPhysicsAdaptor::TranslateFromPhysicsSpace(Vector3 vector) const
+{
+  Vector4 position = mInverseTransform * Vector4(vector.x, vector.y, vector.z, 1.0f);
+  return Vector3(position);
+}
+
+Quaternion BulletPhysicsAdaptor::TranslateToPhysicsSpace(Quaternion orientation) const
+{
+  // 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 BulletPhysicsAdaptor::TranslateFromPhysicsSpace(Quaternion orientation) const
+{
+  // Mirroring conversion is identical in both transforms
+  return TranslateToPhysicsSpace(orientation);
+}
+
+// Convert a vector from dali space to physics space
+Vector3 BulletPhysicsAdaptor::ConvertVectorToPhysicsSpace(Vector3 vector) const
+{
+  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 BulletPhysicsAdaptor::ConvertVectorFromPhysicsSpace(Vector3 vector) const
+{
+  Vector4 otherVector(mInverseTransform * Vector4(vector.x, vector.y, vector.z, 0.0f));
+  return Vector3(otherVector);
+}
+
+void BulletPhysicsAdaptor::BuildPickingRay(Vector3 origin, Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld)
+{
+  rayFromWorld = TranslateToPhysicsSpace(origin);
+  rayToWorld   = TranslateToPhysicsSpace(origin + direction * 10000.0f);
+}
+
+Vector3 BulletPhysicsAdaptor::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;
+  return (rayFromWorld + dir);
+}
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/bullet-impl/bullet-physics-adaptor-impl.h b/dali-physics/internal/bullet-impl/bullet-physics-adaptor-impl.h
new file mode 100644 (file)
index 0000000..3c9001a
--- /dev/null
@@ -0,0 +1,121 @@
+#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 adaptoried.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// EXTERNAL INCLUDES
+
+// INTERNAL INCLUDES
+#include <dali-physics/internal/bullet-impl/bullet-physics-debug-renderer.h>
+#include <dali-physics/internal/physics-adaptor-impl.h>
+
+namespace Dali::Toolkit::Physics::Internal
+{
+class PhysicsDebugRenderer;
+
+class BulletPhysicsAdaptor : public PhysicsAdaptor
+{
+public:
+  BulletPhysicsAdaptor();
+
+  /**
+   * A reference counted object may only be deleted by calling Unreference()
+   */
+  ~BulletPhysicsAdaptor() override;
+
+  // Remove copy constructor and copy assignment
+  BulletPhysicsAdaptor(const PhysicsAdaptor& handle) = delete;
+  BulletPhysicsAdaptor& 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);
+  void OnInitialize(const Dali::Matrix& transform, Uint16Pair size) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::CreateDebugLayer
+   */
+  Dali::Layer CreateDebugLayer(Dali::Window window) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetTransformAndSize
+   */
+  void SetTransformAndSize(const Dali::Matrix& transform, Uint16Pair size) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateToPhysicsSpace
+   */
+  Dali::Vector3 TranslateToPhysicsSpace(Dali::Vector3 vector) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateToPhysicsSpace
+   */
+  Dali::Quaternion TranslateToPhysicsSpace(Dali::Quaternion rotation) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateFromPhysicsSpace
+   */
+  Dali::Vector3 TranslateFromPhysicsSpace(Dali::Vector3 vector) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateFromPhysicsSpace
+   */
+  Dali::Quaternion TranslateFromPhysicsSpace(Quaternion rotation) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ConvertVectorToPhysicsSpace
+   */
+  Dali::Vector3 ConvertVectorToPhysicsSpace(Dali::Vector3 vector) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ConvertVectorFromPhysicsSpace
+   */
+  Dali::Vector3 ConvertVectorFromPhysicsSpace(Dali::Vector3 vector) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::AddActorBody
+   */
+  PhysicsActorPtr AddActorBody(Dali::Actor actor, Dali::Any body) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::RemoveActorBody
+   */
+  void RemoveActorBody(PhysicsActor& physicsActor) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetPhysicsActor
+   */
+  PhysicsActorPtr GetPhysicsActor(Dali::Any body) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::BuildPickingRay
+   */
+  void BuildPickingRay(Dali::Vector3 origin, Dali::Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ProjectPoint
+   */
+  Dali::Vector3 ProjectPoint(Dali::Vector3 origin, Dali::Vector3 direction, float distance) override;
+
+private:
+  Actor                                 mDebugActor;
+  std::unique_ptr<PhysicsDebugRenderer> mDebugRenderer;
+};
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/bullet-impl/bullet-physics-debug-renderer.cpp b/dali-physics/internal/bullet-impl/bullet-physics-debug-renderer.cpp
new file mode 100644 (file)
index 0000000..3b5d390
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * 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 <dali-physics/internal/bullet-impl/bullet-physics-debug-renderer.h>
+
+// External Includes
+#include <dali/dali.h>
+
+// Internal Includes
+#include <dali-physics/internal/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<char> 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<char> 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> PhysicsDebugRenderer::New(uint32_t width, uint32_t height, Dali::CameraActor camera, PhysicsAdaptor* adaptor)
+{
+  auto renderer             = std::make_unique<PhysicsDebugRenderer>(width, height, camera, adaptor);
+  renderer->mRenderCallback = Dali::RenderCallback::New<PhysicsDebugRenderer>(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),
+  mVertexLocation(-1),
+  mVertexColourLocation(-1),
+  mProjectionLocation(-1),
+  mModelViewLocation(-1),
+  mBufferId(0u),
+  mProgramId(0u)
+{
+}
+
+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<const void*>(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/bullet-impl/bullet-physics-debug-renderer.h b/dali-physics/internal/bullet-impl/bullet-physics-debug-renderer.h
new file mode 100644 (file)
index 0000000..5c15601
--- /dev/null
@@ -0,0 +1,107 @@
+#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.
+ */
+
+// External includes
+#include <GLES3/gl3.h>
+#include <LinearMath/btIDebugDraw.h>
+#include <dali/dali.h>
+
+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<PhysicsDebugRenderer> New(uint32_t width, uint32_t height, Dali::CameraActor camera, PhysicsAdaptor* adaptor);
+
+  /**
+   * Get the callback (for actor creation)
+   */
+  std::unique_ptr<Dali::RenderCallback>& 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<Dali::RenderCallback> mRenderCallback;
+
+  enum class State
+  {
+    INIT,
+    RENDER
+  } mState{State::INIT};
+
+  struct VertexLine
+  {
+    Dali::Vector3 position;
+    Dali::Vector3 color;
+  };
+  std::vector<VertexLine> 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/bullet-impl/bullet-physics-world-impl.cpp b/dali-physics/internal/bullet-impl/bullet-physics-world-impl.cpp
new file mode 100644 (file)
index 0000000..79d97de
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * 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 <dali-physics/internal/bullet-impl/bullet-physics-world-impl.h>
+
+// External Headers
+#include <BulletCollision/NarrowPhaseCollision/btRaycastCallback.h>
+#include <btBulletCollisionCommon.h>
+#include <memory>
+
+// Internal Headers
+#include <dali/dali.h>
+#include <dali/devel-api/common/stage-devel.h>
+#include <dali/devel-api/update/frame-callback-interface.h>
+
+namespace Dali::Toolkit::Physics::Internal
+{
+std::unique_ptr<PhysicsWorld> BulletPhysicsWorld::New(Dali::Actor rootActor, Dali::CallbackBase* updateCallback)
+{
+  std::unique_ptr<BulletPhysicsWorld> world = std::make_unique<BulletPhysicsWorld>(rootActor, updateCallback);
+  world->Initialize();
+  return world;
+}
+
+BulletPhysicsWorld::BulletPhysicsWorld(Dali::Actor rootActor, Dali::CallbackBase* updateCallback)
+: PhysicsWorld(rootActor, updateCallback)
+{
+}
+
+void BulletPhysicsWorld::OnInitialize(/*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);
+}
+
+BulletPhysicsWorld::~BulletPhysicsWorld()
+{
+  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::Any BulletPhysicsWorld::GetNative()
+{
+  return mDynamicsWorld;
+}
+
+void BulletPhysicsWorld::Integrate(float timestep)
+{
+  if(mPhysicsIntegrateState == Physics::PhysicsAdaptor::IntegrationState::ON)
+  {
+    mDynamicsWorld->stepSimulation(timestep);
+  }
+  if(mDynamicsWorld->getDebugDrawer() && mPhysicsDebugState == Physics::PhysicsAdaptor::DebugState::ON)
+  {
+    mDynamicsWorld->debugDrawWorld();
+  }
+}
+
+inline btVector3 ConvertVector(Dali::Vector3 vector)
+{
+  return btVector3(vector.x, vector.y, vector.z);
+}
+
+Dali::Any BulletPhysicsWorld::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;
+}
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/bullet-impl/bullet-physics-world-impl.h b/dali-physics/internal/bullet-impl/bullet-physics-world-impl.h
new file mode 100644 (file)
index 0000000..2a5ff55
--- /dev/null
@@ -0,0 +1,67 @@
+#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 <dali/dali.h>
+#include <dali/devel-api/threading/mutex.h>
+#include <dali/devel-api/update/frame-callback-interface.h>
+#include <dali/devel-api/update/update-proxy.h>
+
+#include <dali-physics/internal/physics-world-impl.h>
+#include <dali-physics/public-api/physics-adaptor.h>
+
+#include <btBulletDynamicsCommon.h>
+
+namespace Dali::Toolkit::Physics::Internal
+{
+class PhysicsWorld;
+class FrameCallback;
+
+class BulletPhysicsWorld : public PhysicsWorld
+{
+public:
+  static std::unique_ptr<PhysicsWorld> New(Dali::Actor rootActor, Dali::CallbackBase* updateCallback);
+
+  BulletPhysicsWorld(Dali::Actor rootActor, Dali::CallbackBase* updateCallback);
+  ~BulletPhysicsWorld();
+
+  void OnInitialize(/*void* dynamicsWorld*/) override;
+
+  Dali::Any GetNative() override;
+
+  /**
+   * 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) override;
+
+  void Integrate(float timestep) override;
+
+private:
+  btDiscreteDynamicsWorld*             mDynamicsWorld{nullptr};
+  btCollisionDispatcher*               mDispatcher{nullptr};
+  btDefaultCollisionConfiguration*     mCollisionConfiguration{nullptr};
+  btBroadphaseInterface*               mBroadphase{nullptr};
+  btSequentialImpulseConstraintSolver* mSolver{nullptr};
+};
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/chipmunk-impl/chipmunk-physics-actor-impl.cpp b/dali-physics/internal/chipmunk-impl/chipmunk-physics-actor-impl.cpp
new file mode 100644 (file)
index 0000000..eee9cd4
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * 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 <dali-physics/internal/physics-actor-impl.h>
+#include <dali-physics/internal/physics-adaptor-impl.h>
+
+#include <chipmunk/chipmunk.h>
+
+namespace
+{
+inline cpVect fromVec3(Dali::Vector3 vec3)
+{
+  return cpv(vec3.x, vec3.y);
+}
+
+inline Dali::Vector3 toVec3(cpVect vec)
+{
+  return Dali::Vector3(vec.x, vec.y, 0.0f);
+}
+
+} //Anonymous namespace
+
+namespace Dali::Toolkit::Physics::Internal
+{
+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<int>(Dali::Actor::Property::ID)),
+  mBody(body)
+{
+}
+
+PhysicsActor::~PhysicsActor() = default;
+
+void PhysicsActor::Initialize(void)
+{
+  cpBodySetUserData(mBody.Get<cpBody*>(), this);
+
+  // RegisterObject?
+}
+
+void PhysicsActor::AsyncSetPhysicsPosition(Dali::Vector3 actorPosition)
+{
+  // Queue task
+  cpBody* body = mBody.Get<cpBody*>();
+  cpVect  pos  = fromVec3(mAdaptor.TranslateToPhysicsSpace(actorPosition));
+  mAdaptor.Queue([body, pos] { cpBodySetPosition(body, pos); });
+}
+
+void PhysicsActor::AsyncSetPhysicsRotation(Dali::Quaternion rotation)
+{
+  // Queue task
+  cpBody* body = mBody.Get<cpBody*>();
+  auto    q    = mAdaptor.TranslateToPhysicsSpace(rotation);
+  Vector3 axis;
+  Radian  angle;
+  q.ToAxisAngle(axis, angle);
+  mAdaptor.Queue([body, angle]() { cpBodySetAngle(body, angle); });
+}
+
+Dali::Vector3 PhysicsActor::GetPhysicsPosition() const
+{
+  cpBody* body = mBody.Get<cpBody*>();
+  return toVec3(cpBodyGetPosition(body));
+}
+
+Dali::Quaternion PhysicsActor::GetPhysicsRotation() const
+{
+  cpBody* body  = mBody.Get<cpBody*>();
+  cpFloat angle = cpBodyGetAngle(body);
+  return Quaternion(Radian(angle), Vector3::ZAXIS);
+}
+
+Dali::Vector3 PhysicsActor::GetActorPosition() const
+{
+  cpBody* body       = mBody.Get<cpBody*>();
+  cpVect  cpPosition = cpBodyGetPosition(body);
+  return mAdaptor.TranslateFromPhysicsSpace(Vector3(cpPosition.x, cpPosition.y, 0.0f));
+}
+
+Dali::Quaternion PhysicsActor::GetActorRotation() const
+{
+  cpBody* body  = mBody.Get<cpBody*>();
+  cpFloat angle = cpBodyGetAngle(body);
+  return mAdaptor.TranslateFromPhysicsSpace(Quaternion(Radian(angle), Vector3::ZAXIS));
+}
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/chipmunk-impl/chipmunk-physics-adaptor-impl.cpp b/dali-physics/internal/chipmunk-impl/chipmunk-physics-adaptor-impl.cpp
new file mode 100644 (file)
index 0000000..1a4ffb9
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * 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 <dali-physics/internal/chipmunk-impl/chipmunk-physics-adaptor-impl.h>
+
+// External Headers
+#include <utility>
+
+// Internal Headers
+#include <dali-physics/internal/chipmunk-impl/chipmunk-physics-world-impl.h>
+#include <dali/dali.h>
+#include <dali/integration-api/debug.h>
+
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_PHYSICS");
+#endif
+
+inline cpVect ConvertVector(Dali::Vector3 vector)
+{
+  return cpv(vector.x, vector.y);
+}
+
+} // namespace
+
+namespace Dali::Toolkit::Physics::Internal
+{
+PhysicsAdaptorPtr CreateNewPhysicsAdaptor(const Dali::Matrix& transform, Uint16Pair worldSize)
+{
+  PhysicsAdaptorPtr adaptor(new ChipmunkPhysicsAdaptor());
+  adaptor->Initialize(transform, worldSize);
+  return adaptor;
+}
+
+ChipmunkPhysicsAdaptor::ChipmunkPhysicsAdaptor()
+: PhysicsAdaptor()
+{
+}
+
+ChipmunkPhysicsAdaptor::~ChipmunkPhysicsAdaptor()
+{
+  // @todo Ensure physics bodies don't leak
+}
+
+void ChipmunkPhysicsAdaptor::OnInitialize(const Dali::Matrix& transform, Uint16Pair worldSize)
+{
+  mTransform        = transform;
+  mInverseTransform = transform;
+  mInverseTransform.Invert();
+  mSize = worldSize;
+
+  mPhysicsWorld = ChipmunkPhysicsWorld::New(mRootActor,
+                                            Dali::MakeCallback(mSlotDelegate.GetSlot(),
+                                                               &PhysicsAdaptor::OnUpdateActors));
+}
+
+Layer ChipmunkPhysicsAdaptor::CreateDebugLayer(Dali::Window window)
+{
+  return Layer();
+}
+
+void ChipmunkPhysicsAdaptor::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);
+}
+
+PhysicsActorPtr ChipmunkPhysicsAdaptor::AddActorBody(Dali::Actor actor, Dali::Any body)
+{
+  uint32_t id    = static_cast<uint32_t>(actor.GetProperty<int>(Actor::Property::ID));
+  cpBody*  cBody = body.Get<cpBody*>();
+  cpBodySetUserData(cBody, this);
+
+  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);
+}
+
+void ChipmunkPhysicsAdaptor::RemoveActorBody(PhysicsActor& physicsActor)
+{
+  auto iter = mPhysicsActors.find(physicsActor.GetId());
+  if(iter != mPhysicsActors.end())
+  {
+    mPhysicsActors.erase(iter);
+  }
+  Dali::Actor actor = mRootActor.FindChildById(physicsActor.GetId());
+  if(actor)
+  {
+    actor.Unparent();
+  }
+  auto    body  = physicsActor.GetBody();
+  cpBody* cBody = body.Get<cpBody*>();
+  if(cBody)
+  {
+    cpBodySetUserData(cBody, nullptr);
+  }
+}
+
+PhysicsActorPtr ChipmunkPhysicsAdaptor::GetPhysicsActor(Dali::Any body) const
+{
+  cpBody* cBody = body.Get<cpBody*>();
+  if(cBody)
+  {
+    return reinterpret_cast<PhysicsActor*>(cpBodyGetUserData(cBody));
+  }
+  DALI_LOG_ERROR("Body not found in physics actors");
+  return nullptr;
+}
+
+// Convert a position from root actor local space to physics space
+Vector3 ChipmunkPhysicsAdaptor::TranslateToPhysicsSpace(Vector3 vector) const
+{
+  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 ChipmunkPhysicsAdaptor::TranslateFromPhysicsSpace(Vector3 vector) const
+{
+  Vector4 position = mInverseTransform * Vector4(vector.x, vector.y, vector.z, 1.0f);
+  return Vector3(position);
+}
+
+Quaternion ChipmunkPhysicsAdaptor::TranslateToPhysicsSpace(Quaternion orientation) const
+{
+  // It's complicated.
+  return orientation;
+}
+
+Quaternion ChipmunkPhysicsAdaptor::TranslateFromPhysicsSpace(Quaternion orientation) const
+{
+  // Mirroring conversion is identical in both transforms
+  return TranslateToPhysicsSpace(orientation);
+}
+
+// Convert a vector from dali space to physics space
+Vector3 ChipmunkPhysicsAdaptor::ConvertVectorToPhysicsSpace(Vector3 vector) const
+{
+  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 ChipmunkPhysicsAdaptor::ConvertVectorFromPhysicsSpace(Vector3 vector) const
+{
+  Vector4 otherVector(mInverseTransform * Vector4(vector.x, vector.y, vector.z, 0.0f));
+  return Vector3(otherVector);
+}
+
+void ChipmunkPhysicsAdaptor::BuildPickingRay(Vector3 origin, Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld)
+{
+  rayFromWorld = TranslateToPhysicsSpace(origin);
+  rayToWorld   = TranslateToPhysicsSpace(origin); // rayToWorld is identical - there's no depth
+}
+
+Vector3 ChipmunkPhysicsAdaptor::ProjectPoint(Vector3 origin, Vector3 direction, float distance)
+{
+  // Ignore direction & distance.
+  return TranslateToPhysicsSpace(origin);
+}
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/chipmunk-impl/chipmunk-physics-adaptor-impl.h b/dali-physics/internal/chipmunk-impl/chipmunk-physics-adaptor-impl.h
new file mode 100644 (file)
index 0000000..48715e3
--- /dev/null
@@ -0,0 +1,111 @@
+#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 adaptoried.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// External headers
+
+// Internal headers
+#include <dali-physics/internal/physics-adaptor-impl.h>
+
+namespace Dali::Toolkit::Physics::Internal
+{
+class ChipmunkPhysicsAdaptor : public PhysicsAdaptor
+{
+public:
+  ChipmunkPhysicsAdaptor();
+
+  /**
+   * A reference counted object may only be deleted by calling Unreference()
+   */
+  ~ChipmunkPhysicsAdaptor() override;
+
+  // Remove copy constructor and copy assignment
+  ChipmunkPhysicsAdaptor(const PhysicsAdaptor& handle) = delete;
+  ChipmunkPhysicsAdaptor& operator=(const PhysicsAdaptor& handle) = delete;
+
+  /**
+   * 2nd stage initialization
+   */
+  void OnInitialize(const Dali::Matrix& transform, Uint16Pair size) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::CreateDebugLayer
+   */
+  Dali::Layer CreateDebugLayer(Dali::Window window) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetTransformAndSize
+   */
+  void SetTransformAndSize(const Dali::Matrix& transform, Uint16Pair size) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateToPhysicsSpace
+   */
+  Dali::Vector3 TranslateToPhysicsSpace(Dali::Vector3 vector) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateToPhysicsSpace
+   */
+  Dali::Quaternion TranslateToPhysicsSpace(Dali::Quaternion rotation) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateFromPhysicsSpace
+   */
+  Dali::Vector3 TranslateFromPhysicsSpace(Dali::Vector3 vector) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateFromPhysicsSpace
+   */
+  Dali::Quaternion TranslateFromPhysicsSpace(Quaternion rotation) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ConvertVectorToPhysicsSpace
+   */
+  Dali::Vector3 ConvertVectorToPhysicsSpace(Dali::Vector3 vector) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ConvertVectorFromPhysicsSpace
+   */
+  Dali::Vector3 ConvertVectorFromPhysicsSpace(Dali::Vector3 vector) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::AddActorBody
+   */
+  PhysicsActorPtr AddActorBody(Dali::Actor actor, Dali::Any body) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::RemoveActorBody
+   */
+  void RemoveActorBody(PhysicsActor& physicsActor) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetPhysicsActor
+   */
+  PhysicsActorPtr GetPhysicsActor(Dali::Any body) const override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::BuildPickingRay
+   */
+  void BuildPickingRay(Dali::Vector3 origin, Dali::Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ProjectPoint
+   */
+  Dali::Vector3 ProjectPoint(Dali::Vector3 origin, Dali::Vector3 direction, float distance) override;
+};
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/chipmunk-impl/chipmunk-physics-world-impl.cpp b/dali-physics/internal/chipmunk-impl/chipmunk-physics-world-impl.cpp
new file mode 100644 (file)
index 0000000..6157780
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * 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 <dali-physics/internal/chipmunk-impl/chipmunk-physics-world-impl.h>
+
+// External Headers
+
+// Internal Headers
+#include <dali/dali.h>
+#include <dali/devel-api/common/stage-devel.h>
+#include <dali/devel-api/update/frame-callback-interface.h>
+
+namespace
+{
+#define GRABBABLE_MASK_BIT (1u << 31)
+cpShapeFilter GRAB_FILTER = {CP_NO_GROUP, GRABBABLE_MASK_BIT, GRABBABLE_MASK_BIT};
+
+inline cpVect ConvertVector(Dali::Vector3 vector)
+{
+  return cpv(vector.x, vector.y);
+}
+
+static void ShapeFreeWrap(cpSpace* space, cpShape* shape, void* unused)
+{
+  cpSpaceRemoveShape(space, shape);
+  cpShapeFree(shape);
+}
+
+static void PostShapeFree(cpShape* shape, cpSpace* space)
+{
+  cpSpaceAddPostStepCallback(space, (cpPostStepFunc)ShapeFreeWrap, shape, NULL);
+}
+
+static void ConstraintFreeWrap(cpSpace* space, cpConstraint* constraint, void* unused)
+{
+  cpSpaceRemoveConstraint(space, constraint);
+  cpConstraintFree(constraint);
+}
+
+static void PostConstraintFree(cpConstraint* constraint, cpSpace* space)
+{
+  cpSpaceAddPostStepCallback(space, (cpPostStepFunc)ConstraintFreeWrap, constraint, NULL);
+}
+
+static void BodyFreeWrap(cpSpace* space, cpBody* body, void* unused)
+{
+  cpSpaceRemoveBody(space, body);
+  cpBodyFree(body);
+}
+
+static void PostBodyFree(cpBody* body, cpSpace* space)
+{
+  cpSpaceAddPostStepCallback(space, (cpPostStepFunc)BodyFreeWrap, body, NULL);
+}
+} // namespace
+
+namespace Dali::Toolkit::Physics::Internal
+{
+std::unique_ptr<PhysicsWorld> ChipmunkPhysicsWorld::New(Dali::Actor rootActor, Dali::CallbackBase* updateCallback)
+{
+  std::unique_ptr<ChipmunkPhysicsWorld> world = std::make_unique<ChipmunkPhysicsWorld>(rootActor, updateCallback);
+  world->Initialize();
+  return world;
+}
+
+ChipmunkPhysicsWorld::ChipmunkPhysicsWorld(Dali::Actor rootActor, Dali::CallbackBase* updateCallback)
+: PhysicsWorld(rootActor, updateCallback)
+{
+}
+
+void ChipmunkPhysicsWorld::OnInitialize(/*void* dynamicsWorld*/)
+{
+  // @todo Should enable developer to optionally supply their own created cpSpace.
+  mSpace = cpSpaceNew();
+  cpSpaceSetIterations(mSpace, 30);
+  cpSpaceSetSleepTimeThreshold(mSpace, 0.5f);
+  cpSpaceSetGravity(mSpace, cpv(0, -200));
+}
+
+ChipmunkPhysicsWorld::~ChipmunkPhysicsWorld()
+{
+  Dali::Mutex::ScopedLock lock(mMutex);
+  if(mSpace)
+  {
+    cpSpaceEachShape(mSpace, (cpSpaceShapeIteratorFunc)PostShapeFree, mSpace);
+    cpSpaceEachConstraint(mSpace, (cpSpaceConstraintIteratorFunc)PostConstraintFree, mSpace);
+    cpSpaceEachBody(mSpace, (cpSpaceBodyIteratorFunc)PostBodyFree, mSpace);
+    cpSpaceFree(mSpace);
+    mSpace = nullptr;
+  }
+}
+
+Dali::Any ChipmunkPhysicsWorld::GetNative()
+{
+  return mSpace;
+}
+
+void ChipmunkPhysicsWorld::Integrate(float timestep)
+{
+  if(mPhysicsIntegrateState == Physics::PhysicsAdaptor::IntegrationState::ON)
+  {
+    cpSpaceStep(mSpace, timestep);
+  }
+}
+
+Dali::Any ChipmunkPhysicsWorld::HitTest(Dali::Vector3 rayFromWorld, Dali::Vector3 rayToWorld, Dali::Vector3& localPivot, float& distanceFromCamera)
+{
+  cpVect           spacePosition = cpv(rayFromWorld.x, rayFromWorld.y);
+  cpFloat          radius        = 5.0f;
+  cpPointQueryInfo info          = {0};
+  cpShape*         shape         = cpSpacePointQueryNearest(mSpace, spacePosition, radius, GRAB_FILTER, &info);
+  cpBody*          hitBody{nullptr};
+
+  if(shape && cpBodyGetMass(cpShapeGetBody(shape)) < INFINITY)
+  {
+    // Use the closest point on the surface if the click is outside the shape.
+    cpVect nearest = (info.distance > 0.0f ? info.point : spacePosition);
+    hitBody        = cpShapeGetBody(shape);
+    cpVect local   = cpBodyWorldToLocal(hitBody, nearest);
+    localPivot.x   = local.x;
+    localPivot.y   = local.y;
+    localPivot.z   = 0.0;
+  }
+
+  Dali::Any bodyPtr;
+  // Only set non-null ptr into bodyPtr, leave empty if null.
+  if(hitBody)
+  {
+    bodyPtr = hitBody;
+  }
+  return bodyPtr;
+}
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/chipmunk-impl/chipmunk-physics-world-impl.h b/dali-physics/internal/chipmunk-impl/chipmunk-physics-world-impl.h
new file mode 100644 (file)
index 0000000..fbb2e03
--- /dev/null
@@ -0,0 +1,46 @@
+#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 <dali-physics/internal/physics-world-impl.h>
+
+#include <chipmunk/chipmunk.h>
+#include <memory>
+
+namespace Dali::Toolkit::Physics::Internal
+{
+class ChipmunkPhysicsWorld : public PhysicsWorld
+{
+public:
+  static std::unique_ptr<PhysicsWorld> New(Dali::Actor rootActor, Dali::CallbackBase* updateCallback);
+
+  ChipmunkPhysicsWorld(Dali::Actor rootActor, Dali::CallbackBase* updateCallback);
+  ~ChipmunkPhysicsWorld() override;
+
+  void OnInitialize(/*void* dynamicsWorld*/) override;
+
+  Dali::Any GetNative() override;
+
+  void Integrate(float timestep) override;
+
+  Dali::Any HitTest(Dali::Vector3 rayFromWorld, Dali::Vector3 rayToWorld, Dali::Vector3& localPivot, float& distanceFromCamera) override;
+
+private:
+  cpSpace* mSpace{nullptr};
+};
+
+} //namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/file.list b/dali-physics/internal/file.list
new file mode 100644 (file)
index 0000000..87c7cb2
--- /dev/null
@@ -0,0 +1,20 @@
+set(physics_internal_dir ${physics_dir}/internal)
+set(physics2d_internal_dir ${physics_internal_dir}/chipmunk-impl)
+set(physics3d_internal_dir ${physics_internal_dir}/bullet-impl)
+
+set(physics2d_src_files ${physics_src_files}
+  ${physics2d_internal_dir}/chipmunk-physics-actor-impl.cpp
+  ${physics2d_internal_dir}/chipmunk-physics-adaptor-impl.cpp
+  ${physics2d_internal_dir}/chipmunk-physics-world-impl.cpp
+  ${physics_internal_dir}/physics-adaptor-impl.cpp
+  ${physics_internal_dir}/physics-world-impl.cpp
+)
+
+set(physics3d_src_files ${physics_src_files}
+  ${physics3d_internal_dir}/bullet-physics-actor-impl.cpp
+  ${physics3d_internal_dir}/bullet-physics-adaptor-impl.cpp
+  ${physics3d_internal_dir}/bullet-physics-debug-renderer.cpp
+  ${physics3d_internal_dir}/bullet-physics-world-impl.cpp
+  ${physics_internal_dir}/physics-adaptor-impl.cpp
+  ${physics_internal_dir}/physics-world-impl.cpp
+)
diff --git a/dali-physics/internal/physics-actor-impl.h b/dali-physics/internal/physics-actor-impl.h
new file mode 100644 (file)
index 0000000..6aa2624
--- /dev/null
@@ -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 <dali/public-api/actors/actor.h>
+#include <dali/public-api/object/any.h>
+#include <dali/public-api/object/base-object.h>
+
+// INTERNAL INCLUDES
+#include <dali-physics/public-api/physics-actor.h>
+#include <dali-toolkit/public-api/dali-toolkit-common.h>
+
+namespace Dali::Toolkit::Physics
+{
+namespace Internal
+{
+class PhysicsActor;
+class PhysicsAdaptor;
+
+using PhysicsActorPtr = Dali::IntrusivePtr<PhysicsActor>;
+
+class 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<Internal::PhysicsActor&>(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<const Internal::PhysicsActor&>(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 (file)
index 0000000..09b3408
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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 <dali-physics/internal/physics-adaptor-impl.h>
+
+// External Headers
+#include <memory>
+#include <utility>
+
+// Internal Headers
+#include <dali-physics/internal/physics-world-impl.h>
+#include <dali/dali.h>
+#include <dali/devel-api/threading/mutex.h>
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/actors/drawable-actor.h>
+
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_PHYSICS");
+#endif
+
+} // namespace
+
+namespace Dali::Toolkit::Physics::Internal
+{
+PhysicsAdaptor::PhysicsAdaptor()
+: mSlotDelegate(this)
+{
+}
+
+PhysicsAdaptor::~PhysicsAdaptor()
+{
+}
+
+void PhysicsAdaptor::Initialize(const Dali::Matrix& transform, Uint16Pair worldSize)
+{
+  // Create an actor that can handle mouse events.
+  // @todo Enable this to be fully configured / provided
+  mRootActor                                 = Layer::New();
+  mRootActor[Actor::Property::NAME]          = "PhysicsRootLayer";
+  mRootActor[Layer::Property::BEHAVIOR]      = Layer::LAYER_3D;
+  mRootActor[Layer::Property::DEPTH_TEST]    = true;
+  mRootActor[Actor::Property::SIZE]          = Vector2(worldSize.GetWidth(), worldSize.GetHeight());
+  mRootActor[Actor::Property::ANCHOR_POINT]  = Dali::AnchorPoint::CENTER;
+  mRootActor[Actor::Property::PARENT_ORIGIN] = Dali::ParentOrigin::CENTER;
+
+  // Initialize derived adaptor (and world)
+  OnInitialize(transform, worldSize);
+}
+
+void PhysicsAdaptor::SetTimestep(float timestep)
+{
+  mPhysicsWorld->SetTimestep(timestep);
+}
+
+float PhysicsAdaptor::GetTimestep() const
+{
+  return mPhysicsWorld->GetTimestep();
+}
+
+Physics::PhysicsAdaptor::ScopedPhysicsAccessorPtr PhysicsAdaptor::GetPhysicsAccessor()
+{
+  return std::unique_ptr<Physics::PhysicsAdaptor::ScopedPhysicsAccessor>(new Physics::PhysicsAdaptor::ScopedPhysicsAccessor(*mPhysicsWorld.get()));
+}
+
+void PhysicsAdaptor::SetIntegrationState(Physics::PhysicsAdaptor::IntegrationState state)
+{
+  mPhysicsWorld->SetIntegrationState(state);
+}
+
+Physics::PhysicsAdaptor::IntegrationState PhysicsAdaptor::GetIntegrationState() const
+{
+  return mPhysicsWorld->GetIntegrationState();
+}
+
+void PhysicsAdaptor::SetDebugState(Physics::PhysicsAdaptor::DebugState state)
+{
+  mPhysicsWorld->SetDebugState(state);
+}
+
+Physics::PhysicsAdaptor::DebugState PhysicsAdaptor::GetDebugState() const
+{
+  return mPhysicsWorld->GetDebugState();
+}
+
+Dali::Actor PhysicsAdaptor::GetRootActor() const
+{
+  return mRootActor;
+}
+
+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<void()> function)
+{
+  mPhysicsWorld->Queue(function);
+}
+
+void PhysicsAdaptor::CreateSyncPoint()
+{
+  mPhysicsWorld->CreateSyncPoint();
+}
+
+} // namespace Dali::Toolkit::Physics::Internal
diff --git a/dali-physics/internal/physics-adaptor-impl.h b/dali-physics/internal/physics-adaptor-impl.h
new file mode 100644 (file)
index 0000000..02e651a
--- /dev/null
@@ -0,0 +1,212 @@
+#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 <dali/public-api/object/base-object.h>
+#include <memory>
+#include <unordered_map>
+
+// INTERNAL INCLUDES
+#include <dali-physics/internal/physics-actor-impl.h>
+#include <dali-physics/internal/physics-world-impl.h>
+#include <dali-physics/public-api/physics-adaptor.h>
+
+namespace Dali::Toolkit::Physics
+{
+namespace Internal
+{
+class PhysicsAdaptor;
+class PhysicsDebugRenderer;
+
+using PhysicsAdaptorPtr = IntrusivePtr<PhysicsAdaptor>;
+
+// Declaration of factory function, implemented by derived class
+PhysicsAdaptorPtr CreateNewPhysicsAdaptor(const Dali::Matrix& transform, Uint16Pair worldSize);
+
+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;
+
+  /**
+   * 2nd stage initialization
+   */
+  void         Initialize(const Dali::Matrix& transform, Uint16Pair size);
+  virtual void OnInitialize(const Dali::Matrix& transform, Uint16Pair size) = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetTimestep
+   */
+  void SetTimestep(float timestep);
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetTimestep
+   */
+  float GetTimestep() const;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetPhysicsAccessor
+   */
+  Physics::PhysicsAdaptor::ScopedPhysicsAccessorPtr GetPhysicsAccessor();
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::CreateDebugLayer
+   */
+  virtual Dali::Layer CreateDebugLayer(Dali::Window window) = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateToPhysicsSpace
+   */
+  virtual Dali::Vector3 TranslateToPhysicsSpace(Dali::Vector3 vector) const = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateToPhysicsSpace
+   */
+  virtual Dali::Quaternion TranslateToPhysicsSpace(Dali::Quaternion rotation) const = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateFromPhysicsSpace
+   */
+  virtual Dali::Vector3 TranslateFromPhysicsSpace(Dali::Vector3 vector) const = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::TranslateFromPhysicsSpace
+   */
+  virtual Dali::Quaternion TranslateFromPhysicsSpace(Quaternion rotation) const = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ConvertVectorToPhysicsSpace
+   */
+  virtual Dali::Vector3 ConvertVectorToPhysicsSpace(Dali::Vector3 vector) const = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ConvertVectorFromPhysicsSpace
+   */
+  virtual Dali::Vector3 ConvertVectorFromPhysicsSpace(Dali::Vector3 vector) const = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetTransformAndSize
+   */
+  virtual void SetTransformAndSize(const Dali::Matrix& transform, Uint16Pair size) = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetIntegrationState
+   */
+  void SetIntegrationState(Physics::PhysicsAdaptor::IntegrationState state);
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetIntegrationState
+   */
+  Physics::PhysicsAdaptor::IntegrationState GetIntegrationState() const;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::SetDebugState
+   */
+  void SetDebugState(Physics::PhysicsAdaptor::DebugState state);
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetDebugState
+   */
+  Physics::PhysicsAdaptor::DebugState GetDebugState() const;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::AddActorBody
+   */
+  virtual PhysicsActorPtr AddActorBody(Dali::Actor actor, Dali::Any body) = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::RemoveActorBody
+   */
+  virtual void RemoveActorBody(PhysicsActor& physicsActor) = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetPhysicsActor
+   */
+  virtual PhysicsActorPtr GetPhysicsActor(Dali::Any body) const = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::GetRootActor
+   */
+  Dali::Actor GetRootActor() const;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::BuildPickingRay
+   */
+  virtual void BuildPickingRay(Dali::Vector3 origin, Dali::Vector3 direction, Dali::Vector3& rayFromWorld, Dali::Vector3& rayToWorld) = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::ProjectPoint
+   */
+  virtual Dali::Vector3 ProjectPoint(Dali::Vector3 origin, Dali::Vector3 direction, float distance) = 0;
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::Queue
+   */
+  void Queue(std::function<void(void)> function);
+
+  /**
+   * @copydoc Dali::Toolkit::Physics::PhysicsAdaptor::Queue
+   */
+  void CreateSyncPoint();
+
+  /**
+   * Handle the update of all of the known bound actors
+   */
+  void OnUpdateActors(Dali::UpdateProxy* updateProxy);
+
+protected:
+  std::unique_ptr<PhysicsWorld>                 mPhysicsWorld;
+  std::unordered_map<uint32_t, PhysicsActorPtr> mPhysicsActors;
+  Dali::Actor                                   mRootActor;
+
+  Dali::Matrix     mTransform;
+  Dali::Matrix     mInverseTransform;
+  Dali::Uint16Pair mSize;
+
+  Dali::SlotDelegate<PhysicsAdaptor> 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<Internal::PhysicsAdaptor&>(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<const Internal::PhysicsAdaptor&>(object);
+}
+
+} // namespace Dali::Toolkit::Physics
+
+#endif //DALI_TOOLKIT_PHYSICS_INTERNAL_ADAPTOR_H
diff --git a/dali-physics/internal/physics-world-impl.cpp b/dali-physics/internal/physics-world-impl.cpp
new file mode 100644 (file)
index 0000000..cd43633
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * 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 <dali-physics/internal/physics-world-impl.h>
+
+// External Headers
+
+// Internal Headers
+#include <dali/dali.h>
+#include <dali/devel-api/common/stage-devel.h>
+#include <dali/devel-api/update/frame-callback-interface.h>
+
+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;
+};
+
+PhysicsWorld::PhysicsWorld(Dali::Actor rootActor, Dali::CallbackBase* updateCallback)
+: mUpdateCallback(updateCallback),
+  mRootActor(rootActor)
+{
+}
+
+void PhysicsWorld::Initialize()
+{
+  // Call derived class's initializer
+  OnInitialize();
+
+  // 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<FrameCallback>(*this);
+  Dali::DevelStage::AddFrameCallback(Dali::Stage::GetCurrent(), *mFrameCallback, mRootActor);
+  Dali::Stage::GetCurrent().KeepRendering(30); // @todo Remove!
+}
+
+PhysicsWorld::~PhysicsWorld()
+{
+  // Derived class's destructor should clean down physics objects under mutex lock
+  // On completion, can remove the callback.
+
+  Dali::DevelStage::RemoveFrameCallback(Dali::Stage::GetCurrent(), *mFrameCallback);
+}
+
+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::SetTimestep(float timeStep)
+{
+  mPhysicsTimeStep = timeStep;
+}
+
+float PhysicsWorld::GetTimestep()
+{
+  return mPhysicsTimeStep;
+}
+
+void PhysicsWorld::Queue(std::function<void(void)> 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);
+}
+
+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 (file)
index 0000000..d6e1097
--- /dev/null
@@ -0,0 +1,163 @@
+#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 <dali/dali.h>
+#include <dali/devel-api/threading/mutex.h>
+#include <dali/devel-api/update/frame-callback-interface.h>
+#include <dali/devel-api/update/update-proxy.h>
+
+#include <dali-physics/public-api/physics-adaptor.h>
+
+#include <functional>
+#include <queue>
+
+namespace Dali::Toolkit::Physics::Internal
+{
+class PhysicsWorld;
+class FrameCallback;
+
+/**
+ * Abstract class that handles the update frame callback, queuing and calling
+ * functions before the integration step ; calling the integration step,
+ * and owning the mutex for the update callback.
+ *
+ * Implementing classes should also hold the physics world.
+ */
+class PhysicsWorld
+{
+public:
+  /**
+   * Create a new physics world.
+   */
+  static std::unique_ptr<PhysicsWorld> New(Dali::Actor rootActor, Dali::CallbackBase* updateCallback);
+
+  /**
+   * Constructor which takes the root actor and a callback from the PhysicsAdaptor
+   * @param[in] rootActor The root actor that physics actors will be added to
+   * @param[in] updateCallback A callback from the PhysicsAdaptor which updates the physics actors after the integration step
+   */
+  PhysicsWorld(Dali::Actor rootActor, Dali::CallbackBase* updateCallback);
+
+  /**
+   * Virtual destructor.
+   * Note, removes the frame callback.
+   */
+  virtual ~PhysicsWorld();
+
+  /**
+   * Initialize derived classes and creates the frame callback
+   */
+  void Initialize();
+
+  /**
+   * Initialize the derived class
+   */
+  virtual void OnInitialize() = 0;
+
+  /**
+   * Get the native physics world / space.
+   * @return A pointer to the physics world / space
+   */
+  virtual Dali::Any GetNative() = 0;
+
+  /**
+   * 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<void(void)> 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.
+   */
+  virtual Dali::Any HitTest(Dali::Vector3 rayFromWorld, Dali::Vector3 rayToWorld, Dali::Vector3& localPivot, float& distanceFromCamera) = 0;
+
+  /**
+   * @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);
+
+protected:
+  virtual void Integrate(float timestep) = 0;
+
+protected:
+  Dali::Mutex                           mMutex;
+  std::queue<std::function<void(void)>> commandQueue;
+  Dali::UpdateProxy::NotifySyncPoint    mNotifySyncPoint{Dali::UpdateProxy::INVALID_SYNC};
+  Dali::CallbackBase*                   mUpdateCallback{nullptr};
+  std::unique_ptr<FrameCallback>        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 (file)
index 0000000..6cb7384
--- /dev/null
@@ -0,0 +1,12 @@
+set(physics_public_api_dir "${physics_dir}/public-api")
+
+set(physics_src_files ${physics_src_files}
+       ${physics_public_api_dir}/physics-actor.cpp
+       ${physics_public_api_dir}/physics-adaptor.cpp
+       ${physics_public_api_dir}/scoped-physics-accessor.cpp
+)
+
+set(physics_public_api_header_files
+       ${physics_public_api_dir}/physics-actor.h
+       ${physics_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 (file)
index 0000000..5058520
--- /dev/null
@@ -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 <dali-physics/public-api/physics-actor.h>
+
+// Internal headers
+#include <dali-physics/internal/physics-actor-impl.h>
+#include <dali-physics/internal/physics-adaptor-impl.h>
+
+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<Dali::Toolkit::Physics::Internal::PhysicsActor*>(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 (file)
index 0000000..616c7b2
--- /dev/null
@@ -0,0 +1,227 @@
+#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 <dali/public-api/actors/actor.h>
+#include <dali/public-api/object/any.h>
+#include <dali/public-api/object/base-handle.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/dali-toolkit-common.h>
+
+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<btRigidBody*>();
+ *   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:
+  /**
+   * @brief Constructor
+   *
+   * @SINCE_2_2.43
+   */
+  PhysicsActor();
+
+  /**
+   * @brief Destructor.
+   *
+   * @SINCE_2_2.43
+   * This is non-virtual since derived Handle types must not contain data or virtual methods
+   */
+  ~PhysicsActor();
+
+  /**
+   * @brief Copy Constructor.
+   *
+   * @SINCE_2_2.43
+   * @param[in] handle The handle to copy
+   * @note This creates a new handle, but does not create a new implementation object.
+   */
+  PhysicsActor(const PhysicsActor& handle);
+
+  /**
+   * @brief Move Constructor.
+   *
+   * @SINCE_2_2.43
+   * @param[in] handle A reference to the handle to move
+   */
+  PhysicsActor(PhysicsActor&& rhs) noexcept;
+
+  /**
+   * @brief Assignment operator.
+   *
+   * @SINCE_2_2.43
+   * @param[in] handle A reference to the handle to move
+   * @return a reference to this handle
+   */
+  PhysicsActor& operator=(const PhysicsActor& handle);
+
+  /**
+   * @brief Move Assignment operator.
+   *
+   * @SINCE_2_2.43
+   * @param[in] handle A reference to the handle to move
+   * @return a reference to this handle
+   */
+  PhysicsActor& operator=(PhysicsActor&& handle) noexcept;
+
+  /**
+   * New method.
+   * @SINCE_2_2.43
+   *
+   * 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.43
+   * @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.
+   *
+   * @SINCE_2_2.43
+   */
+  uint32_t GetId() const;
+
+  /**
+   * @brief Get the actual physics body of this object.
+   *
+   * @SINCE_2_2.43
+   * 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<btRigidBody*>();
+   *
+   * @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;
+
+  /**
+   * @brief Queue a method to set the position on the associated physics body
+   * in the update thread before the next integration.
+   *
+   * @SINCE_2_2.43
+   * @param[in] actorPosition The position of the actor in DALi space
+   */
+  void AsyncSetPhysicsPosition(Dali::Vector3 actorPosition);
+
+  /**
+   * @brief Queue a method to set the rotation of the associated physics body
+   * in the update thread before the next integration.
+   *
+   * @SINCE_2_2.43
+   * @param[in] actorRotation The orientation of the actor in DALi space
+   */
+  void AsyncSetPhysicsRotation(Dali::Quaternion actorRotation);
+
+  /**
+   * @brief Get the current position of the physics body in Physics space.
+   *
+   * @SINCE_2_2.43
+   * @return the current position of the physics body in Physics space.
+   */
+  Dali::Vector3 GetPhysicsPosition() const;
+
+  /**
+   * @brief Get the current rotation of the physics body in Physics space.
+   *
+   * @SINCE_2_2.43
+   * @return the current rotation of the physics body in Physics space.
+   */
+  Dali::Quaternion GetPhysicsRotation() const;
+
+  /**
+   * @brief Get the current position of the physics body in DALi space.
+   *
+   * @SINCE_2_2.43
+   * @return the current position of the physics body in DALi space.
+   */
+  Dali::Vector3 GetActorPosition() const;
+
+  /**
+   * @brief Get the current rotation of the physics body in DALi space.
+   *
+   * @SINCE_2_2.43
+   * @return the current rotation of the physics body in DALi space.
+   */
+  Dali::Quaternion GetActorRotation() const;
+
+public: // Not intended for developer use
+  /// @cond internal
+  /**
+   * @brief This constructor is used by PhysicsActor::New() methods.
+   *
+   * @SINCE_2_2.43
+   * @param[in] impl A pointer to a newly allocated Dali resource.
+   * @note Not intended 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 (file)
index 0000000..5cc274b
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * 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 <dali-physics/public-api/physics-adaptor.h>
+
+// Internal headers
+#include <dali-physics/internal/physics-actor-impl.h>
+#include <dali-physics/internal/physics-adaptor-impl.h>
+#include <dali-physics/public-api/physics-actor.h>
+
+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::CreateNewPhysicsAdaptor(transform, size);
+  return PhysicsAdaptor(internal.Get());
+}
+
+PhysicsAdaptor PhysicsAdaptor::DownCast(BaseHandle handle)
+{
+  return PhysicsAdaptor(dynamic_cast<Dali::Toolkit::Physics::Internal::PhysicsAdaptor*>(handle.GetObjectPtr()));
+}
+
+void PhysicsAdaptor::SetTimestep(float timestep)
+{
+  GetImplementation(*this).SetTimestep(timestep);
+}
+
+float PhysicsAdaptor::GetTimestep() const
+{
+  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) const
+{
+  return GetImplementation(*this).TranslateToPhysicsSpace(vector);
+}
+
+Dali::Quaternion PhysicsAdaptor::TranslateToPhysicsSpace(Dali::Quaternion rotation) const
+{
+  return GetImplementation(*this).TranslateToPhysicsSpace(rotation);
+}
+
+Dali::Vector3 PhysicsAdaptor::TranslateFromPhysicsSpace(Dali::Vector3 vector) const
+{
+  return GetImplementation(*this).TranslateFromPhysicsSpace(vector);
+}
+
+Dali::Vector3 PhysicsAdaptor::ConvertVectorToPhysicsSpace(Dali::Vector3 vector) const
+{
+  return GetImplementation(*this).ConvertVectorToPhysicsSpace(vector);
+}
+
+Dali::Vector3 PhysicsAdaptor::ConvertVectorFromPhysicsSpace(Dali::Vector3 vector) const
+{
+  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() const
+{
+  return GetImplementation(*this).GetIntegrationState();
+}
+
+void PhysicsAdaptor::SetDebugState(Physics::PhysicsAdaptor::DebugState state)
+{
+  GetImplementation(*this).SetDebugState(state);
+}
+
+Physics::PhysicsAdaptor::DebugState PhysicsAdaptor::GetDebugState() const
+{
+  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());
+}
+
+void PhysicsAdaptor::RemoveActorBody(PhysicsActor physicsActor)
+{
+  GetImplementation(*this).RemoveActorBody(GetImplementation(physicsActor));
+}
+
+PhysicsActor PhysicsAdaptor::GetPhysicsActor(Dali::Any body) const
+{
+  Internal::PhysicsActorPtr physicsActor = GetImplementation(*this).GetPhysicsActor(body);
+  return PhysicsActor(physicsActor.Get());
+}
+
+Dali::Actor PhysicsAdaptor::GetRootActor() const
+{
+  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<void(void)> 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 (file)
index 0000000..41c7da7
--- /dev/null
@@ -0,0 +1,495 @@
+#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 <dali/public-api/actors/actor.h>
+#include <dali/public-api/adaptor-framework/window.h>
+#include <dali/public-api/object/any.h>
+#include <dali/public-api/object/base-handle.h>
+#include <functional> ///< for std::function
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/dali-toolkit-common.h>
+
+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:
+  /**
+   * @brief Enumeration to turn the integration step on or off.
+   */
+  enum class IntegrationState
+  {
+    OFF,
+    ON
+  };
+
+  /**
+   * @brief Enumeration to turn the debug rendering on or off
+   */
+  enum class DebugState
+  {
+    OFF,
+    ON
+  };
+
+  /**
+   * @brief Scoped accessor to the physics world.
+   *
+   * @SINCE_2_2.43
+   * 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:
+    /**
+     * @brief Get a pointer to the native world.
+     *
+     * @SINCE_2_2.43
+     * 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<btDiscreteDynamiscWorld*>();
+     */
+    Dali::Any GetNative();
+
+    /**
+     * @brief Hit test the physics world and return the nearest body.
+     *
+     * @SINCE_2_2.43
+     * @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;
+
+    /**
+     * @brief Destructor.
+     *
+     * On leaving scope, the mutex is unlocked and the physics integration step
+     * can resume.
+     */
+    ~ScopedPhysicsAccessor();
+
+  private:
+    /**
+     * @brief Private constructor.
+     *
+     * It is created by the physics adaptor.
+     */
+    ScopedPhysicsAccessor(Internal::PhysicsWorld& world);
+    friend Internal::PhysicsAdaptor;
+
+    struct Impl; ///< Opaque implementation structure
+    Impl* mImpl;
+  };
+
+  /**
+   * @brief Creates an uninitalized PhysicsAdaptor, this can be initialized with PhysicsAdaptor::New().
+   *
+   * @SINCE_2_2.43
+   * Calling member functions with an uninitialized PhysicsAdaptor handle is not allowed.
+   */
+  PhysicsAdaptor();
+
+  /**
+   * @brief Destructor
+   *
+   * This is non-virtual since derived Handle types must not contain data or virtual methods.
+   * @SINCE_2_2.43
+   */
+  ~PhysicsAdaptor();
+
+  /**
+   * @brief Copy Constructor.
+   *
+   * @SINCE_2_2.43
+   * @param[in] handle The handle to copy
+   * @note This creates a new handle, but does not create a new implementation object.
+   */
+  PhysicsAdaptor(const PhysicsAdaptor& handle);
+
+  /**
+   * @brief Move Constructor.
+   *
+   * @SINCE_2_2.43
+   * @param[in] handle A reference to the handle to move
+   */
+  PhysicsAdaptor(PhysicsAdaptor&& rhs) noexcept;
+
+  /**
+   * @brief Assignment operator.
+   *
+   * @SINCE_2_2.43
+   * @param[in] handle A reference to the handle to move
+   * @return a reference to this handle
+   */
+  PhysicsAdaptor& operator=(const PhysicsAdaptor& handle);
+
+  /**
+   * @brief Move Assignment operator.
+   *
+   * @SINCE_2_2.43
+   * @param[in] handle A reference to the handle to move
+   * @return a reference to this handle
+   */
+  PhysicsAdaptor& operator=(PhysicsAdaptor&& handle) noexcept;
+
+  /**
+   * @brief Initialize the physics system.
+   *
+   * @SINCE_2_2.43
+   * @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.43
+   * @param[in] handle to an object
+   * @return handle to a PhysicsAdaptor object or an uninitialized handle
+   */
+  static PhysicsAdaptor DownCast(BaseHandle handle);
+
+  /**
+   * @brief Set how long the integration should take.
+   *
+   * @SINCE_2_2.43
+   * @param[in] timestep The length of time that the physics integration should take.
+   */
+  void SetTimestep(float timestep);
+
+  /**
+   * @brief Get the current physics integration timestep.
+   *
+   * @SINCE_2_2.43
+   * @return the current physics integration timestep
+   */
+  float GetTimestep() const;
+
+  /**
+   * @brief Type to represent a pointer to a scoped accessor.
+   *
+   * @SINCE_2_2.43
+   */
+  using ScopedPhysicsAccessorPtr = std::unique_ptr<PhysicsAdaptor::ScopedPhysicsAccessor>;
+
+  /**
+   * @brief Returns an accessor to the physics world.
+   *
+   * @SINCE_2_2.43
+   * 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.
+   * @return the scoped accessor
+   */
+  ScopedPhysicsAccessorPtr GetPhysicsAccessor();
+
+  /**
+   * @brief Create a layer & debug renderer.
+   *
+   * @SINCE_2_2.43
+   * The debug renderer may utilize the debug features of the native physics
+   * engine.
+   *
+   * @param[in] window The window to draw in (requires camera)
+   * @return The debug layer
+   */
+  Dali::Layer CreateDebugLayer(Dali::Window window);
+
+  /**
+   * @brief Converts a point in RootActor local coords (e.g. gesture)
+   * into physics space coords.
+   *
+   * @SINCE_2_2.43
+   * @param vector The point to convert
+   * @return The converted point
+   */
+  Dali::Vector3 TranslateToPhysicsSpace(Dali::Vector3 vector) const;
+
+  /**
+   * @brief Convert a rotation in DALi coordinate system into physics space.
+   *
+   * @SINCE_2_2.43
+   * @param[in] rotation The rotation to convert
+   * @return the converted rotation.
+   */
+  Dali::Quaternion TranslateToPhysicsSpace(Dali::Quaternion rotation) const;
+
+  /**
+   * @brief Converts a point in physics space coords into RootActor local coords.
+   *
+   * @SINCE_2_2.43
+   * @param vector The point to convert
+   * @return The converted point
+   */
+  Dali::Vector3 TranslateFromPhysicsSpace(Dali::Vector3 vector) const;
+
+  /**
+   * @brief Convert a rotation in physics coordinate system into DALi space.
+   *
+   * @SINCE_2_2.43
+   * @param[in] rotation The rotation to convert
+   * @return the converted rotation.
+   */
+  Dali::Quaternion TranslateFromPhysicsSpace(Dali::Quaternion rotation) const;
+
+  /**
+   * @brief Converts a vector (not a point) in DALi space into physics space.
+   *
+   * @SINCE_2_2.43
+   * @param vector The vector to convert
+   * @return The converted vector
+   */
+  Dali::Vector3 ConvertVectorToPhysicsSpace(Dali::Vector3 vector) const;
+
+  /**
+   * @brief Converts a vector (not a point) in physics space to DALi space.
+   *
+   * @SINCE_2_2.43
+   * @param vector The vector to convert
+   * @return The converted vector
+   */
+  Dali::Vector3 ConvertVectorFromPhysicsSpace(Dali::Vector3 vector) const;
+
+  /**
+   * @brief Set up the transform from world space to physics space.
+   *
+   * @SINCE_2_2.43
+   * @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);
+
+  /**
+   * @brief Set the integration state.
+   *
+   * @SINCE_2_2.43
+   * If it's turned off, physics will be paused.
+   * @note This is ON by default
+   * @param[in] state the new integration state
+   */
+  void SetIntegrationState(IntegrationState state);
+
+  /**
+   * @brief Get the integration state.
+   *
+   * @SINCE_2_2.43
+   * @return the new integration state
+   */
+  IntegrationState GetIntegrationState() const;
+
+  /**
+   * @brief Set the debug state.
+   *
+   * @SINCE_2_2.43
+   * 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);
+
+  /**
+   * @brief Get the debug state.
+   *
+   * @SINCE_2_2.43
+   * @return the new debug state
+   */
+  DebugState GetDebugState() const;
+
+  /**
+   * @brief Add an actor / body pair.
+   * @pre It's expected that the client has added the body to the physics world.
+   *
+   * The adaptor does not "take ownership" of the actor or the physics body.
+   * @SINCE_2_2.43
+   * @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);
+
+  /**
+   * @brief Remove the actor / body.
+   *
+   * This will unparent the actor from the root actor and disassociate it from
+   * the body.
+   *
+   * It is the responsibility of the client to remove the body from the physics world
+   * and destroy it at an appropriate time. Create a scoped accessor to ensure
+   * that the integration step isn't being run when doing so.
+   *
+   * If the root actor is holding the last reference to the actor, it will be
+   * destroyed automatically, otherwise it is the responsibility of the client to
+   * dereference the actor.
+   *
+   * @SINCE_2_2.43
+   * @param[in] physicsActor The actor / body pair to remove.
+   */
+  void RemoveActorBody(PhysicsActor physicsActor);
+
+  /**
+   * @brief Get the physics actor associated with the given body.
+   *
+   * @SINCE_2_2.43
+   * @param[in] body The physics body
+   * @return the associated physics actor
+   */
+  PhysicsActor GetPhysicsActor(Dali::Any body) const;
+
+  /**
+   * @brief Get the root actor (which holds all the actor/body pairs).
+   *
+   * @SINCE_2_2.43
+   * @return the root actor.
+   */
+  Dali::Actor GetRootActor() const;
+
+  /**
+   * @brief Convert DALi touch point into a picking ray in the physics world.
+   *
+   * These can then be used to hit test the PhysicsWorld
+   *
+   * @SINCE_2_2.43
+   * @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);
+
+  /**
+   * @brief 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.
+   *
+   * @SINCE_2_2.43
+   * @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);
+
+  /**
+   * @brief Queue a function to be executed before the physics integration in the update thread.
+   *
+   * @SINCE_2_2.43
+   * Multiple functions can be queued up. They are executed in the update frame
+   * callback when the next sync point is seen, so CreateSyncPoint() should be called
+   * afterwards for this to be executed.
+   *
+   * @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<void(void)> function);
+
+  /**
+   * @brief Create a sync point for queued functions.
+   *
+   * @SINCE_2_2.43
+   * Ensures that any previously queued functions are processed
+   * in the Update::FrameCallback during the same frame as other
+   * DALi properties set during this event handler invocation.
+   * For example,
+   *   boxActor = Actor::New();
+   *   //... Create box actor renderer ...
+   *   btRigidBodyConstructionInfo info;
+   *   //... set construction properties
+   *   boxBody = new btRigidBody(info);
+   *   auto boxPhysicsActor = physicsAdaptor.AddActorBody(boxActor, boxBody);
+   *   boxActor.SetProperty(Actor::Property::VISIBLE, true);
+   *   boxActor.SetProperty(Actor::Property::OPACITY, 0.5f);
+   *   physicsAdaptor.Queue([boxBody](){ boxBody->activate(true);});
+   *   btVector3 impulse(4, 5, 6);
+   *   btVector3 position();
+   *   physicsAdaptor.Queue([boxBody](){ boxBody->applyImpulse(impulse, position);});
+   *   physicsAdaptor.CreateSyncPoint();
+   *
+   * Ensures that the box has both render properties and physics properties applied
+   * during the same frame.
+   */
+  void CreateSyncPoint();
+
+public: // Not intended for developer use
+  /// @cond internal
+  /**
+   * @brief This constructor is used by PhysicsAdaptor::New() methods.
+   *
+   * @SINCE_2_2.43
+   * @param[in] impl A pointer to a newly allocated Dali resource.
+   * @note Not intended 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-physics/public-api/scoped-physics-accessor.cpp b/dali-physics/public-api/scoped-physics-accessor.cpp
new file mode 100644 (file)
index 0000000..1f4b6e8
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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 <dali-physics/public-api/physics-adaptor.h>
+
+#include <dali-physics/internal/physics-adaptor-impl.h>
+#include <dali-physics/internal/physics-world-impl.h>
+
+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 Dali::Toolkit::Physics
index a4dbeda..668a10c 100644 (file)
@@ -667,6 +667,12 @@ void DliLoaderImpl::Impl::ParseShaders(const TreeNode* shaders, Dali::Scene3D::L
     auto&            node = (*i0).second;
     ShaderDefinition shaderDef;
     ReadStringVector(node.GetChild("defines"), shaderDef.mDefines);
+    auto sssIter = std::find_if(shaderDef.mDefines.begin(), shaderDef.mDefines.end(), [](std::string& item)
+                                { return (item == "SSS"); });
+    if(sssIter != shaderDef.mDefines.end())
+    {
+      shaderDef.mDefines.erase(sssIter);
+    }
 
     // Read shader hints. Possible values are:
     //                         Don't define for No hints.
@@ -1027,14 +1033,17 @@ void DliLoaderImpl::Impl::ParseMaterials(const TreeNode* materials, DliInputPara
       materialDef.mFlags |= semantic;
     }
 
-    if(ReadString(node.GetChild("subsurfaceMap"), texturePath))
-    {
-      ToUnixFileSeparators(texturePath);
-
-      const auto semantic = MaterialDefinition::SUBSURFACE;
-      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
-      materialDef.mFlags |= semantic;
-    }
+/// @TODO : Some dli shader don't implement this subsurfaceMp usage.
+///         To make visual test pass, Skip subsurfaceMap texture using
+///         until dli shaders are support it.
+//    if(ReadString(node.GetChild("subsurfaceMap"), texturePath))
+//    {
+//      ToUnixFileSeparators(texturePath);
+//
+//      const auto semantic = MaterialDefinition::SUBSURFACE;
+//      materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
+//      materialDef.mFlags |= semantic;
+//    }
 
     if(ReadString(node.GetChild("occlusionMap"), texturePath))
     {
index 15d91b6..1bda11a 100644 (file)
@@ -949,6 +949,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)
@@ -1473,4 +1475,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
index 05bcef1..cf7e498 100644 (file)
@@ -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
index 9fde071..b0b88b3 100644 (file)
@@ -25,6 +25,7 @@
 #include <dali/public-api/math/degree.h>
 #include <dali/public-api/math/matrix.h>
 #include <dali/public-api/math/vector3.h>
+#include <string>
 
 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;
index a175eb3..b17e79d 100644 (file)
@@ -20,8 +20,8 @@
 
 // EXTERNAL INCLUDES
 #include <dali-toolkit/devel-api/toolkit-action-index-ranges.h>
-#include <dali-toolkit/devel-api/visuals/image-visual-actions-devel.h>
 #include <dali-toolkit/devel-api/visuals/animated-image-visual-actions-devel.h>
+#include <dali-toolkit/devel-api/visuals/image-visual-actions-devel.h>
 #include <dali/public-api/signals/callback.h>
 #include <string>
 
@@ -48,7 +48,9 @@ enum Type
   JUMP_TO = DevelAnimatedImageVisual::Action::JUMP_TO, ///< Jump to the specified frame. Property::INTEGER value should be passed.
 
   // AnimatedVectorImageVisual only actions
-  SET_DYNAMIC_PROPERTY = DevelAnimatedImageVisual::Action::ANIMATED_IMAGE_VISUAL_ACTION_END_INDEX ///< Set the dynamic property.
+  SET_DYNAMIC_PROPERTY = DevelAnimatedImageVisual::Action::ANIMATED_IMAGE_VISUAL_ACTION_END_INDEX, ///< Set the dynamic property.
+
+  FLUSH, ///< Flush animation data. It will make ensure that changeness of animated vector image properties flushed.
 };
 
 } // namespace Action
index 8a4ada9..8da0313 100644 (file)
@@ -557,6 +557,14 @@ void AnimatedVectorImageVisual::OnDoAction(const Property::Index actionId, const
       }
       break;
     }
+    case DevelAnimatedVectorImageVisual::Action::FLUSH:
+    {
+      if(DALI_LIKELY(!mCoreShutdown))
+      {
+        SendAnimationData();
+      }
+      break;
+    }
   }
 
   TriggerVectorRasterization();
index c19a440..6bb0d13 100644 (file)
@@ -215,7 +215,7 @@ void VectorAnimationTask::SetAnimationData(const AnimationData& data)
 
   uint32_t index = mAnimationDataIndex == 0 ? 1 : 0; // Use the other buffer
 
-  mAnimationData[index] = data;
+  mAnimationData[index].push_back(data);
   mAnimationDataUpdated = true;
 
   if(data.resendFlag & VectorAnimationTask::RESEND_SIZE)
@@ -660,7 +660,7 @@ void VectorAnimationTask::ApplyAnimationData()
   {
     ConditionalWait::ScopedLock lock(mConditionalWait);
 
-    if(!mAnimationDataUpdated || mAnimationData[mAnimationDataIndex].resendFlag != 0)
+    if(!mAnimationDataUpdated || mAnimationData[mAnimationDataIndex].size() != 0)
     {
       // Data is not updated or the previous data is not applied yet.
       return;
@@ -672,63 +672,65 @@ void VectorAnimationTask::ApplyAnimationData()
     index = mAnimationDataIndex;
   }
 
-  if(mAnimationData[index].resendFlag & VectorAnimationTask::RESEND_LOOP_COUNT)
+  for(const auto& animationData : mAnimationData[index])
   {
-    SetLoopCount(mAnimationData[index].loopCount);
-  }
-
-  if(mAnimationData[index].resendFlag & VectorAnimationTask::RESEND_PLAY_RANGE)
-  {
-    SetPlayRange(mAnimationData[index].playRange);
-  }
-
-  if(mAnimationData[index].resendFlag & VectorAnimationTask::RESEND_STOP_BEHAVIOR)
-  {
-    SetStopBehavior(mAnimationData[index].stopBehavior);
-  }
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_LOOP_COUNT)
+    {
+      SetLoopCount(animationData.loopCount);
+    }
 
-  if(mAnimationData[index].resendFlag & VectorAnimationTask::RESEND_LOOPING_MODE)
-  {
-    SetLoopingMode(mAnimationData[index].loopingMode);
-  }
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_PLAY_RANGE)
+    {
+      SetPlayRange(animationData.playRange);
+    }
 
-  if(mAnimationData[index].resendFlag & VectorAnimationTask::RESEND_CURRENT_FRAME)
-  {
-    SetCurrentFrameNumber(mAnimationData[index].currentFrame);
-  }
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_STOP_BEHAVIOR)
+    {
+      SetStopBehavior(animationData.stopBehavior);
+    }
 
-  if(mAnimationData[index].resendFlag & VectorAnimationTask::RESEND_NEED_RESOURCE_READY)
-  {
-    mVectorRenderer.InvalidateBuffer();
-  }
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_LOOPING_MODE)
+    {
+      SetLoopingMode(animationData.loopingMode);
+    }
 
-  if(mAnimationData[index].resendFlag & VectorAnimationTask::RESEND_DYNAMIC_PROPERTY)
-  {
-    for(auto&& iter : mAnimationData[index].dynamicProperties)
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_CURRENT_FRAME)
     {
-      mVectorRenderer.AddPropertyValueCallback(iter.keyPath, static_cast<VectorAnimationRenderer::VectorProperty>(iter.property), iter.callback, iter.id);
+      SetCurrentFrameNumber(animationData.currentFrame);
     }
-  }
 
-  if(mAnimationData[index].resendFlag & VectorAnimationTask::RESEND_PLAY_STATE)
-  {
-    if(mAnimationData[index].playState == DevelImageVisual::PlayState::PLAYING)
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_NEED_RESOURCE_READY)
     {
-      PlayAnimation();
+      mVectorRenderer.InvalidateBuffer();
     }
-    else if(mAnimationData[index].playState == DevelImageVisual::PlayState::PAUSED)
+
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_DYNAMIC_PROPERTY)
     {
-      PauseAnimation();
+      for(auto&& iter : animationData.dynamicProperties)
+      {
+        mVectorRenderer.AddPropertyValueCallback(iter.keyPath, static_cast<VectorAnimationRenderer::VectorProperty>(iter.property), iter.callback, iter.id);
+      }
     }
-    else if(mAnimationData[index].playState == DevelImageVisual::PlayState::STOPPED)
+
+    if(animationData.resendFlag & VectorAnimationTask::RESEND_PLAY_STATE)
     {
-      StopAnimation();
+      if(animationData.playState == DevelImageVisual::PlayState::PLAYING)
+      {
+        PlayAnimation();
+      }
+      else if(animationData.playState == DevelImageVisual::PlayState::PAUSED)
+      {
+        PauseAnimation();
+      }
+      else if(animationData.playState == DevelImageVisual::PlayState::STOPPED)
+      {
+        StopAnimation();
+      }
     }
   }
 
-  // reset data
-  mAnimationData[index].dynamicProperties.clear();
-  mAnimationData[index].resendFlag = 0;
+  // reset data list
+  mAnimationData[index].clear();
 }
 
 void VectorAnimationTask::OnUploadCompleted()
index e1b80c3..fdf1e2c 100644 (file)
@@ -357,7 +357,7 @@ private:
 
   std::string                          mUrl;
   VectorAnimationRenderer              mVectorRenderer;
-  AnimationData                        mAnimationData[2];
+  std::vector<AnimationData>           mAnimationData[2];
   VectorAnimationThread&               mVectorAnimationThread;
   ConditionalWait                      mConditionalWait;
   ResourceReadySignalType              mResourceReadySignal;
index dc0a055..62c706e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 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.
@@ -39,8 +39,9 @@ NPatchData::NPatchData()
   mCroppedHeight(0),
   mBorder(0, 0, 0, 0),
   mLoadingState(LoadingState::NOT_STARTED),
+  mRenderingMap{nullptr},
   mPreMultiplyOnLoad(false),
-  mRenderingMap{nullptr}
+  mObserverNotifying(false)
 {
 }
 
@@ -51,6 +52,8 @@ NPatchData::~NPatchData()
   {
     RenderingAddOn::Get().DestroyNPatch(mRenderingMap);
   }
+  mObserverList.Clear();
+  mQueuedObservers.Clear();
 }
 
 void NPatchData::SetId(const NPatchDataId id)
@@ -65,7 +68,16 @@ NPatchData::NPatchDataId NPatchData::GetId() const
 
 void NPatchData::AddObserver(TextureUploadObserver* textureObserver)
 {
-  mObserverList.PushBack(textureObserver);
+  if(mObserverNotifying)
+  {
+    // Do not add it into observer list during observer notifying.
+    mQueuedObservers.PushBack(textureObserver);
+  }
+  else
+  {
+    mObserverList.PushBack(textureObserver);
+  }
+  textureObserver->DestructionSignal().Connect(this, &NPatchData::ObserverDestroyed);
 }
 
 void NPatchData::RemoveObserver(TextureUploadObserver* textureObserver)
@@ -74,6 +86,7 @@ void NPatchData::RemoveObserver(TextureUploadObserver* textureObserver)
   {
     if(textureObserver == mObserverList[index])
     {
+      textureObserver->DestructionSignal().Disconnect(this, &NPatchData::ObserverDestroyed);
       mObserverList.Erase(mObserverList.begin() + index);
       break;
     }
@@ -259,13 +272,57 @@ void NPatchData::LoadComplete(bool loadSuccess, TextureInformation textureInform
     }
   }
 
-  for(uint32_t index = 0; index < mObserverList.Count(); ++index)
+  mObserverNotifying = true;
+
+  // Reverse observer list that we can pop_back the observer.
+  std::reverse(mObserverList.Begin(), mObserverList.End());
+
+  while(mObserverList.Count() > 0u)
   {
-    TextureUploadObserver* observer = mObserverList[index];
+    TextureUploadObserver* observer = *(mObserverList.End() - 1u);
+    mObserverList.Erase(mObserverList.End() - 1u);
+
+    observer->DestructionSignal().Disconnect(this, &NPatchData::ObserverDestroyed);
+
     NotifyObserver(observer, loadSuccess);
   }
 
-  mObserverList.Clear();
+  mObserverNotifying = false;
+
+  // Swap observer list what we queued during notify observer.
+  // If mQueuedObserver is not empty, it mean mLoadingState was LOAD_FAILED, and we try to re-load for this data.
+  // (If mLoadingState was LOAD_COMPLETE, NotifyObserver will be called directly. @todo : Should we fix this logic, matched with texture manager?)
+  // So LoadComplete will be called.
+  mObserverList.Swap(mQueuedObservers);
+}
+
+void NPatchData::ObserverDestroyed(TextureUploadObserver* observer)
+{
+  for(auto iter = mObserverList.Begin(); iter != mObserverList.End();)
+  {
+    if(observer == (*iter))
+    {
+      iter = mObserverList.Erase(iter);
+    }
+    else
+    {
+      ++iter;
+    }
+  }
+  if(mObserverNotifying)
+  {
+    for(auto iter = mQueuedObservers.Begin(); iter != mQueuedObservers.End();)
+    {
+      if(observer == (*iter))
+      {
+        iter = mQueuedObservers.Erase(iter);
+      }
+      else
+      {
+        ++iter;
+      }
+    }
+  }
 }
 
 } // namespace Internal
index cc520d8..3df4b29 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_NPATCH_DATA_H
 
 /*
- * Copyright (c) 2022 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.
@@ -33,11 +33,11 @@ namespace Toolkit
 {
 namespace Internal
 {
-class NPatchData : public Dali::Toolkit::TextureUploadObserver
+class NPatchData : public ConnectionTracker, public Dali::Toolkit::TextureUploadObserver
 {
 public:
-  typedef int32_t  NPatchDataId;                ///< The NPatchDataId type. This is used as a handle to refer to a particular Npatch Data.
-  static const int INVALID_NPATCH_DATA_ID = -1; ///< Used to represent a null TextureId or error
+  typedef int32_t           NPatchDataId;                ///< The NPatchDataId type. This is used as a handle to refer to a particular Npatch Data.
+  static const NPatchDataId INVALID_NPATCH_DATA_ID = -1; ///< Used to represent a null TextureId or error
 
   /**
    * @brief Loading State of the NPatch image.
@@ -269,22 +269,32 @@ private:
    */
   void LoadComplete(bool loadSuccess, TextureInformation textureInformation) override;
 
+  /**
+   * This is called by the TextureUploadObserver when an observer is destroyed.
+   * We use the callback to know when to remove an observer from our notify list.
+   * @param[in] observer The observer that generated the callback
+   */
+  void ObserverDestroyed(TextureUploadObserver* observer);
+
 private:
   using ObserverListType = Dali::Vector<TextureUploadObserver*>;
 
   NPatchDataId                 mId;
-  ObserverListType             mObserverList;      ///< Container used to store all observer clients of this Texture
-  VisualUrl                    mUrl;               ///< Url of the N-Patch
-  TextureSet                   mTextureSet;        ///< Texture containing the cropped image
-  NPatchUtility::StretchRanges mStretchPixelsX;    ///< X stretch pixels
-  NPatchUtility::StretchRanges mStretchPixelsY;    ///< Y stretch pixels
-  std::size_t                  mHash;              ///< Hash code for the Url
-  uint32_t                     mCroppedWidth;      ///< Width of the cropped middle part of N-patch
-  uint32_t                     mCroppedHeight;     ///< Height of the cropped middle part of N-patch
-  Rect<int>                    mBorder;            ///< The size of the border
-  LoadingState                 mLoadingState;      ///< True if the data loading is completed
-  bool                         mPreMultiplyOnLoad; ///< Whether to multiply alpha into color channels on load
-  void*                        mRenderingMap;      ///< NPatch rendering data
+  ObserverListType             mObserverList;    ///< Container used to store all observer clients of this Texture
+  ObserverListType             mQueuedObservers; ///< Container observers when user try to add during notify observers
+  VisualUrl                    mUrl;             ///< Url of the N-Patch
+  TextureSet                   mTextureSet;      ///< Texture containing the cropped image
+  NPatchUtility::StretchRanges mStretchPixelsX;  ///< X stretch pixels
+  NPatchUtility::StretchRanges mStretchPixelsY;  ///< Y stretch pixels
+  std::size_t                  mHash;            ///< Hash code for the Url
+  uint32_t                     mCroppedWidth;    ///< Width of the cropped middle part of N-patch
+  uint32_t                     mCroppedHeight;   ///< Height of the cropped middle part of N-patch
+  Rect<int>                    mBorder;          ///< The size of the border
+  LoadingState                 mLoadingState;    ///< True if the data loading is completed
+  void*                        mRenderingMap;    ///< NPatch rendering data
+
+  bool mPreMultiplyOnLoad : 1; ///< Whether to multiply alpha into color channels on load
+  bool mObserverNotifying : 1; ///< Whether this NPatchData notifying observers or not.
 };
 
 } // namespace Internal
index 5b2b713..3e18482 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 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.
@@ -23,6 +23,7 @@
 
 // EXTERNAL HEADERS
 #include <dali/devel-api/common/hash.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
 #include <dali/integration-api/debug.h>
 
 namespace Dali
@@ -39,24 +40,35 @@ constexpr auto UNINITIALIZED_ID    = int32_t{0};  ///< uninitialised id, use to
 } // Anonymous namespace
 
 NPatchLoader::NPatchLoader()
-: mCurrentNPatchDataId(0)
+: mCurrentNPatchDataId(0),
+  mRemoveProcessorRegistered(false)
 {
 }
 
 NPatchLoader::~NPatchLoader()
 {
+  if(mRemoveProcessorRegistered && Adaptor::IsAvailable())
+  {
+    Adaptor::Get().UnregisterProcessor(*this, true);
+    mRemoveProcessorRegistered = false;
+  }
 }
 
 NPatchData::NPatchDataId NPatchLoader::GenerateUniqueNPatchDataId()
 {
+  // Skip invalid id generation.
+  if(DALI_UNLIKELY(mCurrentNPatchDataId == NPatchData::INVALID_NPATCH_DATA_ID))
+  {
+    mCurrentNPatchDataId = 0;
+  }
   return mCurrentNPatchDataId++;
 }
 
-std::size_t NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
+NPatchData::NPatchDataId NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
 {
-  NPatchData* data = GetNPatchData(url, border, preMultiplyOnLoad);
+  std::shared_ptr<NPatchData> data = GetNPatchData(url, border, preMultiplyOnLoad);
 
-  DALI_ASSERT_ALWAYS(data && "NPatchData creation failed!");
+  DALI_ASSERT_ALWAYS(data.get() && "NPatchData creation failed!");
 
   if(data->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
   {
@@ -85,7 +97,7 @@ std::size_t NPatchLoader::Load(TextureManager& textureManager, TextureUploadObse
     auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD
                                                   : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
 
-    Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data, true, preMultiplyOnLoading);
+    Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data.get(), true, preMultiplyOnLoading);
 
     if(pixelBuffer)
     {
@@ -127,7 +139,18 @@ bool NPatchLoader::GetNPatchData(const NPatchData::NPatchDataId id, const NPatch
   return false;
 }
 
-void NPatchLoader::Remove(std::size_t id, TextureUploadObserver* textureObserver)
+void NPatchLoader::RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
+{
+  mRemoveQueue.push_back({id, textureObserver});
+
+  if(!mRemoveProcessorRegistered && Adaptor::IsAvailable())
+  {
+    mRemoveProcessorRegistered = true;
+    Adaptor::Get().RegisterProcessor(*this, true);
+  }
+}
+
+void NPatchLoader::Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
 {
   int32_t cacheIndex = GetCacheIndexFromId(id);
   if(cacheIndex == INVALID_CACHE_INDEX)
@@ -145,7 +168,22 @@ void NPatchLoader::Remove(std::size_t id, TextureUploadObserver* textureObserver
   }
 }
 
-NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
+void NPatchLoader::Process(bool postProcessor)
+{
+  for(auto& iter : mRemoveQueue)
+  {
+    Remove(iter.first, iter.second);
+  }
+  mRemoveQueue.clear();
+
+  if(Adaptor::IsAvailable())
+  {
+    Adaptor::Get().UnregisterProcessor(*this, true);
+    mRemoveProcessorRegistered = false;
+  }
+}
+
+std::shared_ptr<NPatchData> NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
 {
   std::size_t                              hash  = CalculateHash(url.GetUrl());
   std::vector<NPatchInfo>::size_type       index = UNINITIALIZED_ID;
@@ -164,7 +202,7 @@ NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& b
         if(mCache[index].mData->GetBorder() == border)
         {
           mCache[index].mReferenceCount++;
-          return mCache[index].mData.get();
+          return mCache[index].mData;
         }
         else
         {
@@ -197,7 +235,7 @@ NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& b
   // If this is new image loading, make new cache data
   if(infoPtr == nullptr)
   {
-    NPatchInfo info(new NPatchData());
+    NPatchInfo info(std::make_shared<NPatchData>());
     info.mData->SetId(GenerateUniqueNPatchDataId());
     info.mData->SetHash(hash);
     info.mData->SetUrl(url);
@@ -210,7 +248,7 @@ NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& b
   // Else if LOAD_COMPLETE, Same url but border is different - use the existing texture
   else if(infoPtr->mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
   {
-    NPatchInfo info(new NPatchData());
+    NPatchInfo info(std::make_shared<NPatchData>());
 
     info.mData->SetId(GenerateUniqueNPatchDataId());
     info.mData->SetHash(hash);
@@ -245,7 +283,7 @@ NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& b
 
   DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!");
 
-  return infoPtr->mData.get();
+  return infoPtr->mData;
 }
 
 } // namespace Internal
index 539009b..7e846e4 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_NPATCH_LOADER_H
 
 /*
- * Copyright (c) 2022 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.
 
 // EXTERNAL INCLUDES
 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
+#include <dali/integration-api/processor-interface.h>
 #include <dali/public-api/rendering/texture-set.h>
-#include <memory> // for std::unique_ptr
+#include <memory> // for std::shared_ptr
 #include <string>
+#include <utility> // for std::pair
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/devel-api/utility/npatch-utilities.h>
@@ -44,7 +46,7 @@ namespace Internal
  * small space and there's not usually a lot of them. Usually N patches are specified in
  * toolkit default style and there is 1-2 per control that are shared across the whole application.
  */
-class NPatchLoader
+class NPatchLoader : public Integration::Processor
 {
 public:
   /**
@@ -69,16 +71,7 @@ public:
    * @param [in] synchronousLoading True if the image will be loaded in synchronous time.
    * @return id of the texture.
    */
-  std::size_t Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading);
-
-  /**
-   * @brief Set loaded PixelBuffer and its information
-   *
-   * @param [in] id cache data id
-   * @param [in] pixelBuffer of loaded image
-   * @param [in] preMultiplied True if the image had pre-multiplied alpha applied
-   */
-  void SetNPatchData(std::size_t id, Devel::PixelBuffer& pixelBuffer, bool preMultiplied);
+  NPatchData::NPatchDataId Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading);
 
   /**
    * @brief Retrieve N patch data matching to an id
@@ -89,21 +82,36 @@ public:
   bool GetNPatchData(const NPatchData::NPatchDataId id, const NPatchData*& data);
 
   /**
-   * @brief Remove a texture matching id.
+   * @brief Request remove a texture matching id.
    * Erase the observer from the observer list of cache if we need.
-   * This API decrease cached NPatchInfo reference.
-   * If the NPatchInfo reference become 0, the textureSet will be reset.
    *
    * @param [in] id cache data id
    * @param [in] textureObserver The NPatchVisual that requested loading.
    */
-  void Remove(std::size_t id, TextureUploadObserver* textureObserver);
+  void RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver);
+
+protected: // Implementation of Processor
+  /**
+   * @copydoc Dali::Integration::Processor::Process()
+   */
+  void Process(bool postProcessor) override;
 
 private:
   NPatchData::NPatchDataId GenerateUniqueNPatchDataId();
 
   int32_t GetCacheIndexFromId(const NPatchData::NPatchDataId id);
 
+  /**
+   * @brief Remove a texture matching id.
+   * Erase the observer from the observer list of cache if we need.
+   * This API decrease cached NPatchInfo reference.
+   * If the NPatchInfo reference become 0, the textureSet will be reset.
+   *
+   * @param [in] id cache data id
+   * @param [in] textureObserver The NPatchVisual that requested loading.
+   */
+  void Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver);
+
 private:
   /**
    * @brief Information of NPatchData
@@ -111,7 +119,7 @@ private:
    */
   struct NPatchInfo
   {
-    NPatchInfo(NPatchData* data)
+    NPatchInfo(std::shared_ptr<NPatchData> data)
     : mData(data),
       mReferenceCount(1u)
     {
@@ -137,7 +145,7 @@ private:
     NPatchInfo(const NPatchInfo& info) = delete;            // Do not use copy constructor
     NPatchInfo& operator=(const NPatchInfo& info) = delete; // Do not use copy assign
 
-    std::unique_ptr<NPatchData> mData;
+    std::shared_ptr<NPatchData> mData;
     std::int16_t                mReferenceCount; ///< The number of N-patch visuals that use this data.
   };
 
@@ -151,7 +159,7 @@ private:
    *                                   image has no alpha channel
    * @return NPatchData pointer that Load function will used.
    */
-  NPatchData* GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad);
+  std::shared_ptr<NPatchData> GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad);
 
 protected:
   /**
@@ -167,6 +175,10 @@ protected:
 private:
   NPatchData::NPatchDataId mCurrentNPatchDataId;
   std::vector<NPatchInfo>  mCache;
+
+  std::vector<std::pair<NPatchData::NPatchDataId, TextureUploadObserver*>> mRemoveQueue; ///< Queue of textures to remove at PostProcess. It will be cleared after PostProcess.
+
+  bool mRemoveProcessorRegistered : 1; ///< Flag if remove processor registered or not.
 };
 
 } // namespace Internal
index d36eeb1..c9367a5 100644 (file)
@@ -239,7 +239,7 @@ void NPatchVisual::DoSetOffScene(Actor& actor)
   {
     if(mId != NPatchData::INVALID_NPATCH_DATA_ID)
     {
-      mLoader.Remove(mId, this);
+      mLoader.RequestRemove(mId, this);
       mImpl->mResourceStatus = Toolkit::Visual::ResourceStatus::PREPARING;
       mId                    = NPatchData::INVALID_NPATCH_DATA_ID;
     }
@@ -319,7 +319,7 @@ NPatchVisual::~NPatchVisual()
     {
       if(mId != NPatchData::INVALID_NPATCH_DATA_ID)
       {
-        mLoader.Remove(mId, this);
+        mLoader.RequestRemove(mId, this);
         mId = NPatchData::INVALID_NPATCH_DATA_ID;
       }
       if(mAuxiliaryTextureId != TextureManager::INVALID_TEXTURE_ID)
index 7e46322..faa354c 100644 (file)
@@ -29,7 +29,7 @@ namespace Toolkit
 {
 const unsigned int TOOLKIT_MAJOR_VERSION = 2;
 const unsigned int TOOLKIT_MINOR_VERSION = 2;
-const unsigned int TOOLKIT_MICRO_VERSION = 42;
+const unsigned int TOOLKIT_MICRO_VERSION = 43;
 const char* const  TOOLKIT_BUILD_DATE    = __DATE__ " " __TIME__;
 
 #ifdef DEBUG_ENABLED
index bccd06a..da90ed7 100644 (file)
@@ -1,6 +1,6 @@
 Name:       dali2-toolkit
 Summary:    Dali 3D engine Toolkit
-Version:    2.2.42
+Version:    2.2.43
 Release:    1
 Group:      System/Libraries
 License:    Apache-2.0 and BSD-3-Clause and MIT
@@ -544,13 +544,16 @@ 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}/dali-physics/dali-physics.h
 %{_includedir}/chipmunk/*
-%{_libdir}/pkgconfig/dali2-physics-2d.pc
 %{_libdir}/pkgconfig/chipmunk2d.pc
+%{_libdir}/pkgconfig/dali2-physics-2d.pc
 
 %files -n %{dali2_physics3d}
 %if 0%{?enable_dali_smack_rules}
@@ -560,11 +563,13 @@ 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}/dali-physics/dali-physics.h
 %{_includedir}/bullet/*
 %{_libdir}/pkgconfig/dali2-physics-3d.pc
 %{_libdir}/pkgconfig/bullet3.pc
-