From: Adam Bialogonski Date: Mon, 13 Nov 2023 07:34:01 +0000 (+0000) Subject: [Tizen] 3D Scene Hit Testing X-Git-Tag: accepted/tizen/7.0/unified/20231207.071832~3 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e421f66fa7b8dc79e9f4790f0cbfa883fdbd376d;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git [Tizen] 3D Scene Hit Testing - 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 Signed-off-by: huiyu.eun --- 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 index 0000000..5e97c51 --- /dev/null +++ b/automated-tests/src/dali-scene3d/collider-mesh-data.h @@ -0,0 +1,75 @@ +#include +#include + +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> 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(); \ + v.insert(v.end(), col_test_##N, col_test_##N + col_test_##N##_len); \ + return v; \ + }) + +static std::vector GetTestColliderMesh(int n) +{ + auto v = std::vector(); + 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 index 0000000..5df30be --- /dev/null +++ b/automated-tests/src/dali-scene3d/utc-Dali-NavigationMesh.cpp @@ -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 +#include +#include +#include +#include +#include +#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 +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 + 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 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 call_ftell("ftell"); +extern "C" long int ftell(FILE* s) +{ + return call_ftell.Invoke(s); +} + +// Override fread() +static thread_local SysOverride 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 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 COLLIDER_0_IDX = { + 1, + 2, + 0, + 1, + 5, + 3, + 3, + 6, + 2, + 1, + 3, + 2, + 1, + 4, + 5, + 3, + 7, + 6, +}; +static std::vector 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 COLLIDER_1_IDX = { + 1, + 2, + 0, + 2, + 4, + 0, + 1, + 3, + 2, + 2, + 5, + 4, +}; +static std::vector 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 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 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 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 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 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::epsilon(), TEST_LOCATION); + + navMeshParentSpace = navmesh->PointLocalToScene(gravityVector); + + // The gravity should be transformed back into point + DALI_TEST_EQUALS(navMeshParentSpace, point, std::numeric_limits::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 inPositions; + std::vector expectedPositions; + std::vector expectedFaceIndex; + std::vector 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 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 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::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 index 0000000..81b999d --- /dev/null +++ b/dali-scene3d/internal/algorithm/navigation-mesh-impl.cpp @@ -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 + +// EXTERNAL INCLUDES +#include + +#include +#include + +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& buffer) +{ + mBuffer.resize(buffer.size()); + std::copy(buffer.begin(), buffer.end(), mBuffer.begin()); + + // Setup header from the buffer + mHeader = *reinterpret_cast(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 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 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(mBuffer.data() + mHeader.dataOffset + mHeader.polyDataOffset); + return &basePtr[index]; +} + +const Edge* NavigationMesh::GetEdge(EdgeIndex index) const +{ + auto* basePtr = reinterpret_cast(mBuffer.data() + mHeader.dataOffset + mHeader.edgeDataOffset); + return &basePtr[index]; +} + +const Vertex* NavigationMesh::GetVertex(VertexIndex index) const +{ + auto* basePtr = reinterpret_cast(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 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 index 0000000..6a82787 --- /dev/null +++ b/dali-scene3d/internal/algorithm/navigation-mesh-impl.h @@ -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 +#include +#include +#include +#include + +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include + +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& 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& GetData() const + { + return mBuffer; + } + +private: + std::vector 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 diff --git a/dali-scene3d/internal/controls/model/model-impl.cpp b/dali-scene3d/internal/controls/model/model-impl.cpp index a7dde17..df21768 100644 --- a/dali-scene3d/internal/controls/model/model-impl.cpp +++ b/dali-scene3d/internal/controls/model/model-impl.cpp @@ -33,6 +33,7 @@ // INTERNAL INCLUDES #include #include +#include #include #include #include @@ -44,7 +45,7 @@ #include #include #include - +#include 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(Dali::Actor::Property::CONNECTED_TO_SCENE)) { NotifyResourceReady(); } } +void Model::RegisterColliderMesh(Scene3D::ModelNode& modelNode, const Dali::Scene3D::Algorithm::ColliderMesh& mesh) +{ + mColliderMeshes[modelNode.GetProperty(Actor::Property::ID)] = modelNode; + + // Add processor + Scene3D::ColliderMeshProcessor::Get().ColliderMeshChanged(Scene3D::Model::DownCast(Self())); +} + +void Model::RemoveColliderMesh(Scene3D::ModelNode& node) +{ + auto id = node.GetProperty(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); diff --git a/dali-scene3d/internal/controls/model/model-impl.h b/dali-scene3d/internal/controls/model/model-impl.h index b740da4..ff9ba0c 100644 --- a/dali-scene3d/internal/controls/model/model-impl.h +++ b/dali-scene3d/internal/controls/model/model-impl.h @@ -27,11 +27,13 @@ #include #include #include +#include // INTERNAL INCLUDES #include #include #include +#include #include #include #include @@ -56,6 +58,11 @@ public: using CameraData = Loader::CameraParameters; using BlendShapeModelNodeMap = std::map>; + // 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; + /** * @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 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 index 0000000..d8ce462 --- /dev/null +++ b/dali-scene3d/internal/event/collider-mesh-processor-impl.cpp @@ -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 + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include + +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; + +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 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(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(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 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 index 0000000..45c7b18 --- /dev/null +++ b/dali-scene3d/internal/event/collider-mesh-processor-impl.h @@ -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 +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include + +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 mSceneViewsToProcess; + std::vector mConnectedSceneViews; +}; + +} // namespace Internal + +inline Internal::ColliderMeshProcessor& GetImpl(ColliderMeshProcessor& obj) +{ + DALI_ASSERT_ALWAYS(obj); + + Dali::BaseObject& handle = obj.GetBaseObject(); + + return static_cast(handle); +} + +inline const Internal::ColliderMeshProcessor& GetImpl(const ColliderMeshProcessor& obj) +{ + DALI_ASSERT_ALWAYS(obj); + + const Dali::BaseObject& handle = obj.GetBaseObject(); + + return static_cast(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 index 0000000..9f0ca0d --- /dev/null +++ b/dali-scene3d/internal/event/collider-mesh-processor.cpp @@ -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 + +// EXTERNAL INCLUDES +#include + +// INTERNAL INCLUDES +#include + +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(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 index 0000000..0d874ee --- /dev/null +++ b/dali-scene3d/internal/event/collider-mesh-processor.h @@ -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 + +// INTERNAL INCLUDES +#include + +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 diff --git a/dali-scene3d/internal/file.list b/dali-scene3d/internal/file.list index 803a6af..3bd0085 100644 --- a/dali-scene3d/internal/file.list +++ b/dali-scene3d/internal/file.list @@ -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 diff --git a/dali-scene3d/internal/model-components/model-node-impl.cpp b/dali-scene3d/internal/model-components/model-node-impl.cpp index 8370795..a9fbb39 100644 --- a/dali-scene3d/internal/model-components/model-node-impl.cpp +++ b/dali-scene3d/internal/model-components/model-node-impl.cpp @@ -24,6 +24,7 @@ #include // INTERNAL INCLUDES +#include #include #include @@ -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 diff --git a/dali-scene3d/internal/model-components/model-node-impl.h b/dali-scene3d/internal/model-components/model-node-impl.h index ed17a45..c624deb 100644 --- a/dali-scene3d/internal/model-components/model-node-impl.h +++ b/dali-scene3d/internal/model-components/model-node-impl.h @@ -27,12 +27,14 @@ // INTERNAL INCLUDES #include +#include #include #include #include #include #include #include +#include "dali-scene3d/public-api/controls/model/model.h" namespace Dali { @@ -45,6 +47,7 @@ namespace Scene3D namespace Internal { +using ColliderMeshUniquePtr = std::unique_ptr; /** * @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 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 index 0000000..4aff076 --- /dev/null +++ b/dali-scene3d/public-api/algorithm/navigation-mesh.cpp @@ -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 + +// INTERNAL HEADERS +#include + +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 index 0000000..62e4a08 --- /dev/null +++ b/dali-scene3d/public-api/algorithm/navigation-mesh.h @@ -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 +#include +#include +#include + +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include + +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::max()}; ///< Represents null face + static constexpr EdgeIndex NULL_EDGE{std::numeric_limits::max()}; ///< Represents null edge + +public: + DALI_INTERNAL explicit NavigationMesh(NavigationMeshImpl* impl); + + std::unique_ptr 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 diff --git a/dali-scene3d/public-api/controls/model/model.cpp b/dali-scene3d/public-api/controls/model/model.cpp index 76070c6..4e64d58 100644 --- a/dali-scene3d/public-api/controls/model/model.cpp +++ b/dali-scene3d/public-api/controls/model/model.cpp @@ -22,13 +22,9 @@ #include #include -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 diff --git a/dali-scene3d/public-api/controls/model/model.h b/dali-scene3d/public-api/controls/model/model.h index f4aad11..31185b8 100644 --- a/dali-scene3d/public-api/controls/model/model.h +++ b/dali-scene3d/public-api/controls/model/model.h @@ -19,12 +19,15 @@ */ // EXTERNAL INCLUDES +#include + #include #include #include #include // INTERNAL INCLUDES +#include #include #include #include @@ -71,6 +74,10 @@ class Model; class DALI_SCENE3D_API Model : public Dali::Toolkit::Control { public: + // Typedefs + using MeshHitSignalType = Signal; ///< Mesh hit signal type @SINCE_2_2.99 TODO: See what parameters need to be added + using ColliderMeshPtr = std::unique_ptr; + /** * @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 index 0000000..c54cb73 --- /dev/null +++ b/dali-scene3d/public-api/loader/navigation-mesh-factory.cpp @@ -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 + +// EXTERNAL INCLUDES +#include + +// INTERNAL INCLUDES +#include +#include +#include +#include + +namespace Dali::Scene3D::Loader +{ +std::unique_ptr NavigationMeshFactory::CreateFromFile(std::string filename) +{ + std::vector 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 NavigationMeshFactory::CreateFromBuffer(const std::vector& buffer) +{ + auto impl = new Scene3D::Internal::Algorithm::NavigationMesh(buffer); + return std::make_unique(impl); +} + +std::unique_ptr 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("NAVM"); + header.version = 0; // latest version + + // Copy given vertices + std::vector 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 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 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 navigationMeshBinary; + + // Build navigationMeshBinary binary + navigationMeshBinary.insert(navigationMeshBinary.end(), + reinterpret_cast(&header), + reinterpret_cast(&header) + sizeof(Internal::Algorithm::NavigationMeshHeader_V10)); + + auto& h = *reinterpret_cast(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(meshVertices.data()), + reinterpret_cast(meshVertices.data()) + (meshVertices.size() * sizeof(NavigationMesh::Vertex))); + navigationMeshBinary.insert(navigationMeshBinary.end(), + reinterpret_cast(meshEdges.data()), + reinterpret_cast(meshEdges.data()) + (meshEdges.size() * sizeof(NavigationMesh::Edge))); + navigationMeshBinary.insert(navigationMeshBinary.end(), + reinterpret_cast(meshFaces.data()), + reinterpret_cast(meshFaces.data()) + (meshFaces.size() * sizeof(NavigationMesh::Face))); + + auto retval = std::move(NavigationMeshFactory::CreateFromBuffer(navigationMeshBinary)); + return retval; +} + +std::unique_ptr NavigationMeshFactory::CreateFromVertexFaceList(const std::vector& vertices, const std::vector& normals, const std::vector& faceIndices) +{ + return CreateFromVertexFaceList(vertices.data(), normals.data(), vertices.size(), faceIndices.data(), faceIndices.size()); +} + +std::vector 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 index 0000000..b3dd576 --- /dev/null +++ b/dali-scene3d/public-api/loader/navigation-mesh-factory.h @@ -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 +#include + +// INTERNAL INCLUDES +#include +#include + +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 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 CreateFromBuffer(const std::vector& 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 CreateFromVertexFaceList(const std::vector& vertices, const std::vector& vertexNormals, const std::vector& 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 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 GetMeshBinary(const Dali::Scene3D::Algorithm::NavigationMesh& navigationMesh); +}; +} // namespace Dali::Scene3D::Loader + +#endif // DALI_SCENE3D_INTERNAL_LOADER_NAVIGATION_MESH_FACTORY_H diff --git a/dali-scene3d/public-api/model-components/model-node.cpp b/dali-scene3d/public-api/model-components/model-node.cpp index 44dc1fd..3d7c8e5 100644 --- a/dali-scene3d/public-api/model-components/model-node.cpp +++ b/dali-scene3d/public-api/model-components/model-node.cpp @@ -128,6 +128,21 @@ Loader::BlendShapes::Index ModelNode::GetBlendShapeIndexByName(std::string_view return Internal::GetImplementation(*this).GetBlendShapeIndexByName(blendShapeName); } +void ModelNode::SetColliderMesh(std::unique_ptr&& 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 diff --git a/dali-scene3d/public-api/model-components/model-node.h b/dali-scene3d/public-api/model-components/model-node.h index fec2ae8..6d75411 100644 --- a/dali-scene3d/public-api/model-components/model-node.h +++ b/dali-scene3d/public-api/model-components/model-node.h @@ -23,10 +23,10 @@ #include // INTERNAL INCLUDES +#include #include #include ///< For Loader::BlendShapes::Index #include - 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&& 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 /**