[Tizen] 3D Scene Hit Testing 07/302307/2
authorAdam Bialogonski <adam.b@samsung.com>
Mon, 13 Nov 2023 07:34:01 +0000 (07:34 +0000)
committerhuiyu.eun <huiyu.eun@samsung.com>
Tue, 5 Dec 2023 08:04:44 +0000 (17:04 +0900)
- New functions creates mesh using list of vertices and face indices to work with NUI.
- Added the functionality to detect which mesh is hit & emit signal

Requires C# binding and NUI Model.cs update

Change-Id: Ib5bfd8ad6e7c35017db2c1173805f5c863156530
Signed-off-by: Adam Bialogonski <adam.b@samsung.com>
Signed-off-by: huiyu.eun <huiyu.eun@samsung.com>
21 files changed:
automated-tests/src/dali-scene3d/collider-mesh-data.h [new file with mode: 0644]
automated-tests/src/dali-scene3d/utc-Dali-NavigationMesh.cpp [new file with mode: 0644]
dali-scene3d/internal/algorithm/navigation-mesh-impl.cpp [new file with mode: 0644]
dali-scene3d/internal/algorithm/navigation-mesh-impl.h [new file with mode: 0644]
dali-scene3d/internal/controls/model/model-impl.cpp
dali-scene3d/internal/controls/model/model-impl.h
dali-scene3d/internal/event/collider-mesh-processor-impl.cpp [new file with mode: 0644]
dali-scene3d/internal/event/collider-mesh-processor-impl.h [new file with mode: 0644]
dali-scene3d/internal/event/collider-mesh-processor.cpp [new file with mode: 0644]
dali-scene3d/internal/event/collider-mesh-processor.h [new file with mode: 0644]
dali-scene3d/internal/file.list
dali-scene3d/internal/model-components/model-node-impl.cpp
dali-scene3d/internal/model-components/model-node-impl.h
dali-scene3d/public-api/algorithm/navigation-mesh.cpp [new file with mode: 0644]
dali-scene3d/public-api/algorithm/navigation-mesh.h [new file with mode: 0644]
dali-scene3d/public-api/controls/model/model.cpp
dali-scene3d/public-api/controls/model/model.h
dali-scene3d/public-api/loader/navigation-mesh-factory.cpp [new file with mode: 0644]
dali-scene3d/public-api/loader/navigation-mesh-factory.h [new file with mode: 0644]
dali-scene3d/public-api/model-components/model-node.cpp
dali-scene3d/public-api/model-components/model-node.h

diff --git a/automated-tests/src/dali-scene3d/collider-mesh-data.h b/automated-tests/src/dali-scene3d/collider-mesh-data.h
new file mode 100644 (file)
index 0000000..5e97c51
--- /dev/null
@@ -0,0 +1,75 @@
+#include <algorithm>
+#include <vector>
+
+unsigned char col_test_0[] = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x42, 0x00, 0x00, 0x35, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x42, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_0_len = 216;
+unsigned char col_test_1[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x41, 0x00, 0x00, 0x09, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x42, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x42, 0x00, 0x00, 0x09, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x41, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_1_len = 216;
+unsigned char col_test_10[]  = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xa6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x13, 0x44, 0x00, 0x80, 0x93, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1b, 0x44, 0x00, 0x80, 0xa6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1b, 0x44, 0x00, 0x00, 0xa0, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x13, 0x44, 0x00, 0x00, 0xa0, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44, 0x00, 0x80, 0x93, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0xff, 0xff, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0xff, 0xff, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0xff, 0xff, 0x01, 0x00, 0x04, 0x00, 0x03, 0x00, 0xff, 0xff, 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0xff, 0xff, 0x05, 0x00, 0x01, 0x00, 0x03, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_10_len = 360;
+unsigned char col_test_11[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x16, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x08, 0x44, 0x00, 0x80, 0x0f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x1e, 0x44, 0x00, 0xc0, 0x16, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x1e, 0x44, 0x00, 0x80, 0x0f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x08, 0x44, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_11_len = 216;
+unsigned char col_test_12[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x28, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, 0x00, 0x80, 0x0f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x80, 0x0f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x44, 0x00, 0x00, 0xd6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44, 0x00, 0x00, 0xd6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x44, 0x00, 0x00, 0xd6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc6, 0x43, 0x00, 0x00, 0xc0, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44, 0x00, 0x00, 0x8c, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc6, 0x43, 0x00, 0x00, 0xc0, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc6, 0x43, 0x00, 0x00, 0x28, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc6, 0x43, 0x00, 0x00, 0x09, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, 0x00, 0x80, 0xa6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, 0x00, 0x00, 0x28, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, 0x00, 0x00, 0x09, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x42, 0x00, 0x80, 0xa6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x42, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x03, 0x00, 0x04, 0x00, 0x01, 0x00, 0xff, 0xff, 0x04, 0x00, 0x05, 0x00, 0x01, 0x00, 0xff, 0xff, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x04, 0x00, 0x06, 0x00, 0x02, 0x00, 0xff, 0xff, 0x06, 0x00, 0x07, 0x00, 0x02, 0x00, 0xff, 0xff, 0x07, 0x00, 0x04, 0x00, 0x02, 0x00, 0xff, 0xff, 0x03, 0x00, 0x06, 0x00, 0x03, 0x00, 0xff, 0xff, 0x06, 0x00, 0x04, 0x00, 0x03, 0x00, 0xff, 0xff, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0xff, 0xff, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0xff, 0xff, 0x08, 0x00, 0x09, 0x00, 0x04, 0x00, 0xff, 0xff, 0x09, 0x00, 0x06, 0x00, 0x04, 0x00, 0xff, 0xff, 0x06, 0x00, 0x0a, 0x00, 0x05, 0x00, 0xff, 0xff, 0x0a, 0x00, 0x08, 0x00, 0x05, 0x00, 0xff, 0xff, 0x08, 0x00, 0x06, 0x00, 0x05, 0x00, 0xff, 0xff, 0x02, 0x00, 0x06, 0x00, 0x06, 0x00, 0xff, 0xff, 0x06, 0x00, 0x03, 0x00, 0x06, 0x00, 0xff, 0xff, 0x03, 0x00, 0x02, 0x00, 0x06, 0x00, 0xff, 0xff, 0x02, 0x00, 0x0a, 0x00, 0x07, 0x00, 0xff, 0xff, 0x0a, 0x00, 0x06, 0x00, 0x07, 0x00, 0xff, 0xff, 0x06, 0x00, 0x02, 0x00, 0x07, 0x00, 0xff, 0xff, 0x01, 0x00, 0x0a, 0x00, 0x08, 0x00, 0xff, 0xff, 0x0a, 0x00, 0x02, 0x00, 0x08, 0x00, 0xff, 0xff, 0x02, 0x00, 0x01, 0x00, 0x08, 0x00, 0xff, 0xff, 0x0b, 0x00, 0x0a, 0x00, 0x09, 0x00, 0xff, 0xff, 0x0a, 0x00, 0x01, 0x00, 0x09, 0x00, 0xff, 0xff, 0x01, 0x00, 0x0b, 0x00, 0x09, 0x00, 0xff, 0xff, 0x0c, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0xff, 0xff, 0x0a, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0xff, 0xff, 0x0b, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0xff, 0xff, 0x0a, 0x00, 0x0c, 0x00, 0x0b, 0x00, 0xff, 0xff, 0x0c, 0x00, 0x0d, 0x00, 0x0b, 0x00, 0xff, 0xff, 0x0d, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0xff, 0xff, 0x07, 0x00, 0x06, 0x00, 0x0c, 0x00, 0xff, 0xff, 0x06, 0x00, 0x09, 0x00, 0x0c, 0x00, 0xff, 0xff, 0x09, 0x00, 0x07, 0x00, 0x0c, 0x00, 0xff, 0xff, 0x0e, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0xff, 0xff, 0x0c, 0x00, 0x0b, 0x00, 0x0d, 0x00, 0xff, 0xff, 0x0b, 0x00, 0x0e, 0x00, 0x0d, 0x00, 0xff, 0xff, 0x0c, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0xff, 0xff, 0x0e, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xff, 0xff, 0x0f, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0a, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_12_len = 1140;
+unsigned char col_test_13[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc6, 0x43, 0x00, 0x00, 0x28, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xd4, 0x43, 0x00, 0x00, 0x8c, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xd4, 0x43, 0x00, 0x00, 0x28, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc6, 0x43, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_13_len = 216;
+unsigned char col_test_14[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x44, 0x00, 0x00, 0xd6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x1e, 0x44, 0x00, 0x80, 0x0f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x1e, 0x44, 0x00, 0x00, 0xd6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x44, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_14_len = 216;
+unsigned char col_test_2[]    = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x84, 0x43, 0x00, 0x80, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x42, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x84, 0x43, 0x00, 0x00, 0x09, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, 0x00, 0x00, 0x09, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x42, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x04, 0x00, 0x01, 0x00, 0xff, 0xff, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0xff, 0xff, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0xff, 0xff, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x03, 0x00, 0xff, 0xff, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0xff, 0xff, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_2_len = 360;
+unsigned char col_test_3[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xa6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x43, 0x00, 0x00, 0x28, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, 0x00, 0x80, 0xa6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x43, 0x00, 0x00, 0x28, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x43, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_3_len = 216;
+unsigned char col_test_4[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x43, 0x00, 0x40, 0x2f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x80, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x40, 0x2f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x43, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_4_len = 216;
+unsigned char col_test_5[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x43, 0x00, 0x80, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x84, 0x43, 0x00, 0x40, 0x2f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x43, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x40, 0x2f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x40, 0x20, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x84, 0x43, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x04, 0x00, 0x01, 0x00, 0xff, 0xff, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0xff, 0xff, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0xff, 0xff, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x03, 0x00, 0xff, 0xff, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0xff, 0xff, 0x05, 0x00, 0x03, 0x00, 0x03, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_5_len = 360;
+unsigned char col_test_6[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x93, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x00, 0x8c, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xd3, 0x43, 0x00, 0x80, 0x93, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xd3, 0x43, 0x00, 0x00, 0x8c, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_6_len = 216;
+unsigned char col_test_7[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc6, 0x43, 0x00, 0x80, 0x93, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44, 0x00, 0x00, 0xc0, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44, 0x00, 0x80, 0x93, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc6, 0x43, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_7_len = 216;
+unsigned char col_test_8[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x80, 0x0f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x08, 0x44, 0x00, 0x80, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x08, 0x44, 0x00, 0x80, 0x0f, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc4, 0x43, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int  col_test_8_len = 216;
+unsigned char col_test_9[]   = {
+  0x4e, 0x41, 0x56, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1b, 0x44, 0x00, 0x80, 0xa6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x13, 0x44, 0x00, 0x80, 0xa6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1b, 0x44, 0x00, 0x00, 0xd6, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44, 0x00, 0x00, 0xa0, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x13, 0x44, 0x00, 0x00, 0xa0, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00, 0xff, 0xff, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0xff, 0xff, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0xff, 0xff, 0x04, 0x00, 0x03, 0x00, 0x03, 0x00, 0xff, 0xff, 0x03, 0x00, 0x05, 0x00, 0x03, 0x00, 0xff, 0xff, 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+unsigned int col_test_9_len = 360;
+
+static const std::vector<std::pair<uint8_t*, uint32_t>> TEST_COLLIDER_MESH = {
+  {col_test_0, col_test_0_len},
+  {col_test_1, col_test_1_len},
+  {col_test_2, col_test_2_len},
+  {col_test_3, col_test_3_len},
+  {col_test_4, col_test_4_len},
+  {col_test_5, col_test_5_len},
+  {col_test_6, col_test_6_len},
+  {col_test_7, col_test_7_len},
+  {col_test_8, col_test_8_len},
+  {col_test_9, col_test_9_len},
+};
+
+#define COLLIDER_BUFFER(N)                                              \
+  std::invoke([]() {                                                    \
+    auto v = std::vector<uint8_t>();                                    \
+    v.insert(v.end(), col_test_##N, col_test_##N + col_test_##N##_len); \
+    return v;                                                           \
+  })
+
+static std::vector<uint8_t> GetTestColliderMesh(int n)
+{
+  auto v = std::vector<uint8_t>();
+  v.insert(v.end(), TEST_COLLIDER_MESH[n].first, TEST_COLLIDER_MESH[n].first + TEST_COLLIDER_MESH[n].second);
+  return v;
+}
diff --git a/automated-tests/src/dali-scene3d/utc-Dali-NavigationMesh.cpp b/automated-tests/src/dali-scene3d/utc-Dali-NavigationMesh.cpp
new file mode 100644 (file)
index 0000000..5df30be
--- /dev/null
@@ -0,0 +1,815 @@
+/*
+ * 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-scene3d/public-api/controls/model/model.h>
+#include <dali-scene3d/public-api/controls/scene-view/scene-view.h>
+#include <dali-toolkit-test-suite-utils.h>
+#include <dali/devel-api/actors/camera-actor-devel.h>
+#include <dali/integration-api/events/touch-event-integ.h>
+#include <dlfcn.h>
+#include "dali-scene3d/public-api/algorithm/navigation-mesh.h"
+#include "dali-scene3d/public-api/loader/navigation-mesh-factory.h"
+
+// include collider mesh data
+#include "collider-mesh-data.h"
+
+using namespace Dali;
+using namespace Dali::Scene3D::Algorithm;
+using namespace Dali::Scene3D::Loader;
+
+/**
+ * SysOverride allows overriding a system symbol and
+ * set the return value for n-th call of it.
+ *
+ * After invoking the symbol override is disabled.
+ */
+template<class R, class F>
+struct SysOverride
+{
+  SysOverride(const char* funcName)
+  {
+    funcNameStr = funcName;
+    if(!func)
+    {
+      func = decltype(func)(dlsym(RTLD_NEXT, funcName));
+    }
+  }
+
+  void SetReturnValue(R value, uint32_t n)
+  {
+    if(overrideEnabled)
+    {
+      tet_infoline("Warning! Overriding return value is already enabled! Ignoring!\n");
+      return;
+    }
+    result          = value;
+    overrideCounter = n;
+    overrideEnabled = true;
+  }
+
+  template<class... Args>
+  R Invoke(Args&&... args)
+  {
+    auto retval = func(args...);
+    if(overrideEnabled)
+    {
+      if(!overrideCounter)
+      {
+        overrideEnabled = false;
+        return result;
+      }
+      overrideCounter--;
+    }
+    return retval;
+  }
+
+  std::string funcNameStr;
+  R           result{R{}};
+  F*          func{nullptr};
+  uint32_t    overrideCounter = 0;
+  bool        overrideEnabled = false;
+};
+
+// Override fseek()
+static thread_local SysOverride<int, decltype(fseek)> call_fseek("fseek");
+extern "C" int                                        fseek(FILE* s, long int o, int w)
+{
+  return call_fseek.Invoke(s, o, w);
+}
+
+// Override ftell()
+static thread_local SysOverride<int, decltype(ftell)> call_ftell("ftell");
+extern "C" long int                                   ftell(FILE* s)
+{
+  return call_ftell.Invoke(s);
+}
+
+// Override fread()
+static thread_local SysOverride<int, decltype(fread)> call_fread("fread");
+extern "C" size_t                                     fread(void* __restrict p, size_t s, size_t n, FILE* __restrict st)
+{
+  return call_fread.Invoke(p, s, n, st);
+}
+
+// Data to test factory
+static std::vector<Dali::Vector3> COLLIDER_0_VERTS = {
+  Dali::Vector3(-1.000000, -1.556106, 0.000000),
+  Dali::Vector3(1.000000, -1.556106, 0.000000),
+  Dali::Vector3(-1.000000, 1.000000, 0.000000),
+  Dali::Vector3(1.000000, 1.000000, 0.000000),
+  Dali::Vector3(3.026269, -1.556106, 0.000000),
+  Dali::Vector3(3.026269, 1.000000, 0.000000),
+  Dali::Vector3(-1.000000, 2.491248, 0.000000),
+  Dali::Vector3(1.000000, 2.491248, 0.000000),
+};
+static std::vector<uint32_t> COLLIDER_0_IDX = {
+  1,
+  2,
+  0,
+  1,
+  5,
+  3,
+  3,
+  6,
+  2,
+  1,
+  3,
+  2,
+  1,
+  4,
+  5,
+  3,
+  7,
+  6,
+};
+static std::vector<Dali::Vector3> COLLIDER_1_VERTS = {
+  Dali::Vector3(-1.000000, -3.386207, 0.000000),
+  Dali::Vector3(1.000000, -3.386207, 0.000000),
+  Dali::Vector3(-1.000000, 1.000000, 0.000000),
+  Dali::Vector3(1.000000, 1.000000, 0.000000),
+  Dali::Vector3(-3.393266, -3.386207, 0.000000),
+  Dali::Vector3(-3.393266, 1.000000, 0.000000),
+};
+static std::vector<uint32_t> COLLIDER_1_IDX = {
+  1,
+  2,
+  0,
+  2,
+  4,
+  0,
+  1,
+  3,
+  2,
+  2,
+  5,
+  4,
+};
+static std::vector<Dali::Vector3> COLLIDER_2_VERTS = {
+  Dali::Vector3(-3.393266, -1.000000, 0.000000),
+  Dali::Vector3(1.000000, -1.000000, 0.000000),
+  Dali::Vector3(-3.393266, 0.491248, 0.000000),
+  Dali::Vector3(1.000000, 0.491248, 0.000000),
+};
+static std::vector<uint32_t> COLLIDER_2_IDX = {
+  1,
+  2,
+  0,
+  1,
+  3,
+  2,
+};
+
+Integration::TouchEvent GenerateSingleTouch(PointState::Type state, const Vector2& screenPosition)
+{
+  Integration::TouchEvent touchEvent;
+  Integration::Point      point;
+  point.SetState(state);
+  point.SetScreenPosition(screenPosition);
+  point.SetDeviceClass(Device::Class::TOUCH);
+  point.SetDeviceSubclass(Device::Subclass::NONE);
+  touchEvent.points.push_back(point);
+  return touchEvent;
+}
+
+int UtcDaliNavigationMeshCreateFromFileFail1(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromFileFail1: Fails to create navigation mesh from file");
+
+  // No such file, misspelled name
+  auto result = NavigationMeshFactory::CreateFromFile("notexisting.bin");
+
+  DALI_TEST_CHECK(result == nullptr);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCreateFromFileFail2(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromFileFail2: Fails to create navigation mesh using file");
+
+  // Override next fseek to fail
+  call_fseek.SetReturnValue(-1, 0);
+  auto result = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(result == nullptr);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCreateFromFileFail3(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromFileFail3: Fails to create navigation mesh using file");
+
+  // Override next ftell to fail
+  call_ftell.SetReturnValue(-1, 0);
+  auto result = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(result == nullptr);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCreateFromFileFail4(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromFileFail4: Fails to create navigation mesh using file");
+
+  // Override 2nd fseek to fail
+  call_fseek.SetReturnValue(-1, 1);
+  auto result = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(result == nullptr);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCreateFromFileFail5(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromFileFail5: Fails to create navigation mesh using file");
+
+  // Override fread() to fail reading file
+  call_fread.SetReturnValue(-1, 0);
+  auto result = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(result == nullptr);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCreateFromFileOk1(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromFileOk1: Creates navigation mesh using file");
+
+  auto result = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(result != nullptr);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCreateFromBufferP(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromBufferP: Creates navigation mesh using binary buffer");
+
+  auto                  fin    = fopen("resources/navmesh-test.bin", "rb");
+  [[maybe_unused]] auto err    = fseek(fin, 0, SEEK_END);
+  auto                  length = ftell(fin);
+  fseek(fin, 0, SEEK_SET);
+  std::vector<uint8_t> buffer;
+  buffer.resize(length);
+  fread(buffer.data(), 1, length, fin);
+  fclose(fin);
+  auto result = NavigationMeshFactory::CreateFromBuffer(buffer);
+  DALI_TEST_CHECK(result != nullptr);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCountersP(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCountersP: Test vertex, edge and face counts");
+
+  auto result = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(result != nullptr);
+
+  auto vertexCount = result->GetVertexCount();
+  auto edgeCount   = result->GetEdgeCount();
+  auto faceCount   = result->GetFaceCount();
+
+  DALI_TEST_EQUALS(vertexCount, 132, TEST_LOCATION);
+  DALI_TEST_EQUALS(edgeCount, 300, TEST_LOCATION);
+  DALI_TEST_EQUALS(faceCount, 165, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshGetVertexP(void)
+{
+  tet_infoline("UtcDaliNavigationMeshGetVertexP: Test vertex getters");
+
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(navmesh != nullptr);
+
+  auto vertexCount = navmesh->GetVertexCount();
+
+  DALI_TEST_EQUALS(vertexCount, 132, TEST_LOCATION);
+
+  // List of coords, must be verified with Blender exporter
+  // clang-format off
+  std::vector<float> vertexData = {
+    -7.000000f, -3.000000f, 0.000000f, -4.018748f, 3.000000f, 0.000000f,
+    1.943754f, -1.500000f, 0.000000f, -2.541295f, -0.756627f, 0.000000f,
+    -0.277504f, -1.593252f, 0.000000f, 0.682341f, 2.316388f, 3.349901f,
+    1.912569f, 1.240314f, 2.549901f, 2.215021f, -0.365898f, 1.749901f,
+    1.460422f, -1.815717f, 0.949901f, -0.336699f, -2.992929f, 3.829999f,
+    -3.179410f, 0.153939f, 3.829999f, -3.664814f, 2.992929f, 3.829999f,
+    -1.384417f, 0.876845f, 3.829999f, -1.571236f, 1.101834f, 3.829999f
+  };
+  // clang-format on
+
+  auto j = 0;
+  for(auto i = 0u; i < 132; i += 10, j += 3)
+  {
+    const auto* vertex = navmesh->GetVertex(i);
+    Vector3     v0(vertex->coordinates);
+    Vector3     v1(vertexData[j], vertexData[j + 1], vertexData[j + 2]);
+    DALI_TEST_EQUALS(v0, v1, TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshGetEdgeP(void)
+{
+  tet_infoline("UtcDaliNavigationMeshGetEdgeP: Test edge getters");
+
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(navmesh != nullptr);
+
+  auto edgeCount = navmesh->GetEdgeCount();
+
+  DALI_TEST_EQUALS(edgeCount, 300, TEST_LOCATION);
+
+  // List of coords, must be verified with Blender exporter
+  // clang-format off
+  std::vector<EdgeIndex> edgeData = {
+    2, 65535, 8, 1,
+    8, 109, 124, 108,
+    10, 158, 32, 35,
+    78, 65535, 50, 52,
+    54, 75, 70, 69,
+    83, 65535, 83, 81,
+    79, 65535, 86, 42,
+    140, 65535, 94, 115,
+    111, 112, 118, 111,
+    101, 143, 106, 127
+  };
+  // clang-format on
+  auto j = 0;
+  for(auto i = 0u; i < 300; i += 30, j += 4)
+  {
+    const auto* edge = navmesh->GetEdge(i);
+    auto        e0   = edge->face[0];
+    auto        e1   = edge->face[1];
+    auto        v0   = edge->vertex[0];
+    auto        v1   = edge->vertex[1];
+
+    DALI_TEST_EQUALS(e0, edgeData[j + 0], TEST_LOCATION);
+    DALI_TEST_EQUALS(e1, edgeData[j + 1], TEST_LOCATION);
+    DALI_TEST_EQUALS(v0, edgeData[j + 2], TEST_LOCATION);
+    DALI_TEST_EQUALS(v1, edgeData[j + 3], TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshGetFaceP(void)
+{
+  tet_infoline("UtcDaliNavigationMeshGetFaceP: Test face getters");
+
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  DALI_TEST_CHECK(navmesh != nullptr);
+
+  auto faceCount = navmesh->GetFaceCount();
+
+  DALI_TEST_EQUALS(faceCount, 165, TEST_LOCATION);
+
+  // List of coords, must be verified with Blender exporter
+  // clang-format off
+
+  std::vector<NavigationMesh::Face> faceData = {
+    {{6, 10, 17}, {14, 32, 8}, {0.000000f, 0.000000f, 1.000000f}, {-3.024998f, 2.500000f, 0.000000f}},
+    {{130, 120, 44}, {228, 215, 33}, {0.000000f, 0.000000f, 1.000000f}, {-1.097451f, 1.192811f, 3.829999f}},
+    {{30, 9, 38}, {13, 291, 289}, {0.000000f, -0.000000f, 1.000000f}, {-3.029388f, -1.252209f, 0.000000f}},
+    {{55, 52, 53}, {140, 95, 96}, {0.522345f, -0.298279f, 0.798865f}, {0.743287f, 1.610713f, 3.136567f}},
+    {{69, 66, 67}, {91, 121, 122}, {0.071722f, -0.597219f, 0.798865f}, {1.632142f, 0.155658f, 2.016567f}},
+    {{41, 86, 87}, {81, 160, 80}, {-0.563316f, -0.210929f, 0.798864f}, {0.340215f, -1.799765f, 0.416567f}},
+    {{28, 19, 27}, {55, 74, 47}, {0.000000f, -0.000000f, 1.000000f}, {-0.640862f, -1.037395f, 0.000000f}},
+    {{118, 96, 111}, {213, 241, 240}, {0.000000f, 0.000000f, 1.000000f}, {-6.577459f, -0.586560f, 3.829999f}},
+    {{91, 107, 103}, {170, 258, 257}, {-0.021129f, 0.023143f, 0.999509f}, {-2.551766f, 1.007552f, 3.829145f}},
+    {{97, 120, 130}, {191, 228, 271}, {0.000000f, 0.000000f, 1.000000f}, {-1.795930f, 0.710873f, 3.829999f}},
+    {{30, 39, 31}, {290, 296, 295}, {0.000000f, 0.000000f, 1.000000f}, {-2.291577f, -0.509718f, 0.000000f}},
+  };
+  // clang-format on
+  auto j = 0;
+  for(auto i = 0u; i < 165; i += 16, j++)
+  {
+    const auto* face = navmesh->GetFace(i);
+    Vector3     n0(face->normal);
+    Vector3     c0(face->center);
+
+    Vector3 n1(faceData[j].normal);
+    Vector3 c1(faceData[j].center);
+
+    DALI_TEST_EQUALS(n0, n1, TEST_LOCATION);
+    DALI_TEST_EQUALS(c0, c1, TEST_LOCATION);
+
+    DALI_TEST_EQUALS(faceData[j].vertex[0], face->vertex[0], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].vertex[1], face->vertex[1], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].vertex[2], face->vertex[2], TEST_LOCATION);
+
+    DALI_TEST_EQUALS(faceData[j].edge[0], face->edge[0], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].edge[1], face->edge[1], TEST_LOCATION);
+    DALI_TEST_EQUALS(faceData[j].edge[2], face->edge[2], TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliNavigationGetGravityP(void)
+{
+  tet_infoline("UtcDaliNavigationGetGravityP: Tests gravity vector");
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+  auto gravity = navmesh->GetGravityVector();
+
+  // navmesh-test.bin is exported in Blender and the default gravity is Z = -1
+  Dali::Vector3 expectedGravity(0.0f, 0.0f, -1.0f);
+
+  DALI_TEST_EQUALS(gravity, expectedGravity, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationSetTransformP(void)
+{
+  tet_infoline("UtcDaliNavigationSetTransformP: Test setting transform");
+
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  Matrix matrix;
+  matrix.SetIdentity();
+  Quaternion q = Quaternion(Radian(Degree(-90)), Vector3(1.0, 0.0, 0.0));
+  Matrix     newMatrix;
+  matrix.Multiply(newMatrix, matrix, q); // Rotate matrix along X-axis
+
+  navmesh->SetSceneTransform(newMatrix);
+
+  auto point = Vector3(0, -1, 0);
+
+  [[maybe_unused]] Vector3 navMeshLocalSpace;
+  [[maybe_unused]] Vector3 navMeshParentSpace;
+  navMeshLocalSpace = navmesh->PointSceneToLocal(point);
+
+  // Should match gravity vector
+  auto gravityVector = navmesh->GetGravityVector();
+
+  // 'point' should be turned into the gravity vector after transforming into the local space
+  DALI_TEST_EQUALS(navMeshLocalSpace, gravityVector, std::numeric_limits<float>::epsilon(), TEST_LOCATION);
+
+  navMeshParentSpace = navmesh->PointLocalToScene(gravityVector);
+
+  // The gravity should be transformed back into point
+  DALI_TEST_EQUALS(navMeshParentSpace, point, std::numeric_limits<float>::epsilon(), TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliNavigationFindFloor0P(void)
+{
+  tet_infoline("UtcDaliNavigationFindFloor0P: Finds floor with result");
+
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  // All calculations in the navmesh local space
+  navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
+
+  std::vector<Vector3>   inPositions;
+  std::vector<Vector3>   expectedPositions;
+  std::vector<FaceIndex> expectedFaceIndex;
+  std::vector<bool>      expectedResult;
+
+  // Lift slightly over the floor level
+  auto upFromGravity = navmesh->GetGravityVector() * (0.05f);
+
+  auto size = navmesh->GetFaceCount();
+  for(auto i = 0u; i < size; ++i)
+  {
+    const auto* face = navmesh->GetFace(i);
+    Vector3(face->center);
+    inPositions.emplace_back(Vector3(face->center));
+    inPositions.back() -= Vector3(upFromGravity);
+    expectedResult.emplace_back(true);
+
+    expectedPositions.emplace_back(face->center);
+    expectedFaceIndex.emplace_back(i);
+  }
+
+  // Add negative results
+  // Middle 'circle' of scene
+  inPositions.emplace_back(Vector3(-0.048838f, 0.039285f, 0.013085f));
+  expectedPositions.emplace_back();
+  expectedFaceIndex.emplace_back(NavigationMesh::NULL_FACE);
+  expectedResult.emplace_back(false);
+
+  // Triangle under stairs
+  inPositions.emplace_back(Vector3(0.44365f, -1.787f, 0.13085f));
+  expectedPositions.emplace_back();
+  expectedFaceIndex.emplace_back(NavigationMesh::NULL_FACE);
+  expectedResult.emplace_back(false);
+
+  // Outside area
+  inPositions.emplace_back(Vector3(0.77197f, -3.8596f, 0.13085f));
+  expectedPositions.emplace_back();
+  expectedFaceIndex.emplace_back(NavigationMesh::NULL_FACE);
+  expectedResult.emplace_back(false);
+
+  for(auto i = 0u; i < inPositions.size(); ++i)
+  {
+    Vector3   outPosition;
+    FaceIndex faceIndex{NavigationMesh::NULL_FACE};
+    auto      result = navmesh->FindFloor(inPositions[i], outPosition, faceIndex);
+    DALI_TEST_EQUALS(bool(result), bool(expectedResult[i]), TEST_LOCATION);
+    DALI_TEST_EQUALS(faceIndex, expectedFaceIndex[i], TEST_LOCATION);
+    DALI_TEST_EQUALS(outPosition, expectedPositions[i], TEST_LOCATION);
+  }
+
+  END_TEST;
+}
+
+int UtcDaliNavigationFindFloorForFace1P(void)
+{
+  tet_infoline("UtcDaliNavigationFindFloorForFace1P: Finds floor for selected face");
+
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  // All calculations in the navmesh local space
+  navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
+
+  {
+    auto faceIndex           = FaceIndex(0u);
+    auto position            = Vector3(0, 0, 0);
+    auto dontCheckNeighbours = true;
+    auto outPosition         = Vector3();
+    auto expectedPosition    = Vector3();
+    bool result              = false;
+
+    {
+      // test 1. position lies within selected triangle
+      faceIndex           = 137;
+      position            = Vector3(-6.0767f, -1.7268f, 4.287f);
+      expectedPosition    = Vector3(-6.0767f, -1.7268f, 3.83f);
+      dontCheckNeighbours = true;
+      result              = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
+
+      DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+      DALI_TEST_EQUALS(outPosition, expectedPosition, TEST_LOCATION);
+    }
+
+    {
+      // test 2. position lies outside selected triangle, not checking neighbours
+      faceIndex           = 137;
+      position            = Vector3(-5.3073f, -0.6023f, 4.287f);
+      expectedPosition    = Vector3::ZERO;
+      outPosition         = Vector3::ZERO;
+      dontCheckNeighbours = true;
+      result              = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
+
+      DALI_TEST_EQUALS(result, false, TEST_LOCATION);
+      DALI_TEST_EQUALS(outPosition, expectedPosition, TEST_LOCATION);
+    }
+
+    {
+      // test 3. position lies outside selected triangle but this time checking neighbours
+      faceIndex           = 137;
+      position            = Vector3(-5.3073f, -0.6023f, 4.287f);
+      expectedPosition    = Vector3(-5.3073, -0.6023, 3.83);
+      outPosition         = Vector3::ZERO;
+      dontCheckNeighbours = false;
+      result              = navmesh->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
+
+      DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+      DALI_TEST_EQUALS(outPosition, expectedPosition, TEST_LOCATION);
+    }
+  }
+
+  END_TEST;
+}
+
+int UtcDaliNavigationFindFloorForFace2P(void)
+{
+  tet_infoline("UtcDaliNavigationFindFloorForFace2P: Finds floor for selected face");
+
+  auto navmesh = NavigationMeshFactory::CreateFromFile("resources/navmesh-test.bin");
+
+  // All calculations in the navmesh local space
+  navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
+
+  {
+    [[maybe_unused]] auto faceIndex           = 0u;
+    auto                  position            = Vector3(0, 0, 0);
+    auto                  dontCheckNeighbours = true;
+    auto                  outPosition         = Vector3();
+    auto                  expectedPosition    = Vector3();
+    bool                  result              = false;
+
+    {
+      // test 4. position lies within a triangle but this time full search forced,
+      // the navmesh must have no previous searches (mCurrentFace shouldn't be set)
+      faceIndex           = 137;
+      position            = Vector3(-6.0767f, -1.7268f, 4.287f);
+      expectedPosition    = Vector3(-6.0767f, -1.7268f, 3.83f);
+      dontCheckNeighbours = true;
+      result              = navmesh->FindFloorForFace(position, NavigationMesh::NULL_FACE, dontCheckNeighbours, outPosition);
+
+      DALI_TEST_EQUALS(result, true, TEST_LOCATION);
+      DALI_TEST_EQUALS(outPosition, expectedPosition, TEST_LOCATION);
+    }
+  }
+
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCreateFromVerticesAndFaces(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromVerticesAndFaces: Creates NavigationMesh using vertices and faces");
+
+  auto buffer0 = COLLIDER_BUFFER(0);
+
+  // All calculations in the navmesh local space
+  auto fn = [&](const auto& vertices, const auto& normals, const auto& indices) {
+    auto navmesh = NavigationMeshFactory::CreateFromVertexFaceList(vertices, normals, indices);
+    navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
+    DALI_TEST_EQUALS(navmesh->GetVertexCount(), vertices.size(), TEST_LOCATION);
+    DALI_TEST_EQUALS(navmesh->GetFaceCount(), indices.size() / 3, TEST_LOCATION);
+    DALI_TEST_EQUALS(navmesh->GetEdgeCount(), indices.size(), TEST_LOCATION);
+
+    // compare data
+    for(auto i = 0u; i < navmesh->GetVertexCount(); ++i)
+    {
+      Dali::Vector3 v(navmesh->GetVertex(i)->coordinates);
+      DALI_TEST_EQUALS(vertices[i], v, TEST_LOCATION);
+    }
+
+    for(auto i = 0u; i < navmesh->GetFaceCount() * 3; i += 3)
+    {
+      const auto& v = navmesh->GetFace(i / 3)->vertex;
+      DALI_TEST_EQUALS(indices[i], v[0], TEST_LOCATION);
+      DALI_TEST_EQUALS(indices[i + 1], v[1], TEST_LOCATION);
+      DALI_TEST_EQUALS(indices[i + 2], v[2], TEST_LOCATION);
+    }
+  };
+
+  std::vector<Vector3> normals;
+  normals.resize(COLLIDER_0_VERTS.size());
+  std::fill(normals.begin(), normals.end(), Vector3(0.0, 1.0, 0.0));
+  fn(COLLIDER_0_VERTS, normals, COLLIDER_0_IDX);
+
+  normals.resize(COLLIDER_1_VERTS.size());
+  std::fill(normals.begin(), normals.end(), Vector3(0.0, 1.0, 0.0));
+  fn(COLLIDER_1_VERTS, normals, COLLIDER_1_IDX);
+
+  normals.resize(COLLIDER_2_VERTS.size());
+  std::fill(normals.begin(), normals.end(), Vector3(0.0, 1.0, 0.0));
+  fn(COLLIDER_2_VERTS, normals, COLLIDER_2_IDX);
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshCreateFromVerticesAndFacesNoNormals(void)
+{
+  tet_infoline("UtcDaliNavigationMeshCreateFromVerticesAndFacesNoNormals: Creates NavigationMesh using vertices and faces but recalculates normals");
+
+  auto buffer0 = COLLIDER_BUFFER(0);
+
+  // All calculations in the navmesh local space
+  auto fn = [&](const auto& vertices, const auto& indices) {
+    auto navmesh = NavigationMeshFactory::CreateFromVertexFaceList(vertices.data(), nullptr, vertices.size(), indices.data(), indices.size());
+    navmesh->SetSceneTransform(Matrix(Matrix::IDENTITY));
+    DALI_TEST_EQUALS(navmesh->GetVertexCount(), vertices.size(), TEST_LOCATION);
+    DALI_TEST_EQUALS(navmesh->GetFaceCount(), indices.size() / 3, TEST_LOCATION);
+    DALI_TEST_EQUALS(navmesh->GetEdgeCount(), indices.size(), TEST_LOCATION);
+
+    // compare data
+    for(auto i = 0u; i < navmesh->GetVertexCount(); ++i)
+    {
+      Dali::Vector3 v(navmesh->GetVertex(i)->coordinates);
+      DALI_TEST_EQUALS(vertices[i], v, TEST_LOCATION);
+    }
+
+    for(auto i = 0u; i < navmesh->GetFaceCount() * 3; i += 3)
+    {
+      const auto& v = navmesh->GetFace(i / 3)->vertex;
+      DALI_TEST_EQUALS(indices[i], v[0], TEST_LOCATION);
+      DALI_TEST_EQUALS(indices[i + 1], v[1], TEST_LOCATION);
+      DALI_TEST_EQUALS(indices[i + 2], v[2], TEST_LOCATION);
+    }
+  };
+
+  std::vector<Vector3> normals;
+  normals.resize(COLLIDER_0_VERTS.size());
+  std::fill(normals.begin(), normals.end(), Vector3(0.0, 1.0, 0.0));
+  fn(COLLIDER_0_VERTS, COLLIDER_0_IDX);
+
+  normals.resize(COLLIDER_1_VERTS.size());
+  std::fill(normals.begin(), normals.end(), Vector3(0.0, 1.0, 0.0));
+  fn(COLLIDER_1_VERTS, COLLIDER_1_IDX);
+
+  normals.resize(COLLIDER_2_VERTS.size());
+  std::fill(normals.begin(), normals.end(), Vector3(0.0, 1.0, 0.0));
+  fn(COLLIDER_2_VERTS, COLLIDER_2_IDX);
+  END_TEST;
+}
+
+int UtcDaliNavigationMeshGetBinaryTest(void)
+{
+  tet_infoline("UtcDaliNavigationMeshGetBinaryTest: Creates meshes dynamically, reloads binaries and compares");
+
+  // Test 10 collider meshes
+  for(auto i = 0u; i < 10; ++i)
+  {
+    auto colliderMesh = NavigationMeshFactory::CreateFromBuffer(GetTestColliderMesh(i));
+    auto binary       = NavigationMeshFactory::GetMeshBinary(*colliderMesh);
+    DALI_TEST_EQUALS(binary.size() > 0, true, TEST_LOCATION);
+
+    auto colliderMesh2 = NavigationMeshFactory::CreateFromBuffer(binary);
+
+    DALI_TEST_EQUALS(colliderMesh->GetFaceCount(), colliderMesh2->GetFaceCount(), TEST_LOCATION);
+    DALI_TEST_EQUALS(colliderMesh->GetVertexCount(), colliderMesh2->GetVertexCount(), TEST_LOCATION);
+    DALI_TEST_EQUALS(colliderMesh->GetEdgeCount(), colliderMesh2->GetEdgeCount(), TEST_LOCATION);
+
+    // test vertices
+    for(auto idx = 0u; idx < colliderMesh->GetFaceCount(); ++idx)
+    {
+      auto v0  = colliderMesh->GetVertex(idx);
+      auto v1  = colliderMesh2->GetVertex(idx);
+      auto co0 = Vector3(v0->coordinates);
+      auto co1 = Vector3(v1->coordinates);
+      DALI_TEST_EQUALS(co0, co1, std::numeric_limits<float>::epsilon(), TEST_LOCATION);
+    }
+
+    // test face
+    for(auto idx = 0u; idx < colliderMesh->GetFaceCount(); ++idx)
+    {
+      auto  f0  = colliderMesh->GetFace(idx);
+      auto  f1  = colliderMesh2->GetFace(idx);
+      auto& vi0 = f0->vertex;
+      auto& vi1 = f1->vertex;
+      DALI_TEST_EQUALS(vi0[0], vi1[0], TEST_LOCATION);
+      DALI_TEST_EQUALS(vi0[1], vi1[1], TEST_LOCATION);
+      DALI_TEST_EQUALS(vi0[2], vi1[2], TEST_LOCATION);
+    }
+  }
+
+  END_TEST;
+}
+
+int UtcDaliColliderMeshModelNodeSetup(void)
+{
+  tet_infoline("UtcDaliColliderMeshModelNodeSetup: Test different variants of setting up a collider mesh to the node");
+
+  ToolkitTestApplication   application;
+  Dali::Scene3D::ModelNode node  = Dali::Scene3D::ModelNode::New();
+  Dali::Scene3D::Model     model = Dali::Scene3D::Model::New();
+  model.AddModelNode(node);
+
+  application.GetWindow().Add(model);
+  application.SendNotification();
+  application.Render();
+
+  auto colliderMesh = NavigationMeshFactory::CreateFromBuffer(GetTestColliderMesh(0));
+
+  // Redundant setup test
+  DALI_TEST_EQUALS(node.HasColliderMesh(), false, TEST_LOCATION);
+  node.SetColliderMesh(nullptr);
+  DALI_TEST_EQUALS(node.HasColliderMesh(), false, TEST_LOCATION);
+  node.SetColliderMesh(std::move(colliderMesh));
+  DALI_TEST_EQUALS(node.HasColliderMesh(), true, TEST_LOCATION);
+
+  // Reset collider mesh
+  node.SetColliderMesh(nullptr);
+  DALI_TEST_EQUALS(node.HasColliderMesh(), false, TEST_LOCATION);
+
+  auto colliderMesh2 = NavigationMeshFactory::CreateFromBuffer(GetTestColliderMesh(1));
+  auto colliderMesh3 = NavigationMeshFactory::CreateFromBuffer(GetTestColliderMesh(2));
+
+  const auto& cm2 = *colliderMesh2;
+  const auto& cm3 = *colliderMesh3;
+
+  node.SetColliderMesh(std::move(colliderMesh2));
+  DALI_TEST_EQUALS(node.HasColliderMesh(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(&node.GetColliderMesh(), &cm2, TEST_LOCATION);
+  node.SetColliderMesh(std::move(colliderMesh3));
+  DALI_TEST_EQUALS(node.HasColliderMesh(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(&node.GetColliderMesh(), &cm3, TEST_LOCATION);
+
+  node.SetColliderMesh(nullptr);
+  DALI_TEST_EQUALS(node.HasColliderMesh(), false, TEST_LOCATION);
+
+  END_TEST;
+}
diff --git a/dali-scene3d/internal/algorithm/navigation-mesh-impl.cpp b/dali-scene3d/internal/algorithm/navigation-mesh-impl.cpp
new file mode 100644 (file)
index 0000000..81b999d
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ * 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-scene3d/internal/algorithm/navigation-mesh-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/debug.h>
+
+#include <algorithm>
+#include <filesystem>
+
+using Dali::Vector3;
+
+namespace Dali::Scene3D::Internal::Algorithm
+{
+using Poly   = Dali::Scene3D::Algorithm::NavigationMesh::Face;
+using Edge   = Dali::Scene3D::Algorithm::NavigationMesh::Edge;
+using Vertex = Dali::Scene3D::Algorithm::NavigationMesh::Vertex;
+
+/**
+ * Helper function calculating intersection point between triangle and ray
+ */
+static bool RayTriangleIntersect(
+  const Vector3& origin, const Vector3& direction, const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2, const Vector3& normal, float& outDistance, Vector3& position)
+{
+  auto N = normal;
+  N.Normalize();
+  // Step 1: finding P
+
+  // check if ray and plane are parallel ?
+  float NdotRayDirection = N.Dot(direction);
+  if(Dali::Equals(NdotRayDirection, 0.0f))
+  {
+    return false; // they are parallel so they don'outDistance intersect !
+  }
+
+  // compute d parameter using equation 2
+  float d = -N.Dot(vertex0);
+
+  // compute outDistance (equation 3)
+  outDistance = -(N.Dot(origin) + d) / NdotRayDirection;
+
+  // check if the triangle is in behind the ray
+  if(outDistance < 0) return false; // the triangle is behind
+
+  // compute the intersection point using equation 1
+  auto P = origin + (direction * outDistance);
+
+  position = P;
+
+  auto edge0 = vertex1 - vertex0;
+  auto edge1 = vertex2 - vertex1;
+  auto edge2 = vertex0 - vertex2;
+  auto C0    = P - vertex0;
+  auto C1    = P - vertex1;
+  auto C2    = P - vertex2;
+
+  auto r0 = N.Dot(edge0.Cross(C0));
+  auto r1 = N.Dot(edge1.Cross(C1));
+  auto r2 = N.Dot(edge2.Cross(C2));
+  if(r0 > 0 &&
+     r1 > 0 &&
+     r2 > 0) return true; // P is inside the triangle
+
+  return false; // this ray hits the triangle
+}
+
+NavigationMesh::NavigationMesh(const std::vector<uint8_t>& buffer)
+{
+  mBuffer.resize(buffer.size());
+  std::copy(buffer.begin(), buffer.end(), mBuffer.begin());
+
+  // Setup header from the buffer
+  mHeader      = *reinterpret_cast<NavigationMeshHeader_V10*>(mBuffer.data());
+  mCurrentFace = Scene3D::Algorithm::NavigationMesh::NULL_FACE;
+}
+
+[[nodiscard]] uint32_t NavigationMesh::GetFaceCount() const
+{
+  return mHeader.polyCount;
+}
+
+[[nodiscard]] uint32_t NavigationMesh::GetEdgeCount() const
+{
+  return mHeader.edgeCount;
+}
+
+[[nodiscard]] uint32_t NavigationMesh::GetVertexCount() const
+{
+  return mHeader.vertexCount;
+}
+
+bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
+{
+  if(faceIndex == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
+  {
+    faceIndex = mCurrentFace;
+  }
+
+  // No current face, do full check
+  if(mCurrentFace == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
+  {
+    return FindFloor(position, outPosition);
+  }
+
+  NavigationRay ray;
+  ray.origin = PointSceneToLocal(Dali::Vector3(position)); // origin is equal position
+
+  // Ray direction matches gravity direction
+  ray.direction = Vector3(mHeader.gravityVector);
+
+  IntersectResult result = NavigationRayFaceIntersection(ray, *GetFace(faceIndex));
+  if(result.result)
+  {
+    outPosition = PointLocalToScene(result.point);
+    return true;
+  }
+  else
+  {
+    if(dontCheckNeighbours)
+    {
+      return false;
+    }
+
+    // Test neighbouring by edges
+    const auto& poly = GetFace(faceIndex);
+
+    // collect faces sharing edges
+    std::vector<FaceIndex> neighbourFaces;
+
+    for(auto edgeIndex : poly->edge)
+    {
+      const auto& edge = *GetEdge(edgeIndex);
+
+      // store faces
+      for(auto edgeFaceIndex : edge.face)
+      {
+        if(edgeFaceIndex != ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE && edgeFaceIndex != faceIndex)
+        {
+          neighbourFaces.emplace_back(edgeFaceIndex);
+        }
+      }
+    }
+
+    if(neighbourFaces.empty())
+    {
+      return false;
+    }
+
+    for(const auto& neighbourFaceIndex : neighbourFaces)
+    {
+      if(FindFloorForFace(position, neighbourFaceIndex, true, outPosition))
+      {
+        mCurrentFace = neighbourFaceIndex;
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+/**
+ * Drops observer onto the floor
+ *
+ * When dropping observer, the nearest floor face is found
+ */
+bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition)
+{
+  FaceIndex faceIndex = 0u;
+  return FindFloor(position, outPosition, faceIndex);
+}
+
+bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex)
+{
+  [[maybe_unused]] auto newPos = PointSceneToLocal(Dali::Vector3(position));
+
+  NavigationRay ray;
+
+  ray.origin = PointSceneToLocal(Dali::Vector3(position)); // origin is equal position
+
+  // Ray direction matches gravity direction
+  ray.direction = Vector3(mHeader.gravityVector);
+
+  const auto                   POLY_COUNT = GetFaceCount();
+  std::vector<IntersectResult> results;
+  for(auto faceIndex = 0u; faceIndex < POLY_COUNT; ++faceIndex)
+  {
+    auto result = NavigationRayFaceIntersection(ray, *GetFace(faceIndex));
+    if(result.result)
+    {
+      result.faceIndex = faceIndex;
+      results.emplace_back(result);
+    }
+  }
+
+  // find minimal distance to the floor and return that position and distance
+  if(results.empty())
+  {
+    return false;
+  }
+
+  std::sort(results.begin(), results.end(), [](const IntersectResult& lhs, const IntersectResult& rhs) { return lhs.distance < rhs.distance; });
+
+  outPosition  = PointLocalToScene(results.front().point);
+  faceIndex    = results.front().faceIndex;
+  mCurrentFace = results.front().faceIndex;
+
+  return true;
+}
+
+const Poly* NavigationMesh::GetFace(FaceIndex index) const
+{
+  auto* basePtr = reinterpret_cast<const Poly*>(mBuffer.data() + mHeader.dataOffset + mHeader.polyDataOffset);
+  return &basePtr[index];
+}
+
+const Edge* NavigationMesh::GetEdge(EdgeIndex index) const
+{
+  auto* basePtr = reinterpret_cast<const Edge*>(mBuffer.data() + mHeader.dataOffset + mHeader.edgeDataOffset);
+  return &basePtr[index];
+}
+
+const Vertex* NavigationMesh::GetVertex(VertexIndex index) const
+{
+  auto* basePtr = reinterpret_cast<const Vertex*>(mBuffer.data() + mHeader.dataOffset + mHeader.vertexDataOffset);
+  return &basePtr[index];
+}
+
+NavigationMesh::IntersectResult NavigationMesh::NavigationRayFaceIntersection(NavigationRay& ray, const Face& face) const
+{
+  auto vi0 = *GetVertex(face.vertex[0]);
+  auto vi1 = *GetVertex(face.vertex[1]);
+  auto vi2 = *GetVertex(face.vertex[2]);
+
+  IntersectResult result{Vector3::ZERO, 0.0f, 0u, false};
+
+  result.result = RayTriangleIntersect(ray.origin, ray.direction, Vector3(vi0.x, vi0.y, vi0.z), Vector3(vi1.x, vi1.y, vi1.z), Vector3(vi2.x, vi2.y, vi2.z), Vector3(face.normal), result.distance, result.point);
+  return result;
+}
+
+NavigationMesh::IntersectResult NavigationMesh::RayCastIntersect(NavigationRay& rayOrig) const
+{
+  auto                       faceCount = GetFaceCount();
+  std::list<IntersectResult> results;
+
+  NavigationRay ray;
+
+  ray.origin = PointSceneToLocal(rayOrig.origin); // origin is equal position
+
+  // Ray direction matches gravity direction
+  ray.direction = PointSceneToLocal(rayOrig.origin + rayOrig.direction) - ray.origin;
+  ray.direction.Normalize();
+  for(auto i = 0u; i < faceCount; ++i)
+  {
+    auto result = NavigationRayFaceIntersection(ray, *GetFace(i));
+    if(result.result)
+    {
+      result.faceIndex = i;
+      if(results.empty())
+      {
+        results.push_back(result);
+      }
+      else
+      {
+        for(auto it = results.begin(); it != results.end(); ++it)
+        {
+          if((*it).distance > result.distance)
+          {
+            results.insert(it, result);
+            break;
+          }
+        }
+      }
+    }
+  }
+  if(!results.empty())
+  {
+    return results.front();
+  }
+  else
+  {
+    return IntersectResult{Vector3::ZERO, 0.0f, 0u, false};
+  }
+}
+
+void NavigationMesh::SetTransform(const Dali::Matrix& transform)
+{
+  mTransform        = transform;
+  mTransformInverse = mTransform;
+  mTransformInverse.Invert();
+}
+
+Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point) const
+{
+  Dali::Vector4 invNewPos = mTransformInverse * Dali::Vector4(point.x, point.y, point.z, 1.0f);
+
+  return Dali::Vector3(invNewPos.AsFloat());
+}
+
+Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point) const
+{
+  // Transform point into scene transform space
+  Dali::Vector4 newPos = mTransform * Dali::Vector4(point.x, point.y, point.z, 1.0f);
+
+  return Dali::Vector3(newPos.AsFloat());
+}
+
+Dali::Vector3 NavigationMesh::GetGravityVector() const
+{
+  return Dali::Vector3(mHeader.gravityVector);
+}
+
+} // namespace Dali::Scene3D::Internal::Algorithm
\ No newline at end of file
diff --git a/dali-scene3d/internal/algorithm/navigation-mesh-impl.h b/dali-scene3d/internal/algorithm/navigation-mesh-impl.h
new file mode 100644 (file)
index 0000000..6a82787
--- /dev/null
@@ -0,0 +1,197 @@
+#ifndef DALI_SCENE3D_INTERNAL_NAVIGATION_MESH_H
+#define DALI_SCENE3D_INTERNAL_NAVIGATION_MESH_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 EXTERNAL
+#include <dali/public-api/actors/actor.h>
+#include <dali/public-api/common/vector-wrapper.h>
+#include <dali/public-api/math/matrix.h>
+#include <dali/public-api/math/vector3.h>
+#include <dali/public-api/math/vector4.h>
+
+#include <cinttypes>
+#include <cstdio>
+#include <mutex>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/algorithm/navigation-mesh-header.h>
+#include <dali-scene3d/public-api/algorithm/navigation-mesh.h>
+#include <dali-scene3d/public-api/algorithm/path-finder.h>
+
+namespace Dali::Scene3D::Loader
+{
+class NavigationMeshFactory;
+}
+
+namespace Dali::Scene3D::Internal::Algorithm
+{
+// Internal Navigation ray structure
+struct NavigationRay
+{
+  Dali::Vector3 origin;    // Origin of ray
+  Dali::Vector3 direction; // Direction of ray
+};
+
+// Make each to change each index value's type here.
+using VertexIndex = Dali::Scene3D::Algorithm::VertexIndex;
+using EdgeIndex   = Dali::Scene3D::Algorithm::EdgeIndex;
+using FaceIndex   = Dali::Scene3D::Algorithm::FaceIndex;
+
+/**
+ * @class NavigationMesh
+ */
+class NavigationMesh
+{
+public:
+  using Face   = Dali::Scene3D::Algorithm::NavigationMesh::Face;
+  using Edge   = Dali::Scene3D::Algorithm::NavigationMesh::Edge;
+  using Vertex = Dali::Scene3D::Algorithm::NavigationMesh::Vertex;
+
+private:
+  friend class Scene3D::Loader::NavigationMeshFactory;
+
+  /**
+   * Constructor
+   */
+  NavigationMesh(const std::vector<uint8_t>& buffer);
+
+public:
+  /**
+   * Destructor
+   */
+  ~NavigationMesh() = default;
+
+  /**
+   * Result of Ray/Polygon intersection
+   */
+  struct IntersectResult
+  {
+    Dali::Vector3 point;
+    float         distance;
+    FaceIndex     faceIndex;
+    bool          result;
+  };
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetFaceCount()
+   */
+  [[nodiscard]] uint32_t GetFaceCount() const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetEdgeCount()
+   */
+  [[nodiscard]] uint32_t GetEdgeCount() const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetVertexCount()
+   */
+  [[nodiscard]] uint32_t GetVertexCount() const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::FindFloorForFace()
+   */
+  bool FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition);
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::FindFloor()
+   */
+  bool FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition);
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::FindFloor()
+   */
+  bool FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex);
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetFace()
+   */
+  [[nodiscard]] const Face* GetFace(FaceIndex index) const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetEdge()
+   */
+  [[nodiscard]] const Edge* GetEdge(EdgeIndex index) const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetVertex()
+   */
+  [[nodiscard]] const Vertex* GetVertex(VertexIndex index) const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::SetSceneTransform()
+   */
+  void SetTransform(const Dali::Matrix& transform);
+
+  /**
+   * Tests intersection between navigation ray and face
+   */
+  IntersectResult NavigationRayFaceIntersection(NavigationRay& ray, const Face& face) const;
+
+  /**
+   * @brief Test ray against the mesh and returns intersection result
+   * @param[in] rayOrig Input ray to test collision
+   *
+   * @return Valid IntersectResult structure
+   */
+  IntersectResult RayCastIntersect(NavigationRay& rayOrig) const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::PointSceneToLocal()
+   */
+  Dali::Vector3 PointSceneToLocal(const Dali::Vector3& point) const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::PointLocalToScene()
+   */
+  Dali::Vector3 PointLocalToScene(const Dali::Vector3& point) const;
+
+  /**
+   * @copydoc Dali::Scene3D::Algorithm::NavigationMesh::GetGravityVector()
+   */
+  [[nodiscard]] Dali::Vector3 GetGravityVector() const;
+
+  /**
+   * @brief Returns binary data of the mesh
+   * @return Reference to the binary buffer
+   */
+  [[nodiscard]] const std::vector<uint8_t>& GetData() const
+  {
+    return mBuffer;
+  }
+
+private:
+  std::vector<uint8_t>     mBuffer;           //< Data buffer
+  NavigationMeshHeader_V10 mHeader;           //< Navigation mesh header
+  FaceIndex                mCurrentFace;      //< Current face (last floor position)
+  Dali::Matrix             mTransform;        //< Transform matrix
+  Dali::Matrix             mTransformInverse; //< Inverse of the transform matrix
+};
+
+inline Internal::Algorithm::NavigationMesh& GetImplementation(Dali::Scene3D::Algorithm::NavigationMesh& navigationMesh)
+{
+  return *navigationMesh.mImpl;
+}
+
+inline const Internal::Algorithm::NavigationMesh& GetImplementation(const Dali::Scene3D::Algorithm::NavigationMesh& navigationMesh)
+{
+  return *navigationMesh.mImpl;
+}
+
+} // namespace Dali::Scene3D::Internal::Algorithm
+
+#endif // DALI_SCENE3D_INTERNAL_NAVIGATION_MESH_H
index a7dde17..df21768 100644 (file)
@@ -33,6 +33,7 @@
 // INTERNAL INCLUDES
 #include <dali-scene3d/internal/common/model-cache-manager.h>
 #include <dali-scene3d/internal/controls/scene-view/scene-view-impl.h>
+#include <dali-scene3d/internal/event/collider-mesh-processor.h>
 #include <dali-scene3d/internal/light/light-impl.h>
 #include <dali-scene3d/internal/model-components/model-node-impl.h>
 #include <dali-scene3d/public-api/controls/model/model.h>
@@ -44,7 +45,7 @@
 #include <dali-scene3d/public-api/loader/scene-definition.h>
 #include <dali-scene3d/public-api/loader/shader-manager.h>
 #include <dali-scene3d/public-api/model-motion/motion-index/blend-shape-index.h>
-
+#include <dali-toolkit/public-api/controls/control-impl.h>
 using namespace Dali;
 
 namespace Dali
@@ -311,14 +312,48 @@ void Model::AddModelNode(Scene3D::ModelNode modelNode)
     UpdateImageBasedLightScaleFactor();
   }
 
+  GetImplementation(modelNode).SetRootModel(*this);
+
+  // If model has a collider mesh set, add it to the container
+  if(modelNode.HasColliderMesh())
+  {
+    Scene3D::ColliderMeshProcessor::Get().ColliderMeshChanged(Scene3D::Model::DownCast(Self()));
+  }
+
   if(Self().GetProperty<bool>(Dali::Actor::Property::CONNECTED_TO_SCENE))
   {
     NotifyResourceReady();
   }
 }
 
+void Model::RegisterColliderMesh(Scene3D::ModelNode& modelNode, const Dali::Scene3D::Algorithm::ColliderMesh& mesh)
+{
+  mColliderMeshes[modelNode.GetProperty<int>(Actor::Property::ID)] = modelNode;
+
+  // Add processor
+  Scene3D::ColliderMeshProcessor::Get().ColliderMeshChanged(Scene3D::Model::DownCast(Self()));
+}
+
+void Model::RemoveColliderMesh(Scene3D::ModelNode& node)
+{
+  auto id   = node.GetProperty<int>(Actor::Property::ID);
+  auto iter = std::find_if(mColliderMeshes.begin(), mColliderMeshes.end(), [id](auto& item) {
+    return item.first == id;
+  });
+  if(iter != mColliderMeshes.end())
+  {
+    mColliderMeshes.erase(iter);
+  }
+}
+
 void Model::RemoveModelNode(Scene3D::ModelNode modelNode)
 {
+  // remove collider mesh from the list if node is being removed
+  if(modelNode.HasColliderMesh())
+  {
+    RemoveColliderMesh(modelNode);
+  }
+
   if(mModelRoot)
   {
     UpdateShaderRecursively(modelNode, nullptr);
index b740da4..ff9ba0c 100644 (file)
 #include <dali/public-api/object/property-notification.h>
 #include <dali/public-api/object/weak-handle.h>
 #include <dali/public-api/rendering/texture.h>
+#include <unordered_map>
 
 // INTERNAL INCLUDES
 #include <dali-scene3d/internal/common/environment-map-load-task.h>
 #include <dali-scene3d/internal/common/light-observer.h>
 #include <dali-scene3d/internal/common/model-load-task.h>
+#include <dali-scene3d/internal/model-components/model-node-impl.h>
 #include <dali-scene3d/public-api/controls/model/model.h>
 #include <dali-scene3d/public-api/controls/scene-view/scene-view.h>
 #include <dali-scene3d/public-api/light/light.h>
@@ -56,6 +58,11 @@ public:
   using CameraData             = Loader::CameraParameters;
   using BlendShapeModelNodeMap = std::map<std::string, std::vector<Scene3D::ModelNode>>;
 
+  // ColliderMeshContainer doesn't hold actual collider meshes
+  // but pairs unique ModelNode id with ModelNode for lookup purposes.
+  // All model nodes in the container have collider mesh attached.
+  using ColliderMeshContainer = std::unordered_map<int, Scene3D::ModelNode>;
+
   /**
    * @copydoc Model::New()
    */
@@ -166,6 +173,53 @@ public:
    */
   void SetMotionData(Scene3D::MotionData motionData);
 
+  /**
+   * @copydoc Scene3D::Model::MeshHitSignal()
+   */
+  Scene3D::Model::MeshHitSignalType& MeshHitSignal()
+  {
+    return mMeshHitSignal;
+  }
+
+  /**
+   * @brief Emits MeshHitSignal
+   * @param[in] modelNode ModelNode that has been hit
+   * @return Result of hit handling.
+   */
+  bool EmitMeshHitSignal(Scene3D::ModelNode modelNode)
+  {
+    bool retVal = false;
+    if(!mMeshHitSignal.Empty())
+    {
+      Scene3D::Model handle(GetOwner());
+      retVal = mMeshHitSignal.Emit(handle, modelNode);
+    }
+    return retVal;
+  }
+
+  /**
+   * @brief Returns collider mesh container
+   * @return Returns valid container
+   */
+  const ColliderMeshContainer& GetNodeColliderMeshContainer() const
+  {
+    return mColliderMeshes;
+  }
+
+  /**
+   * @brief Registers child node with collidier mesh
+   *
+   * @param[in] node ModelNode to register
+   * @param[in] mesh Collider mesh to associate with model node
+   */
+  void RegisterColliderMesh(Scene3D::ModelNode& node, const Dali::Scene3D::Algorithm::ColliderMesh& mesh);
+
+  /**
+   * @brief Removes node/collider mesh from the register
+   * @param[in] node Child node to remove from the register
+   */
+  void RemoveColliderMesh(Scene3D::ModelNode& node);
+
 protected:
   /**
    * @brief Constructs a new Model.
@@ -356,8 +410,13 @@ private:
   WeakHandle<Scene3D::SceneView> mParentSceneView;
   Dali::PropertyNotification     mSizeNotification;
 
+  // Signals
+  Scene3D::Model::MeshHitSignalType mMeshHitSignal;
+
   Dali::Scene3D::Loader::ShaderManagerPtr mShaderManager;
 
+  ColliderMeshContainer mColliderMeshes;
+
   // List of ModelNode for name of blend shape.
   BlendShapeModelNodeMap mBlendShapeModelNodeMap;
 
diff --git a/dali-scene3d/internal/event/collider-mesh-processor-impl.cpp b/dali-scene3d/internal/event/collider-mesh-processor-impl.cpp
new file mode 100644 (file)
index 0000000..d8ce462
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * 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-scene3d/internal/event/collider-mesh-processor-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/actors/actor-devel.h>
+#include <dali/devel-api/events/hit-test-algorithm.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
+#include <dali/public-api/events/touch-event.h>
+#include <dali/public-api/render-tasks/render-task-list.h>
+#include <algorithm>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/controls/model/model-impl.h>
+#include <dali-scene3d/internal/controls/scene-view/scene-view-impl.h>
+#include <dali-scene3d/public-api/controls/scene-view/scene-view.h>
+
+namespace Dali::Scene3D::Internal
+{
+namespace
+{
+struct ColliderMeshData
+{
+  Scene3D::Model                                model;
+  Scene3D::ModelNode                            modelNode;
+  const Dali::Scene3D::Algorithm::ColliderMesh& colliderMesh;
+  Matrix                                        worldMatrix{false};
+};
+using ColliderMeshDataContainer = std::vector<ColliderMeshData>;
+
+void IterateThroughChildren(Actor actor, ColliderMeshDataContainer& meshData)
+{
+  if(actor)
+  {
+    const auto childCount = actor.GetChildCount();
+    for(auto i = 0u; i < childCount; ++i)
+    {
+      Actor          child = actor.GetChildAt(i);
+      Scene3D::Model model = Scene3D::Model::DownCast(child);
+      if(model)
+      {
+        const Model::ColliderMeshContainer& colliderMeshes = GetImpl(model).GetNodeColliderMeshContainer();
+        for(const auto& colliderMeshItem : colliderMeshes)
+        {
+          // If actor name is empty, then assume the mesh if for the model itself
+          [[maybe_unused]] int     actorId   = colliderMeshItem.first;
+          Dali::Scene3D::ModelNode modelNode = colliderMeshItem.second;
+          if(modelNode)
+          {
+            meshData.push_back({model, modelNode, GetImplementation(modelNode).GetColliderMesh(), DevelActor::GetWorldTransform(modelNode)});
+          }
+        }
+      }
+      IterateThroughChildren(child, meshData);
+    }
+  }
+}
+
+class SceneViewTouchHandler
+{
+public:
+  bool operator()(Actor actor, const TouchEvent& touchEvent)
+  {
+    Scene3D::SceneView sceneView = Scene3D::SceneView::DownCast(actor);
+    bool               retVal(false);
+    if(sceneView)
+    {
+      // Get nav-mesh information for the children
+      std::vector<ColliderMeshData> meshData;
+      IterateThroughChildren(sceneView, meshData);
+
+      auto renderTask  = touchEvent.GetRenderTask();
+      auto cameraActor = renderTask.GetCameraActor();
+
+      const auto& result = touchEvent.GetScreenPosition(0);
+      float       x = 0.0f, y = 0.0f;
+      Vector3     origin;
+      Vector3     direction;
+      cameraActor.ScreenToLocal(x, y, result.x, result.y);
+
+      auto                  list      = Stage::GetCurrent().GetRenderTaskList();
+      [[maybe_unused]] auto taskCount = list.GetTaskCount();
+      renderTask                      = list.GetTask(list.GetTaskCount() - 1);
+
+      if(HitTestAlgorithm::BuildPickingRay(renderTask, result, origin, direction))
+      {
+        for(auto& mesh : meshData)
+        {
+          // Set transform for the collider mesh
+          const_cast<Dali::Scene3D::Algorithm::ColliderMesh&>(mesh.colliderMesh).SetSceneTransform(mesh.worldMatrix);
+          auto res = mesh.colliderMesh.RayFaceIntersect(origin, direction);
+          if(res != Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
+          {
+            Scene3D::Model            model     = mesh.model;
+            Scene3D::Internal::Model& modelImpl = GetImpl(model);
+            retVal                              = modelImpl.EmitMeshHitSignal(mesh.modelNode);
+            break;
+          }
+        }
+      }
+    }
+    return retVal;
+  }
+};
+
+} // unnamed namespace
+
+ColliderMeshProcessor::ColliderMeshProcessor()
+{
+  Adaptor::Get().RegisterProcessor(*this, true);
+}
+
+ColliderMeshProcessor::~ColliderMeshProcessor()
+{
+  if(Adaptor::IsAvailable())
+  {
+    Adaptor::Get().UnregisterProcessor(*this, true);
+  }
+}
+
+void ColliderMeshProcessor::ColliderMeshChanged(Scene3D::Model model)
+{
+  if(model.GetProperty<bool>(Actor::Property::CONNECTED_TO_SCENE))
+  {
+    AddSceneViewParentToProcessingQueue(model);
+  }
+  else
+  {
+    // TODO: Check if signal already connected
+    model.OnSceneSignal().Connect(this, &ColliderMeshProcessor::ModelOnScene);
+  }
+}
+
+void ColliderMeshProcessor::ModelOnScene(Actor actor)
+{
+  Scene3D::Model model = Scene3D::Model::DownCast(actor);
+  if(model)
+  {
+    AddSceneViewParentToProcessingQueue(model);
+  }
+  model.OnSceneSignal().Disconnect(this, &ColliderMeshProcessor::ModelOnScene);
+}
+
+void ColliderMeshProcessor::AddSceneViewParentToProcessingQueue(Scene3D::Model model)
+{
+  Actor actor = model;
+  do
+  {
+    actor = actor.GetParent();
+    Scene3D::SceneView sceneView(Scene3D::SceneView::DownCast(actor));
+    if(sceneView)
+    {
+      mSceneViewsToProcess.push_back(sceneView);
+      break;
+    }
+  } while(actor);
+}
+
+void ColliderMeshProcessor::Process(bool /* postProcess */)
+{
+  // Remove any duplicates
+  std::sort(mSceneViewsToProcess.begin(), mSceneViewsToProcess.end());
+  mSceneViewsToProcess.erase(std::unique(mSceneViewsToProcess.begin(), mSceneViewsToProcess.end()), mSceneViewsToProcess.end());
+
+  for(auto sceneView : mSceneViewsToProcess)
+  {
+    std::vector<ColliderMeshData> meshData;
+    IterateThroughChildren(sceneView, meshData);
+
+    if(meshData.size())
+    {
+      // TODO: Get SceneView Camera parameters and calculate bounding box
+      // for now, it's a pass-thru algorthm
+      if(std::find(mConnectedSceneViews.begin(), mConnectedSceneViews.end(), sceneView) == mConnectedSceneViews.end())
+      {
+        mConnectedSceneViews.push_back(sceneView);
+        // TODO: Consider passing in camera parameters and meshData into SceneViewTouchHandler
+        sceneView.TouchedSignal().Connect(this, SceneViewTouchHandler());
+      }
+    }
+  }
+  mSceneViewsToProcess.clear();
+}
+
+} // namespace Dali::Scene3D::Internal
diff --git a/dali-scene3d/internal/event/collider-mesh-processor-impl.h b/dali-scene3d/internal/event/collider-mesh-processor-impl.h
new file mode 100644 (file)
index 0000000..45c7b18
--- /dev/null
@@ -0,0 +1,77 @@
+#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 <dali/integration-api/processor-interface.h>
+#include <dali/public-api/common/vector-wrapper.h>
+#include <dali/public-api/object/base-handle.h>
+#include <dali/public-api/object/base-object.h>
+#include <dali/public-api/signals/connection-tracker.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/event/collider-mesh-processor.h>
+#include <dali-scene3d/public-api/controls/model/model.h>
+#include <dali-scene3d/public-api/controls/scene-view/scene-view.h>
+
+namespace Dali::Scene3D
+{
+namespace Internal
+{
+class ColliderMeshProcessor : public BaseObject, public Dali::ConnectionTracker, public Integration::Processor
+{
+public:
+  ColliderMeshProcessor();
+
+  ~ColliderMeshProcessor();
+
+  void ColliderMeshChanged(Scene3D::Model model);
+
+private:
+  void ModelOnScene(Actor actor);
+
+  void Process(bool /*postProcessor*/);
+
+  void AddSceneViewParentToProcessingQueue(Scene3D::Model model);
+
+private:
+  std::vector<Scene3D::SceneView> mSceneViewsToProcess;
+  std::vector<Scene3D::SceneView> mConnectedSceneViews;
+};
+
+} // namespace Internal
+
+inline Internal::ColliderMeshProcessor& GetImpl(ColliderMeshProcessor& obj)
+{
+  DALI_ASSERT_ALWAYS(obj);
+
+  Dali::BaseObject& handle = obj.GetBaseObject();
+
+  return static_cast<Internal::ColliderMeshProcessor&>(handle);
+}
+
+inline const Internal::ColliderMeshProcessor& GetImpl(const ColliderMeshProcessor& obj)
+{
+  DALI_ASSERT_ALWAYS(obj);
+
+  const Dali::BaseObject& handle = obj.GetBaseObject();
+
+  return static_cast<const Internal::ColliderMeshProcessor&>(handle);
+}
+
+} // namespace Dali::Scene3D
diff --git a/dali-scene3d/internal/event/collider-mesh-processor.cpp b/dali-scene3d/internal/event/collider-mesh-processor.cpp
new file mode 100644 (file)
index 0000000..9f0ca0d
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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-scene3d/internal/event/collider-mesh-processor.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/common/singleton-service.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/event/collider-mesh-processor-impl.h>
+
+namespace Dali::Scene3D
+{
+ColliderMeshProcessor ColliderMeshProcessor::Get()
+{
+  ColliderMeshProcessor processor;
+
+  // Check whether the processor has already created
+  SingletonService singletonService(SingletonService::Get());
+  if(singletonService)
+  {
+    Dali::BaseHandle handle = singletonService.GetSingleton(typeid(ColliderMeshProcessor));
+    if(handle)
+    {
+      // If so, downcast the handle of singleton to focus manager
+      processor = ColliderMeshProcessor(dynamic_cast<Internal::ColliderMeshProcessor*>(handle.GetObjectPtr()));
+    }
+
+    if(!processor)
+    {
+      // If not, create the focus manager and register it as a singleton
+      processor = ColliderMeshProcessor(new Internal::ColliderMeshProcessor());
+      singletonService.Register(typeid(processor), processor);
+    }
+  }
+
+  return processor;
+}
+
+void ColliderMeshProcessor::ColliderMeshChanged(Scene3D::Model model)
+{
+  GetImpl(*this).ColliderMeshChanged(model);
+}
+
+ColliderMeshProcessor::ColliderMeshProcessor(Internal::ColliderMeshProcessor* impl)
+: BaseHandle(impl)
+{
+}
+
+} // namespace Dali::Scene3D
diff --git a/dali-scene3d/internal/event/collider-mesh-processor.h b/dali-scene3d/internal/event/collider-mesh-processor.h
new file mode 100644 (file)
index 0000000..0d874ee
--- /dev/null
@@ -0,0 +1,47 @@
+#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 <dali/public-api/object/base-handle.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/public-api/controls/model/model.h>
+
+namespace Dali::Scene3D
+{
+namespace Internal
+{
+class ColliderMeshProcessor;
+}
+
+class ColliderMeshProcessor : public BaseHandle
+{
+public:
+  ColliderMeshProcessor()  = default;
+  ~ColliderMeshProcessor() = default;
+
+  static ColliderMeshProcessor Get();
+
+  void ColliderMeshChanged(Scene3D::Model model);
+
+private:
+  explicit ColliderMeshProcessor(Internal::ColliderMeshProcessor* impl);
+};
+
+} // namespace Dali::Scene3D
index 803a6af..3bd0085 100644 (file)
@@ -6,6 +6,8 @@ set(scene3d_src_files ${scene3d_src_files}
        ${scene3d_internal_dir}/common/model-load-task.cpp
        ${scene3d_internal_dir}/controls/model/model-impl.cpp
        ${scene3d_internal_dir}/controls/scene-view/scene-view-impl.cpp
+       ${scene3d_internal_dir}/event/collider-mesh-processor.cpp
+       ${scene3d_internal_dir}/event/collider-mesh-processor-impl.cpp
        ${scene3d_internal_dir}/light/light-impl.cpp
        ${scene3d_internal_dir}/loader/dli-loader-impl.cpp
        ${scene3d_internal_dir}/loader/gltf2-asset.cpp
index 8370795..a9fbb39 100644 (file)
@@ -24,6 +24,7 @@
 #include <dali/public-api/object/type-registry.h>
 
 // INTERNAL INCLUDES
+#include <dali-scene3d/internal/controls/model/model-impl.h>
 #include <dali-scene3d/internal/light/light-impl.h>
 #include <dali-scene3d/internal/model-components/model-primitive-impl.h>
 
@@ -405,6 +406,52 @@ void ModelNode::UpdateBoneMatrix(Scene3D::ModelPrimitive primitive)
   }
 }
 
+void ModelNode::SetColliderMesh(ColliderMeshUniquePtr&& colliderMesh)
+{
+  if(!colliderMesh && !mColliderMesh)
+  {
+    return;
+  }
+
+  if(!mParentModel) // find parent model if not set
+  {
+    auto parent = Self().GetParent();
+    while(parent)
+    {
+      auto modelHandle = Scene3D::Model::DownCast(parent);
+      if(modelHandle)
+      {
+        mParentModel = &GetImpl(modelHandle);
+        break;
+      }
+      parent = parent.GetParent();
+    }
+  }
+
+  // Resetting collider mesh if argument is nullptr
+  auto handle = Scene3D::ModelNode::DownCast(Self());
+  if(mParentModel)
+  {
+    if(mColliderMesh || colliderMesh == nullptr)
+    {
+      mParentModel->RemoveColliderMesh(handle);
+    }
+    mParentModel->RegisterColliderMesh(handle, *colliderMesh);
+  }
+
+  mColliderMesh = std::move(colliderMesh);
+}
+
+bool ModelNode::HasColliderMesh() const
+{
+  return mColliderMesh != nullptr;
+}
+
+const Scene3D::Algorithm::ColliderMesh& ModelNode::GetColliderMesh() const
+{
+  return *mColliderMesh;
+}
+
 } // namespace Internal
 
 } // namespace Scene3D
index ed17a45..c624deb 100644 (file)
 
 // INTERNAL INCLUDES
 #include <dali-scene3d/internal/model-components/model-primitive-modify-observer.h>
+#include <dali-scene3d/public-api/algorithm/navigation-mesh.h>
 #include <dali-scene3d/public-api/light/light.h>
 #include <dali-scene3d/public-api/loader/mesh-definition.h>
 #include <dali-scene3d/public-api/loader/shader-manager.h>
 #include <dali-scene3d/public-api/loader/skinning-details.h>
 #include <dali-scene3d/public-api/model-components/model-node.h>
 #include <dali-scene3d/public-api/model-components/model-primitive.h>
+#include "dali-scene3d/public-api/controls/model/model.h"
 
 namespace Dali
 {
@@ -45,6 +47,7 @@ namespace Scene3D
 
 namespace Internal
 {
+using ColliderMeshUniquePtr = std::unique_ptr<Dali::Scene3D::Algorithm::ColliderMesh>;
 /**
  * @brief This is the internal base class for custom node of model.
  *
@@ -285,6 +288,30 @@ public: // Public Method
    */
   void OnRendererCreated(Renderer renderer) override;
 
+  /**
+   * @copydoc Dali::Scene3D::ModelNode::SetColliderMesh()
+   */
+  void SetColliderMesh(ColliderMeshUniquePtr&& colliderMesh);
+
+  /**
+   * @copydoc Dali::Scene3D::ModelNode::HasColliderMesh()
+   */
+  [[nodiscard]] bool HasColliderMesh() const;
+
+  /**
+   * @copydoc Dali::Scene3D::ModelNode::GetColliderMesh()
+   */
+  [[nodiscard]] const Scene3D::Algorithm::ColliderMesh& GetColliderMesh() const;
+
+  /**
+  * @brief Sets a root model for the ModelNode
+  * @param[in] model Valid Model to set
+  */
+  void SetRootModel(Dali::Scene3D::Internal::Model& model)
+  {
+    mParentModel = &model;
+  }
+
 private:
   /**
    * @brief Updates the bone matrix for a ModelPrimitive.
@@ -293,7 +320,6 @@ private:
    */
   void UpdateBoneMatrix(Scene3D::ModelPrimitive primitive);
 
-private:
   /// @cond internal
 
   // Not copyable or movable
@@ -310,8 +336,14 @@ private:
   Dali::Texture                     mShadowMapTexture;
   Dali::Texture                     mSpecularTexture;
   Dali::Texture                     mDiffuseTexture;
-  float                             mIblScaleFactor{1.0f};
-  uint32_t                          mSpecularMipmapLevels{1u};
+
+  Internal::Model* mParentModel{nullptr};
+
+  // Collider mesh
+  std::unique_ptr<Dali::Scene3D::Algorithm::ColliderMesh> mColliderMesh;
+
+  float    mIblScaleFactor{1.0f};
+  uint32_t mSpecularMipmapLevels{1u};
   /// @endcond
 };
 
diff --git a/dali-scene3d/public-api/algorithm/navigation-mesh.cpp b/dali-scene3d/public-api/algorithm/navigation-mesh.cpp
new file mode 100644 (file)
index 0000000..4aff076
--- /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-scene3d/public-api/algorithm/navigation-mesh.h>
+
+// INTERNAL HEADERS
+#include <dali-scene3d/internal/algorithm/navigation-mesh-impl.h>
+
+using Dali::Vector3;
+
+namespace Dali::Scene3D::Algorithm
+{
+NavigationMesh::NavigationMesh(NavigationMeshImpl* impl)
+{
+  mImpl.reset(impl);
+}
+
+NavigationMesh::~NavigationMesh() = default;
+
+[[nodiscard]] uint32_t NavigationMesh::GetFaceCount() const
+{
+  return mImpl->GetFaceCount();
+}
+
+[[nodiscard]] uint32_t NavigationMesh::GetEdgeCount() const
+{
+  return mImpl->GetEdgeCount();
+}
+
+[[nodiscard]] uint32_t NavigationMesh::GetVertexCount() const
+{
+  return mImpl->GetVertexCount();
+}
+
+bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex)
+{
+  return mImpl->FindFloor(position, outPosition, faceIndex);
+}
+
+bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
+{
+  return mImpl->FindFloorForFace(position, faceIndex, dontCheckNeighbours, outPosition);
+}
+
+[[nodiscard]] const NavigationMesh::Face* NavigationMesh::GetFace(FaceIndex index) const
+{
+  return mImpl->GetFace(index);
+}
+
+[[nodiscard]] const NavigationMesh::Edge* NavigationMesh::GetEdge(EdgeIndex index) const
+{
+  return mImpl->GetEdge(index);
+}
+
+[[nodiscard]] const NavigationMesh::Vertex* NavigationMesh::GetVertex(VertexIndex index) const
+{
+  return mImpl->GetVertex(index);
+}
+
+void NavigationMesh::SetSceneTransform(const Dali::Matrix& transform)
+{
+  mImpl->SetTransform(transform);
+}
+
+Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point) const
+{
+  return mImpl->PointSceneToLocal(point);
+}
+
+Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point) const
+{
+  return mImpl->PointLocalToScene(point);
+}
+
+Dali::Vector3 NavigationMesh::GetGravityVector() const
+{
+  return mImpl->GetGravityVector();
+}
+
+FaceIndex NavigationMesh::RayFaceIntersect(const Vector3& origin, const Vector3& direction) const
+{
+  Internal::Algorithm::NavigationRay ray;
+  ray.origin    = origin;
+  ray.direction = direction;
+
+  auto result = mImpl->RayCastIntersect(ray);
+  if(result.result)
+  {
+    return result.faceIndex;
+  }
+  return NULL_FACE;
+}
+
+} // namespace Dali::Scene3D::Algorithm
\ No newline at end of file
diff --git a/dali-scene3d/public-api/algorithm/navigation-mesh.h b/dali-scene3d/public-api/algorithm/navigation-mesh.h
new file mode 100644 (file)
index 0000000..62e4a08
--- /dev/null
@@ -0,0 +1,282 @@
+#ifndef DALI_SCENE3D_NAVIGATION_MESH_H
+#define DALI_SCENE3D_NAVIGATION_MESH_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/common/vector-wrapper.h>
+#include <dali/public-api/math/matrix.h>
+#include <dali/public-api/math/vector3.h>
+#include <dali/public-api/math/vector4.h>
+
+#include <cinttypes>
+#include <cstdio>
+#include <limits>
+#include <memory>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/public-api/api.h>
+
+namespace Dali::Scene3D::Internal::Algorithm
+{
+class NavigationMesh;
+}
+
+namespace Dali::Scene3D::Loader
+{
+class NavigationMeshFactory;
+}
+
+constexpr auto NAVIGATION_MESH_MAX_VERTICES_PER_FACE = 3u;
+constexpr auto NAVIGATION_MESH_MAX_EDGES_PER_FACE    = 3u;
+constexpr auto NAVIGATION_MESH_MAX_COMPONENTS_3D     = 3u;
+constexpr auto NAVIGATION_MESH_MAX_COMPONENTS_2D     = 2u;
+
+namespace Dali::Scene3D::Algorithm
+{
+// Using PImpling but not usual DALi handles as this object isn't supposed to be refcounted
+using NavigationMeshImpl = Dali::Scene3D::Internal::Algorithm::NavigationMesh;
+
+// Make each to change each index value's type here.
+using VertexIndex = uint16_t;
+using EdgeIndex   = uint16_t;
+using FaceIndex   = uint16_t;
+
+/**
+ * @class NavigationMesh
+ *
+ * NavigationMesh is a set of connected faces. The data contains
+ * Polygons (Polys), Edges and Vertices and describes relations
+ * between (for example, edge knows which polys are on each side).
+ *
+ * NavigationMesh uses any coordinate system that it has been exported with.
+ *
+ * The mesh is exported with gravity direction. This is because various editors
+ * may define UP vector differently. Note, the Gravity vector points DOWN.
+ *
+ * - All calculation take place in the navigation mesh local space
+ * - The NavigationMesh should use a correct transformation matrix (SetSceneTransform())
+ * - Without transform, the NavigationMesh space stays local (compatible with exporter tool)
+ * - The NavigationMesh defines Gravity vector (down)
+ * - The finding floor results are returned back into the scene space (set with SetSceneTransform()).
+ *
+ */
+class DALI_SCENE3D_API NavigationMesh
+{
+public:
+  /**
+   * @struct Face
+   *
+   * Describes a single polygon's face
+   */
+  struct Face
+  {
+    VertexIndex vertex[NAVIGATION_MESH_MAX_VERTICES_PER_FACE]; ///< Vertices per face
+    EdgeIndex   edge[NAVIGATION_MESH_MAX_EDGES_PER_FACE];      ///< Edges per face
+    float       normal[NAVIGATION_MESH_MAX_COMPONENTS_3D];     ///< Normal vector
+    float       center[NAVIGATION_MESH_MAX_COMPONENTS_3D];     ///< Barycentric coordinates
+  };
+
+  /**
+   * @struct Edge
+   *
+   * Describes a single edge
+   */
+  struct Edge
+  {
+    VertexIndex vertex[NAVIGATION_MESH_MAX_COMPONENTS_2D]; ///< Vertices making the edge
+    FaceIndex   face[NAVIGATION_MESH_MAX_COMPONENTS_2D];   ///< Faces on both sides of edge
+  };
+
+  /**
+   * @struct Vertex
+   *
+   * Describes a single Vertex
+   *
+   */
+  struct Vertex
+  {
+    union
+    {
+      float coordinates[NAVIGATION_MESH_MAX_COMPONENTS_3D]; ///< Coordinates of vertex
+      struct
+      {
+        float x, y, z;
+      };
+    };
+  };
+
+  NavigationMesh() = delete;
+
+public:
+  /**
+   * @brief Destructor
+   */
+  ~NavigationMesh();
+
+  /**
+   * @brief Returns total number of faces
+   *
+   * @return number of faces
+   */
+  [[nodiscard]] uint32_t GetFaceCount() const;
+
+  /**
+   * @brief Returns total number of edges
+   *
+   * @return number of edges
+   */
+  [[nodiscard]] uint32_t GetEdgeCount() const;
+
+  /**
+   * @brief Returns total number of vertices
+   *
+   * @return number of vertices
+   */
+  [[nodiscard]] uint32_t GetVertexCount() const;
+
+  /**
+   * @brief Looks for the floor under specified position
+   * @param[in] position Position to investigate
+   * @param[in] outPosition Position on the floor in found
+   * @param[out] faceIndex Index of NavigationMesh face associated with floor
+   *
+   * @return True if floor has been found, False otherwise
+   */
+  bool FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex);
+
+  /**
+   * @brief Looks for a floor starting from specified face
+   *
+   * The function performs lookup starting from the specified face. If 'dontCheckNeighbours' is 'true'
+   * then function will fail if 'position' falls outside boundries of face. If 'dontCheckNeighbours'
+   * is 'false' the function will continue search expanding onto neighbouring faces.
+   *
+   * @param[in] position Position to investigate
+   * @param[in] faceIndex Face index to start lookup
+   * @param[in] dontCheckNeighbours If true, the neighbouring faces won't be tested
+   * @param[out] outPosition Result of lookup
+   *
+   * @return True on success, false otherwise
+   */
+  bool FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition);
+
+  /**
+   * @brief Returns pointer to Face structure
+   * @param[in] index Index of face to retrieve
+   * @return Pointer to valid Face structure or nullptr
+   */
+  [[nodiscard]] const Face* GetFace(FaceIndex index) const;
+
+  /**
+   * @brief Returns edge structure
+   * @param[in] index Index of edge to retrieve
+   * @return Pointer to valid Edge structure or nullptr
+   */
+  [[nodiscard]] const Edge* GetEdge(EdgeIndex index) const;
+
+  /**
+   * @brief Returns vertex structure
+   * @param[in] index Index of vertex to retrieve
+   * @return Pointer to valid Vertex structure or nullptr
+   */
+  [[nodiscard]] const Vertex* GetVertex(VertexIndex index) const;
+
+  /**
+   * @brief Sets static transform for the navigation mesh object
+   *
+   * The NavigationMesh may require to be transformed into the coordinates
+   * of the scene object. The exporter exports navigation geometry in a local
+   * space. The transform must be set in order to use the navigation mesh
+   * in the scene space (most likely DALi coordinate space).
+   *
+   * The scene transform matrix can be set in the DALi event thread using
+   * Dali::DevelActor::GetWorldTransform(sceneActor)
+   *
+   * For example:
+   * @code
+   * Actor parentActorOfNavigationMesh; // non-null object
+   * Dali::DevelActor::GetWorldTransform(parentActorOfNavigationMesh);
+   * navigationMesh->SetSceneTransform(parentActorOfNavigationMesh);
+   * @endcode
+   *
+   * The transform remains static until changed by calling SetSceneTransform() again.
+   * It means that if the matrix is obtained from the actor and actor transform will
+   * change the navigation mesh won't be aligned anymore.
+   *
+   * @param[in] transform Valid transform 4x4 matrix
+   */
+  void SetSceneTransform(const Dali::Matrix& transform);
+
+  /**
+   * @brief transforms point into the NavigationMesh local space
+   *
+   * Transforms a 3D point into navigation mesh space (space used when
+   * NavigationMesh has been created, most likely 3D editor space).
+   *
+   * @param[in] point Point to transform
+   * @return Point transformed to the local space
+   */
+  Dali::Vector3 PointSceneToLocal(const Dali::Vector3& point) const;
+
+  /**
+   * @brief Transforms point into the parent transform space
+   *
+   * Transforms the given point into the parent space (set with SetSceneTransform()).
+   *
+   * @param[in] point Point to transform
+   * @return Point transformed into the parent space
+   */
+  Dali::Vector3 PointLocalToScene(const Dali::Vector3& point) const;
+
+  /**
+   * @brief Returns direction of the gravity vector
+   *
+   * Gravity vector points down.
+   *
+   * @return Gravity vector 3D
+   */
+  Dali::Vector3 GetGravityVector() const;
+
+  /**
+   * @brief Performs ray/face intersect test
+   * @param[in] origin Origin of ray
+   * @param[in] direction Direction of ray
+   *
+   * @SINCE_2_3.0
+   * @return Valid FaceIndex on hit or NULL_FACE on miss
+   */
+  [[nodiscard]] FaceIndex RayFaceIntersect(const Vector3& origin, const Vector3& direction) const;
+
+  static constexpr FaceIndex NULL_FACE{std::numeric_limits<FaceIndex>::max()}; ///< Represents null face
+  static constexpr EdgeIndex NULL_EDGE{std::numeric_limits<EdgeIndex>::max()}; ///< Represents null edge
+
+public:
+  DALI_INTERNAL explicit NavigationMesh(NavigationMeshImpl* impl);
+
+  std::unique_ptr<NavigationMeshImpl> mImpl;
+};
+
+// Alias name for collider mesh
+// TODO: currently ColliderMesh is NavigationMesh however
+//       there should be separation from data and algorithms.
+//       Both, NavigationMesh and ColliderMesh use the same
+//       data structures but differ in the way they use data.
+using ColliderMesh = NavigationMesh;
+
+} // namespace Dali::Scene3D::Algorithm
+#endif // DALI_SCENE3D_NAVIGATION_MESH_H
index 76070c6..4e64d58 100644 (file)
 #include <dali-scene3d/internal/controls/model/model-impl.h>
 #include <dali-scene3d/public-api/model-components/model-node.h>
 
-namespace Dali
+namespace Dali::Scene3D
 {
-namespace Scene3D
-{
-Model::Model()
-{
-}
+Model::Model() = default;
 
 Model::Model(const Model& model) = default;
 
@@ -38,9 +34,7 @@ Model& Model::operator=(const Model& model) = default;
 
 Model& Model::operator=(Model&& rhs) = default;
 
-Model::~Model()
-{
-}
+Model::~Model() = default;
 
 Model Model::New(const std::string& modelUrl, const std::string& resourceDirectoryUrl)
 {
@@ -168,6 +162,9 @@ void Model::SetMotionData(MotionData motionData)
   GetImpl(*this).SetMotionData(motionData);
 }
 
-} // namespace Scene3D
+Model::MeshHitSignalType& Model::MeshHitSignal()
+{
+  return GetImpl(*this).MeshHitSignal();
+}
 
-} // namespace Dali
+} // namespace Dali::Scene3D
index f4aad11..31185b8 100644 (file)
  */
 
 // EXTERNAL INCLUDES
+#include <memory>
+
 #include <dali-toolkit/public-api/controls/control.h>
 #include <dali/public-api/actors/camera-actor.h>
 #include <dali/public-api/common/dali-common.h>
 #include <dali/public-api/rendering/texture.h>
 
 // INTERNAL INCLUDES
+#include <dali-scene3d/public-api/algorithm/navigation-mesh.h>
 #include <dali-scene3d/public-api/api.h>
 #include <dali-scene3d/public-api/model-components/model-node.h>
 #include <dali-scene3d/public-api/model-motion/motion-data.h>
@@ -71,6 +74,10 @@ class Model;
 class DALI_SCENE3D_API Model : public Dali::Toolkit::Control
 {
 public:
+  // Typedefs
+  using MeshHitSignalType = Signal<bool(Model, Scene3D::ModelNode)>; ///< Mesh hit signal type @SINCE_2_2.99 TODO: See what parameters need to be added
+  using ColliderMeshPtr   = std::unique_ptr<Algorithm::NavigationMesh>;
+
   /**
    * @brief Create an initialized Model.
    *
@@ -366,6 +373,23 @@ public:
    */
   void SetMotionData(Scene3D::MotionData motionData);
 
+  /**
+   * @brief This signal is emitted when the collider mesh is touched/hit.
+   *
+   * A callback of the following type may be connected:
+   * @code
+   *   bool YourCallbackName(Model model, ModelNode modelNode);
+   * @endcode
+   * Here the model is the model that is hit and the ModelNode containing the collider mesh
+   * was applied to.
+   * The return value of True, indicates that the hover event should be consumed.
+   * Otherwise the signal will be emitted on the next sensitive parent of the actor.
+   *
+   * @SINCE_2_3.0
+   * @return The signal to connect to
+   */
+  MeshHitSignalType& MeshHitSignal();
+
 public: // Not intended for application developers
   /// @cond internal
   /**
diff --git a/dali-scene3d/public-api/loader/navigation-mesh-factory.cpp b/dali-scene3d/public-api/loader/navigation-mesh-factory.cpp
new file mode 100644 (file)
index 0000000..c54cb73
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * 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-scene3d/public-api/loader/navigation-mesh-factory.h>
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/debug.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/internal/algorithm/navigation-mesh-impl.h>
+#include <dali/devel-api/adaptor-framework/file-stream.h>
+#include <stdlib.h>
+#include <memory>
+
+namespace Dali::Scene3D::Loader
+{
+std::unique_ptr<Algorithm::NavigationMesh> NavigationMeshFactory::CreateFromFile(std::string filename)
+{
+  std::vector<uint8_t> buffer;
+
+  Dali::FileStream fileStream(filename, Dali::FileStream::READ | Dali::FileStream::BINARY);
+  auto             fin = fileStream.GetFile();
+
+  if(DALI_UNLIKELY(!fin))
+  {
+    const int bufferLength = 128;
+    char      buffer[bufferLength];
+
+    // Return type of stderror_r is different between system type. We should not use return value.
+    [[maybe_unused]] auto ret = strerror_r(errno, buffer, bufferLength - 1);
+
+    DALI_LOG_ERROR("NavigationMesh: Can't open %s for reading: %s", filename.c_str(), buffer);
+    return nullptr;
+  }
+
+  if(DALI_UNLIKELY(fseek(fin, 0, SEEK_END)))
+  {
+    DALI_LOG_ERROR("NavigationMesh: Error reading file: %s\n", filename.c_str());
+    return nullptr;
+  }
+
+  auto size = ftell(fin);
+  if(DALI_UNLIKELY(size < 0))
+  {
+    DALI_LOG_ERROR("NavigationMesh: Error reading file: %s\n", filename.c_str());
+    return nullptr;
+  }
+
+  auto fileSize = size_t(size);
+  if(DALI_UNLIKELY(fseek(fin, 0, SEEK_SET)))
+  {
+    DALI_LOG_ERROR("NavigationMesh: Error reading file: %s\n", filename.c_str());
+    return nullptr;
+  }
+
+  buffer.resize(size);
+  auto count = fread(buffer.data(), 1, fileSize, fin);
+  if(DALI_UNLIKELY(count != fileSize))
+  {
+    DALI_LOG_ERROR("NavigationMesh: Error reading file: %s\n", filename.c_str());
+    return nullptr;
+  }
+  return CreateFromBuffer(buffer);
+}
+
+std::unique_ptr<Algorithm::NavigationMesh> NavigationMeshFactory::CreateFromBuffer(const std::vector<uint8_t>& buffer)
+{
+  auto impl = new Scene3D::Internal::Algorithm::NavigationMesh(buffer);
+  return std::make_unique<Algorithm::NavigationMesh>(impl);
+}
+
+std::unique_ptr<Algorithm::NavigationMesh> NavigationMeshFactory::CreateFromVertexFaceList(const Vector3* vertices, const Vector3* vertexNormals, uint32_t vertexCount, const uint32_t* faceIndices, uint32_t indexCount)
+{
+  // The function takes the data and creates a binary buffer out of it
+  using namespace Dali::Scene3D::Algorithm;
+  auto header = Internal::Algorithm::NavigationMeshHeader_V10();
+
+  // create header
+  header.checksum = *reinterpret_cast<const uint32_t*>("NAVM");
+  header.version  = 0; // latest version
+
+  // Copy given vertices
+  std::vector<NavigationMesh::Vertex> meshVertices;
+  meshVertices.reserve(vertexCount);
+  for(auto i = 0u; i < vertexCount; ++i)
+  {
+    meshVertices.emplace_back();
+    meshVertices.back().x = vertices[i].x;
+    meshVertices.back().y = vertices[i].y;
+    meshVertices.back().z = vertices[i].z;
+  }
+
+  // copy faces and edges
+  std::vector<NavigationMesh::Face> meshFaces;
+  meshFaces.resize(indexCount / 3);
+  auto i              = 0u;
+  bool computeNormals = (vertexNormals == nullptr);
+  for(auto& f : meshFaces)
+  {
+    f.vertex[0] = faceIndices[i];
+    f.vertex[1] = faceIndices[i + 1];
+    f.vertex[2] = faceIndices[i + 2];
+
+    // compute normals (if not supplied)
+    if(computeNormals)
+    {
+      auto v01 = Vector3(meshVertices[f.vertex[1]].coordinates) - Vector3(meshVertices[f.vertex[0]].coordinates);
+      auto v02 = Vector3(meshVertices[f.vertex[2]].coordinates) - Vector3(meshVertices[f.vertex[0]].coordinates);
+      auto n   = v01.Cross(v02);
+      n.Normalize();
+      f.normal[0] = n.x;
+      f.normal[1] = n.y;
+      f.normal[2] = n.z;
+    }
+    else
+    {
+      auto& n0 = vertexNormals[faceIndices[i]];
+      auto& n1 = vertexNormals[faceIndices[i + 1]];
+      auto& n2 = vertexNormals[faceIndices[i + 2]];
+
+      auto faceNormal = (n0 + n1 + n2) / 3.0f;
+      faceNormal.Normalize();
+      f.normal[0] = faceNormal.x;
+      f.normal[1] = -faceNormal.y;
+      f.normal[2] = faceNormal.z;
+    }
+    i += 3;
+  }
+
+  // Create edges, in this case we don't care about duplicates
+  // This mesh cannot be used for navigation
+  std::vector<NavigationMesh::Edge> meshEdges;
+  meshEdges.reserve(meshFaces.size() * 3);
+  i = 0;
+  for(auto& f : meshFaces)
+  {
+    for(auto k = 0u; k < 3; ++k)
+    {
+      meshEdges.emplace_back();
+      auto& edge     = meshEdges.back();
+      edge.face[0]   = i;
+      edge.face[1]   = NavigationMesh::NULL_FACE;
+      edge.vertex[0] = f.vertex[k];
+      edge.vertex[1] = f.vertex[(k + 1) % 3];
+    }
+    ++i;
+  }
+
+  std::vector<uint8_t> navigationMeshBinary;
+
+  // Build navigationMeshBinary binary
+  navigationMeshBinary.insert(navigationMeshBinary.end(),
+              reinterpret_cast<uint8_t*>(&header),
+              reinterpret_cast<uint8_t*>(&header) + sizeof(Internal::Algorithm::NavigationMeshHeader_V10));
+
+  auto& h = *reinterpret_cast<decltype(header)*>(navigationMeshBinary.data());
+
+  h.dataOffset       = sizeof(header);
+  h.edgeCount        = meshEdges.size();
+  h.polyCount        = meshFaces.size();
+  h.vertexCount      = meshVertices.size();
+  h.gravityVector[0] = 0.0f;
+  h.gravityVector[1] = -1.0f;
+  h.gravityVector[2] = 0.0f;
+  h.version          = 0;
+  h.vertexDataOffset = 0;
+  h.edgeDataOffset   = meshVertices.size() * sizeof(NavigationMesh::Vertex);
+  h.polyDataOffset   = h.edgeDataOffset + meshEdges.size() * sizeof(NavigationMesh::Edge);
+
+  // Copy data
+  navigationMeshBinary.insert(navigationMeshBinary.end(),
+              reinterpret_cast<uint8_t*>(meshVertices.data()),
+              reinterpret_cast<uint8_t*>(meshVertices.data()) + (meshVertices.size() * sizeof(NavigationMesh::Vertex)));
+  navigationMeshBinary.insert(navigationMeshBinary.end(),
+              reinterpret_cast<uint8_t*>(meshEdges.data()),
+              reinterpret_cast<uint8_t*>(meshEdges.data()) + (meshEdges.size() * sizeof(NavigationMesh::Edge)));
+  navigationMeshBinary.insert(navigationMeshBinary.end(),
+              reinterpret_cast<uint8_t*>(meshFaces.data()),
+              reinterpret_cast<uint8_t*>(meshFaces.data()) + (meshFaces.size() * sizeof(NavigationMesh::Face)));
+
+  auto retval = std::move(NavigationMeshFactory::CreateFromBuffer(navigationMeshBinary));
+  return retval;
+}
+
+std::unique_ptr<Algorithm::NavigationMesh> NavigationMeshFactory::CreateFromVertexFaceList(const std::vector<Vector3>& vertices, const std::vector<Vector3>& normals, const std::vector<uint32_t>& faceIndices)
+{
+  return CreateFromVertexFaceList(vertices.data(), normals.data(), vertices.size(), faceIndices.data(), faceIndices.size());
+}
+
+std::vector<uint8_t> NavigationMeshFactory::GetMeshBinary(const Dali::Scene3D::Algorithm::NavigationMesh& navigationMesh)
+{
+  auto& meshImpl = Internal::Algorithm::GetImplementation(navigationMesh);
+
+  // Return as mutable copy
+  return meshImpl.GetData();
+}
+
+} // namespace Dali::Scene3D::Loader
\ No newline at end of file
diff --git a/dali-scene3d/public-api/loader/navigation-mesh-factory.h b/dali-scene3d/public-api/loader/navigation-mesh-factory.h
new file mode 100644 (file)
index 0000000..b3dd576
--- /dev/null
@@ -0,0 +1,93 @@
+#ifndef DALI_SCENE3D_LOADER_NAVIGATION_MESH_FACTORY_H
+#define DALI_SCENE3D_LOADER_NAVIGATION_MESH_FACTORY_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/rendering/geometry.h>
+#include <dali/public-api/rendering/texture.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/public-api/algorithm/navigation-mesh.h>
+#include <dali-scene3d/public-api/api.h>
+
+namespace Dali::Scene3D::Loader
+{
+struct DALI_SCENE3D_API NavigationMeshFactory
+{
+public:
+  /**
+   * @brief Creates NavigationMesh object from file
+   *
+   * @param[in] filename file to load
+   * @return Valid NavigationMesh or nullptr
+   */
+  static std::unique_ptr<Algorithm::NavigationMesh> CreateFromFile(std::string filename);
+
+  /**
+   * @brief Creates NavigationMesh object from binary buffer
+   *
+   * @param[in] buffer buffer with data
+   * @return Valid NavigationMesh or nullptr
+   */
+  static std::unique_ptr<Algorithm::NavigationMesh> CreateFromBuffer(const std::vector<uint8_t>& buffer);
+
+  /**
+   * @brief Creates new mesh from lists of vertices and faces
+   *
+   * List of faces contains indices into the vertex list
+   *
+   * @SINCE_2_3.0
+   * @param[in] vertices List of Vector3 vertices
+   * @param[in] vertexNormals List of Vector3 vertices
+   * @param[in] faceIndices List of faces
+   * @return Valid NavigationMesh or nullptr
+   */
+  static std::unique_ptr<Algorithm::NavigationMesh> CreateFromVertexFaceList(const std::vector<Vector3>& vertices, const std::vector<Vector3>& vertexNormals, const std::vector<uint32_t>& faceIndices);
+
+  /**
+   * @brief Creates new mesh from lists of vertices and faces
+   *
+   * List of faces contains indices into the vertex list
+   *
+   * This function reduces number of array copys when called from NUI.
+   *
+   * @SINCE_2_3.0
+   * @param[in] vertices Pointer to C-style array of vertices
+   * @param[in] vertexCount Number of vertices
+   * @param[in] vertexNormals to C-style array of vertex normals
+   * @param[in] faceIndices Pointer to C-style array of face elements indices
+   * @param[in] indexCount Number of indices
+   * @return Valid NavigationMesh or nullptr
+   */
+  static std::unique_ptr<Algorithm::NavigationMesh> CreateFromVertexFaceList(const Vector3* vertices, const Vector3* vertexNormals, uint32_t vertexCount, const uint32_t* faceIndices, uint32_t indexCount);
+
+  /**
+   * @brief Serializes mesh data to the binary format.
+   *
+   * The binary data returned by the function can be used
+   * as an input for NavigationMeshFactory::CreateFromBuffer()
+   *
+   * @SINCE_2_3.0
+   * @param[in] navigationMesh Navigation mesh to serialize
+   * @return Buffer containing serialized mesh data
+   */
+  static std::vector<uint8_t> GetMeshBinary(const Dali::Scene3D::Algorithm::NavigationMesh& navigationMesh);
+};
+} // namespace Dali::Scene3D::Loader
+
+#endif // DALI_SCENE3D_INTERNAL_LOADER_NAVIGATION_MESH_FACTORY_H
index 44dc1fd..3d7c8e5 100644 (file)
@@ -128,6 +128,21 @@ Loader::BlendShapes::Index ModelNode::GetBlendShapeIndexByName(std::string_view
   return Internal::GetImplementation(*this).GetBlendShapeIndexByName(blendShapeName);
 }
 
+void ModelNode::SetColliderMesh(std::unique_ptr<Algorithm::ColliderMesh>&& colliderMesh)
+{
+  Internal::GetImplementation(*this).SetColliderMesh(std::move(colliderMesh));
+}
+
+const Algorithm::ColliderMesh& ModelNode::GetColliderMesh()
+{
+  return Internal::GetImplementation(*this).GetColliderMesh();
+}
+
+bool ModelNode::HasColliderMesh() const
+{
+  return Internal::GetImplementation(*this).HasColliderMesh();
+}
+
 } // namespace Scene3D
 
 } // namespace Dali
index fec2ae8..6d75411 100644 (file)
 #include <dali/public-api/common/dali-common.h>
 
 // INTERNAL INCLUDES
+#include <dali-scene3d/public-api/algorithm/navigation-mesh.h>
 #include <dali-scene3d/public-api/api.h>
 #include <dali-scene3d/public-api/loader/blend-shape-details.h> ///< For Loader::BlendShapes::Index
 #include <dali-scene3d/public-api/model-components/model-primitive.h>
-
 namespace Dali
 {
 namespace Scene3D
@@ -205,6 +205,40 @@ public: // Public Method
    */
   Loader::BlendShapes::Index GetBlendShapeIndexByName(std::string_view blendShapeName) const;
 
+  /**
+   * @brief Sets collider mesh on the ModelNode
+   *
+   * The ownership of a collider mesh is taken over by the ModelNode.
+   *
+   * If there was a collider mesh set previously it will be erased.
+   *
+   * To remove collider mesh empty unique_ptr must be passed.
+   *
+   * @SINCE_2_3.0
+   * @param[in] colliderMesh r-value to unique pointer of ColliderMesh
+   */
+  void SetColliderMesh(std::unique_ptr<Algorithm::ColliderMesh>&& colliderMesh);
+
+  /**
+   * @brief Returns associated collider mesh
+   *
+   * HasColliderMesh() must be called to determine whether a valid
+   * collider mesh is associated. Calling GetColliderMesh() without
+   * previous check may produce undefined behaviour.
+   *
+   * @SINCE_2_3.0
+   * @return Associated collider mesh
+   */
+  const Algorithm::ColliderMesh& GetColliderMesh();
+
+  /**
+   * @brief Determines whether there is a valid collider mesh set
+   *
+   * @SINCE_2_3.0
+   * @return True if collider mesh is set, False otherwise
+   */
+  [[nodiscard]] bool HasColliderMesh() const;
+
 public: // Not intended for application developers
   /// @cond internal
   /**