test_primitives.h 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. /**************************************************************************/
  2. /* test_primitives.h */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #ifndef TEST_PRIMITIVES_H
  31. #define TEST_PRIMITIVES_H
  32. #include "scene/resources/3d/primitive_meshes.h"
  33. #include "tests/test_macros.h"
  34. namespace TestPrimitives {
  35. TEST_CASE("[SceneTree][Primitive][Capsule] Capsule Primitive") {
  36. Ref<CapsuleMesh> capsule = memnew(CapsuleMesh);
  37. SUBCASE("[SceneTree][Primitive][Capsule] Default values should be valid") {
  38. CHECK_MESSAGE(capsule->get_radius() > 0,
  39. "Radius of default capsule positive.");
  40. CHECK_MESSAGE(capsule->get_height() > 0,
  41. "Height of default capsule positive.");
  42. CHECK_MESSAGE(capsule->get_radial_segments() >= 0,
  43. "Radius Segments of default capsule positive.");
  44. CHECK_MESSAGE(capsule->get_rings() >= 0,
  45. "Number of rings of default capsule positive.");
  46. }
  47. SUBCASE("[SceneTree][Primitive][Capsule] Set properties of the capsule and get them with accessor methods") {
  48. capsule->set_height(7.1f);
  49. capsule->set_radius(1.3f);
  50. capsule->set_radial_segments(16);
  51. capsule->set_rings(32);
  52. CHECK_MESSAGE(capsule->get_radius() == doctest::Approx(1.3f),
  53. "Get/Set radius work with one set.");
  54. CHECK_MESSAGE(capsule->get_height() == doctest::Approx(7.1f),
  55. "Get/Set radius work with one set.");
  56. CHECK_MESSAGE(capsule->get_radial_segments() == 16,
  57. "Get/Set radius work with one set.");
  58. CHECK_MESSAGE(capsule->get_rings() == 32,
  59. "Get/Set radius work with one set.");
  60. }
  61. SUBCASE("[SceneTree][Primitive][Capsule] If set segments negative, default to at least 0") {
  62. ERR_PRINT_OFF;
  63. capsule->set_radial_segments(-5);
  64. capsule->set_rings(-17);
  65. ERR_PRINT_ON;
  66. CHECK_MESSAGE(capsule->get_radial_segments() >= 0,
  67. "Ensure number of radial segments is >= 0.");
  68. CHECK_MESSAGE(capsule->get_rings() >= 0,
  69. "Ensure number of rings is >= 0.");
  70. }
  71. SUBCASE("[SceneTree][Primitive][Capsule] If set height < 2*radius, adjust radius and height to radius=height*0.5") {
  72. capsule->set_radius(1.f);
  73. capsule->set_height(0.5f);
  74. CHECK_MESSAGE(capsule->get_radius() >= capsule->get_height() * 0.5,
  75. "Ensure radius >= height * 0.5 (needed for capsule to exist).");
  76. }
  77. SUBCASE("[Primitive][Capsule] Check mesh is correct") {
  78. Array data{};
  79. data.resize(RS::ARRAY_MAX);
  80. float radius{ 0.5f };
  81. float height{ 4.f };
  82. int num_radial_segments{ 4 };
  83. int num_rings{ 8 };
  84. CapsuleMesh::create_mesh_array(data, radius, height, num_radial_segments, num_rings);
  85. Vector<Vector3> points = data[RS::ARRAY_VERTEX];
  86. SUBCASE("[Primitive][Capsule] Ensure all vertices positions are within bounding radius and height") {
  87. // Get mesh data
  88. // Check all points within radius of capsule
  89. float dist_to_yaxis = 0.f;
  90. for (Vector3 point : points) {
  91. float new_dist_to_y = point.x * point.x + point.z * point.z;
  92. if (new_dist_to_y > dist_to_yaxis) {
  93. dist_to_yaxis = new_dist_to_y;
  94. }
  95. }
  96. CHECK(dist_to_yaxis <= radius * radius);
  97. // Check highest point and lowest point are within height of each other
  98. float max_y{ 0.f };
  99. float min_y{ 0.f };
  100. for (Vector3 point : points) {
  101. if (point.y > max_y) {
  102. max_y = point.y;
  103. }
  104. if (point.y < min_y) {
  105. min_y = point.y;
  106. }
  107. }
  108. CHECK(max_y - min_y <= height);
  109. }
  110. SUBCASE("[Primitive][Capsule] If normal.y == 0, then mesh makes a cylinder.") {
  111. Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
  112. for (int ii = 0; ii < points.size(); ++ii) {
  113. float point_dist_from_yaxis = Math::sqrt(points[ii].x * points[ii].x + points[ii].z * points[ii].z);
  114. Vector3 yaxis_to_point{ points[ii].x / point_dist_from_yaxis, 0.f, points[ii].z / point_dist_from_yaxis };
  115. if (normals[ii].y == 0.f) {
  116. float mag_of_normal = Math::sqrt(normals[ii].x * normals[ii].x + normals[ii].z * normals[ii].z);
  117. Vector3 normalized_normal = normals[ii] / mag_of_normal;
  118. CHECK_MESSAGE(point_dist_from_yaxis == doctest::Approx(radius),
  119. "Points on the tube of the capsule are radius away from y-axis.");
  120. CHECK_MESSAGE(normalized_normal.is_equal_approx(yaxis_to_point),
  121. "Normal points orthogonal from mid cylinder.");
  122. }
  123. }
  124. }
  125. }
  126. } // End capsule tests
  127. TEST_CASE("[SceneTree][Primitive][Box] Box Primitive") {
  128. Ref<BoxMesh> box = memnew(BoxMesh);
  129. SUBCASE("[SceneTree][Primitive][Box] Default values should be valid") {
  130. CHECK(box->get_size().x > 0);
  131. CHECK(box->get_size().y > 0);
  132. CHECK(box->get_size().z > 0);
  133. CHECK(box->get_subdivide_width() >= 0);
  134. CHECK(box->get_subdivide_height() >= 0);
  135. CHECK(box->get_subdivide_depth() >= 0);
  136. }
  137. SUBCASE("[SceneTree][Primitive][Box] Set properties and get them with accessor methods") {
  138. Vector3 size{ 2.1, 3.3, 1.7 };
  139. box->set_size(size);
  140. box->set_subdivide_width(3);
  141. box->set_subdivide_height(2);
  142. box->set_subdivide_depth(4);
  143. CHECK(box->get_size().is_equal_approx(size));
  144. CHECK(box->get_subdivide_width() == 3);
  145. CHECK(box->get_subdivide_height() == 2);
  146. CHECK(box->get_subdivide_depth() == 4);
  147. }
  148. SUBCASE("[SceneTree][Primitive][Box] Set subdivides to negative and ensure they are >= 0") {
  149. ERR_PRINT_OFF;
  150. box->set_subdivide_width(-2);
  151. box->set_subdivide_height(-2);
  152. box->set_subdivide_depth(-2);
  153. ERR_PRINT_ON;
  154. CHECK(box->get_subdivide_width() >= 0);
  155. CHECK(box->get_subdivide_height() >= 0);
  156. CHECK(box->get_subdivide_depth() >= 0);
  157. }
  158. SUBCASE("[Primitive][Box] Check mesh is correct.") {
  159. Array data{};
  160. data.resize(RS::ARRAY_MAX);
  161. Vector3 size{ 0.5f, 1.2f, .9f };
  162. int subdivide_width{ 3 };
  163. int subdivide_height{ 2 };
  164. int subdivide_depth{ 8 };
  165. BoxMesh::create_mesh_array(data, size, subdivide_width, subdivide_height, subdivide_depth);
  166. Vector<Vector3> points = data[RS::ARRAY_VERTEX];
  167. Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
  168. SUBCASE("Only 6 distinct normals.") {
  169. Vector<Vector3> distinct_normals{};
  170. distinct_normals.push_back(normals[0]);
  171. for (const Vector3 &normal : normals) {
  172. bool add_normal{ true };
  173. for (const Vector3 &vec : distinct_normals) {
  174. if (vec.is_equal_approx(normal)) {
  175. add_normal = false;
  176. }
  177. }
  178. if (add_normal) {
  179. distinct_normals.push_back(normal);
  180. }
  181. }
  182. CHECK_MESSAGE(distinct_normals.size() == 6,
  183. "There are exactly 6 distinct normals in the mesh data.");
  184. // All normals are orthogonal, or pointing in same direction.
  185. bool normal_correct_direction{ true };
  186. for (int rowIndex = 0; rowIndex < distinct_normals.size(); ++rowIndex) {
  187. for (int colIndex = rowIndex + 1; colIndex < distinct_normals.size(); ++colIndex) {
  188. if (!Math::is_equal_approx(distinct_normals[rowIndex].normalized().dot(distinct_normals[colIndex].normalized()), 0) &&
  189. !Math::is_equal_approx(distinct_normals[rowIndex].normalized().dot(distinct_normals[colIndex].normalized()), 1) &&
  190. !Math::is_equal_approx(distinct_normals[rowIndex].normalized().dot(distinct_normals[colIndex].normalized()), -1)) {
  191. normal_correct_direction = false;
  192. break;
  193. }
  194. }
  195. if (!normal_correct_direction) {
  196. break;
  197. }
  198. }
  199. CHECK_MESSAGE(normal_correct_direction,
  200. "All normals are either orthogonal or colinear.");
  201. }
  202. }
  203. } // End box tests
  204. TEST_CASE("[SceneTree][Primitive][Cylinder] Cylinder Primitive") {
  205. Ref<CylinderMesh> cylinder = memnew(CylinderMesh);
  206. SUBCASE("[SceneTree][Primitive][Cylinder] Default values should be valid") {
  207. CHECK(cylinder->get_top_radius() > 0);
  208. CHECK(cylinder->get_bottom_radius() > 0);
  209. CHECK(cylinder->get_height() > 0);
  210. CHECK(cylinder->get_radial_segments() > 0);
  211. CHECK(cylinder->get_rings() >= 0);
  212. }
  213. SUBCASE("[SceneTree][Primitive][Cylinder] Set properties and get them") {
  214. cylinder->set_top_radius(4.3f);
  215. cylinder->set_bottom_radius(1.2f);
  216. cylinder->set_height(9.77f);
  217. cylinder->set_radial_segments(12);
  218. cylinder->set_rings(16);
  219. cylinder->set_cap_top(false);
  220. cylinder->set_cap_bottom(false);
  221. CHECK(cylinder->get_top_radius() == doctest::Approx(4.3f));
  222. CHECK(cylinder->get_bottom_radius() == doctest::Approx(1.2f));
  223. CHECK(cylinder->get_height() == doctest::Approx(9.77f));
  224. CHECK(cylinder->get_radial_segments() == 12);
  225. CHECK(cylinder->get_rings() == 16);
  226. CHECK(!cylinder->is_cap_top());
  227. CHECK(!cylinder->is_cap_bottom());
  228. }
  229. SUBCASE("[SceneTree][Primitive][Cylinder] Ensure num segments is >= 0") {
  230. ERR_PRINT_OFF;
  231. cylinder->set_radial_segments(-12);
  232. cylinder->set_rings(-16);
  233. ERR_PRINT_ON;
  234. CHECK(cylinder->get_radial_segments() >= 0);
  235. CHECK(cylinder->get_rings() >= 0);
  236. }
  237. SUBCASE("[Primitive][Cylinder] Actual cylinder mesh tests (top and bottom radius the same).") {
  238. Array data{};
  239. data.resize(RS::ARRAY_MAX);
  240. real_t radius = .9f;
  241. real_t height = 3.2f;
  242. int radial_segments = 8;
  243. int rings = 5;
  244. bool top_cap = true;
  245. bool bottom_cap = true;
  246. CylinderMesh::create_mesh_array(data, radius, radius, height, radial_segments, rings, top_cap, bottom_cap);
  247. Vector<Vector3> points = data[RS::ARRAY_VERTEX];
  248. Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
  249. SUBCASE("[Primitive][Cylinder] Side points are radius away from y-axis.") {
  250. bool is_radius_correct{ true };
  251. for (int index = 0; index < normals.size(); ++index) {
  252. if (Math::is_equal_approx(normals[index].y, 0)) {
  253. if (!Math::is_equal_approx((points[index] - Vector3(0, points[index].y, 0)).length_squared(), radius * radius)) {
  254. is_radius_correct = false;
  255. break;
  256. }
  257. }
  258. }
  259. CHECK(is_radius_correct);
  260. }
  261. SUBCASE("[Primitive][Cylinder] Only possible normals point in direction of point or in positive/negative y direction.") {
  262. bool is_correct_normals{ true };
  263. for (int index = 0; index < normals.size(); ++index) {
  264. Vector3 yaxis_to_point = points[index] - Vector3(0.f, points[index].y, 0.f);
  265. Vector3 point_to_normal = normals[index].normalized() - yaxis_to_point.normalized();
  266. // std::cout << "<" << point_to_normal.x << ", " << point_to_normal.y << ", " << point_to_normal.z << ">\n";
  267. if (!(point_to_normal.is_equal_approx(Vector3(0, 0, 0))) &&
  268. (!Math::is_equal_approx(Math::abs(normals[index].normalized().y), 1))) {
  269. is_correct_normals = false;
  270. break;
  271. }
  272. }
  273. CHECK(is_correct_normals);
  274. }
  275. SUBCASE("[Primitive][Cylinder] Points on top and bottom are height/2 away from origin.") {
  276. bool is_height_correct{ true };
  277. real_t half_height = 0.5 * height;
  278. for (int index = 0; index < normals.size(); ++index) {
  279. if (Math::is_equal_approx(normals[index].x, 0) &&
  280. Math::is_equal_approx(normals[index].z, 0) &&
  281. normals[index].y > 0) {
  282. if (!Math::is_equal_approx(points[index].y, half_height)) {
  283. is_height_correct = false;
  284. break;
  285. }
  286. }
  287. if (Math::is_equal_approx(normals[index].x, 0) &&
  288. Math::is_equal_approx(normals[index].z, 0) &&
  289. normals[index].y < 0) {
  290. if (!Math::is_equal_approx(points[index].y, -half_height)) {
  291. is_height_correct = false;
  292. break;
  293. }
  294. }
  295. }
  296. CHECK(is_height_correct);
  297. }
  298. SUBCASE("[Primitive][Cylinder] Does mesh obey cap parameters?") {
  299. CylinderMesh::create_mesh_array(data, radius, radius, height, radial_segments, rings, top_cap, false);
  300. points = data[RS::ARRAY_VERTEX];
  301. normals = data[RS::ARRAY_NORMAL];
  302. bool no_bottom_cap{ true };
  303. for (int index = 0; index < normals.size(); ++index) {
  304. if (Math::is_equal_approx(normals[index].x, 0) &&
  305. Math::is_equal_approx(normals[index].z, 0) &&
  306. normals[index].y < 0) {
  307. no_bottom_cap = false;
  308. break;
  309. }
  310. }
  311. CHECK_MESSAGE(no_bottom_cap,
  312. "Check there is no bottom cap.");
  313. CylinderMesh::create_mesh_array(data, radius, radius, height, radial_segments, rings, false, bottom_cap);
  314. points = data[RS::ARRAY_VERTEX];
  315. normals = data[RS::ARRAY_NORMAL];
  316. bool no_top_cap{ true };
  317. for (int index = 0; index < normals.size(); ++index) {
  318. if (Math::is_equal_approx(normals[index].x, 0) &&
  319. Math::is_equal_approx(normals[index].z, 0) &&
  320. normals[index].y > 0) {
  321. no_top_cap = false;
  322. break;
  323. }
  324. }
  325. CHECK_MESSAGE(no_top_cap,
  326. "Check there is no top cap.");
  327. }
  328. }
  329. SUBCASE("[Primitive][Cylinder] Slanted cylinder mesh (top and bottom radius different).") {
  330. Array data{};
  331. data.resize(RS::ARRAY_MAX);
  332. real_t top_radius = 2.f;
  333. real_t bottom_radius = 1.f;
  334. real_t height = 1.f;
  335. int radial_segments = 8;
  336. int rings = 5;
  337. CylinderMesh::create_mesh_array(data, top_radius, bottom_radius, height, radial_segments, rings, false, false);
  338. Vector<Vector3> points = data[RS::ARRAY_VERTEX];
  339. Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
  340. SUBCASE("[Primitive][Cylinder] Side points lie correct distance from y-axis") {
  341. bool is_radius_correct{ true };
  342. for (int index = 0; index < points.size(); ++index) {
  343. real_t radius = ((top_radius - bottom_radius) / height) * (points[index].y - 0.5 * height) + top_radius;
  344. Vector3 distance_to_yaxis = points[index] - Vector3(0.f, points[index].y, 0.f);
  345. if (!Math::is_equal_approx(distance_to_yaxis.length_squared(), radius * radius)) {
  346. is_radius_correct = false;
  347. break;
  348. }
  349. }
  350. CHECK(is_radius_correct);
  351. }
  352. SUBCASE("[Primitive][Cylinder] Normal on side is orthogonal to side tangent vector") {
  353. bool is_normal_correct{ true };
  354. for (int index = 0; index < points.size(); ++index) {
  355. Vector3 yaxis_to_point = points[index] - Vector3(0.f, points[index].y, 0.f);
  356. Vector3 yaxis_to_rb = yaxis_to_point.normalized() * bottom_radius;
  357. Vector3 rb_to_point = yaxis_to_point - yaxis_to_rb;
  358. Vector3 y_to_bottom = -Vector3(0.f, points[index].y + 0.5 * height, 0.f);
  359. Vector3 side_tangent = rb_to_point - y_to_bottom;
  360. if (!Math::is_equal_approx(normals[index].dot(side_tangent), 0)) {
  361. is_normal_correct = false;
  362. break;
  363. }
  364. }
  365. CHECK(is_normal_correct);
  366. }
  367. }
  368. } // End cylinder tests
  369. TEST_CASE("[SceneTree][Primitive][Plane] Plane Primitive") {
  370. Ref<PlaneMesh> plane = memnew(PlaneMesh);
  371. SUBCASE("[SceneTree][Primitive][Plane] Default values should be valid") {
  372. CHECK(plane->get_size().x > 0);
  373. CHECK(plane->get_size().y > 0);
  374. CHECK(plane->get_subdivide_width() >= 0);
  375. CHECK(plane->get_subdivide_depth() >= 0);
  376. CHECK((plane->get_orientation() == PlaneMesh::FACE_X || plane->get_orientation() == PlaneMesh::FACE_Y || plane->get_orientation() == PlaneMesh::FACE_Z));
  377. }
  378. SUBCASE("[SceneTree][Primitive][Plane] Set properties and get them.") {
  379. Size2 size{ 3.2, 1.8 };
  380. Vector3 offset{ -7.3, 0.4, -1.7 };
  381. plane->set_size(size);
  382. plane->set_subdivide_width(15);
  383. plane->set_subdivide_depth(29);
  384. plane->set_center_offset(offset);
  385. plane->set_orientation(PlaneMesh::FACE_X);
  386. CHECK(plane->get_size().is_equal_approx(size));
  387. CHECK(plane->get_subdivide_width() == 15);
  388. CHECK(plane->get_subdivide_depth() == 29);
  389. CHECK(plane->get_center_offset().is_equal_approx(offset));
  390. CHECK(plane->get_orientation() == PlaneMesh::FACE_X);
  391. }
  392. SUBCASE("[SceneTree][Primitive][Plane] Ensure number of segments is >= 0.") {
  393. ERR_PRINT_OFF;
  394. plane->set_subdivide_width(-15);
  395. plane->set_subdivide_depth(-29);
  396. ERR_PRINT_ON;
  397. CHECK(plane->get_subdivide_width() >= 0);
  398. CHECK(plane->get_subdivide_depth() >= 0);
  399. }
  400. }
  401. TEST_CASE("[SceneTree][Primitive][Quad] QuadMesh Primitive") {
  402. Ref<QuadMesh> quad = memnew(QuadMesh);
  403. SUBCASE("[Primitive][Quad] Orientation on initialization is in z direction") {
  404. CHECK(quad->get_orientation() == PlaneMesh::FACE_Z);
  405. }
  406. }
  407. TEST_CASE("[SceneTree][Primitive][Prism] Prism Primitive") {
  408. Ref<PrismMesh> prism = memnew(PrismMesh);
  409. SUBCASE("[Primitive][Prism] There are valid values of properties on initialization.") {
  410. CHECK(prism->get_left_to_right() >= 0);
  411. CHECK(prism->get_size().x >= 0);
  412. CHECK(prism->get_size().y >= 0);
  413. CHECK(prism->get_size().z >= 0);
  414. CHECK(prism->get_subdivide_width() >= 0);
  415. CHECK(prism->get_subdivide_height() >= 0);
  416. CHECK(prism->get_subdivide_depth() >= 0);
  417. }
  418. SUBCASE("[Primitive][Prism] Are able to change prism properties.") {
  419. Vector3 size{ 4.3, 9.1, 0.43 };
  420. prism->set_left_to_right(3.4f);
  421. prism->set_size(size);
  422. prism->set_subdivide_width(36);
  423. prism->set_subdivide_height(5);
  424. prism->set_subdivide_depth(64);
  425. CHECK(prism->get_left_to_right() == doctest::Approx(3.4f));
  426. CHECK(prism->get_size().is_equal_approx(size));
  427. CHECK(prism->get_subdivide_width() == 36);
  428. CHECK(prism->get_subdivide_height() == 5);
  429. CHECK(prism->get_subdivide_depth() == 64);
  430. }
  431. SUBCASE("[Primitive][Prism] Ensure number of segments always >= 0") {
  432. ERR_PRINT_OFF;
  433. prism->set_subdivide_width(-36);
  434. prism->set_subdivide_height(-5);
  435. prism->set_subdivide_depth(-64);
  436. ERR_PRINT_ON;
  437. CHECK(prism->get_subdivide_width() >= 0);
  438. CHECK(prism->get_subdivide_height() >= 0);
  439. CHECK(prism->get_subdivide_depth() >= 0);
  440. }
  441. }
  442. TEST_CASE("[SceneTree][Primitive][Sphere] Sphere Primitive") {
  443. Ref<SphereMesh> sphere = memnew(SphereMesh);
  444. SUBCASE("[Primitive][Sphere] There are valid values of properties on initialization.") {
  445. CHECK(sphere->get_radius() >= 0);
  446. CHECK(sphere->get_height() >= 0);
  447. CHECK(sphere->get_radial_segments() >= 0);
  448. CHECK(sphere->get_rings() >= 0);
  449. }
  450. SUBCASE("[Primitive][Sphere] Are able to change prism properties.") {
  451. sphere->set_radius(3.4f);
  452. sphere->set_height(2.2f);
  453. sphere->set_radial_segments(36);
  454. sphere->set_rings(5);
  455. sphere->set_is_hemisphere(true);
  456. CHECK(sphere->get_radius() == doctest::Approx(3.4f));
  457. CHECK(sphere->get_height() == doctest::Approx(2.2f));
  458. CHECK(sphere->get_radial_segments() == 36);
  459. CHECK(sphere->get_rings() == 5);
  460. CHECK(sphere->get_is_hemisphere());
  461. }
  462. SUBCASE("[Primitive][Sphere] Ensure number of segments always >= 0") {
  463. ERR_PRINT_OFF;
  464. sphere->set_radial_segments(-36);
  465. sphere->set_rings(-5);
  466. ERR_PRINT_ON;
  467. CHECK(sphere->get_radial_segments() >= 0);
  468. CHECK(sphere->get_rings() >= 0);
  469. }
  470. SUBCASE("[Primitive][Sphere] Sphere mesh tests.") {
  471. Array data{};
  472. data.resize(RS::ARRAY_MAX);
  473. real_t radius = 1.1f;
  474. int radial_segments = 8;
  475. int rings = 5;
  476. SphereMesh::create_mesh_array(data, radius, 2 * radius, radial_segments, rings);
  477. Vector<Vector3> points = data[RS::ARRAY_VERTEX];
  478. Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
  479. SUBCASE("[Primitive][Sphere] All points lie radius away from origin.") {
  480. bool is_radius_correct = true;
  481. for (Vector3 point : points) {
  482. if (!Math::is_equal_approx(point.length_squared(), radius * radius)) {
  483. is_radius_correct = false;
  484. break;
  485. }
  486. }
  487. CHECK(is_radius_correct);
  488. }
  489. SUBCASE("[Primitive][Sphere] All normals lie in direction of corresponding point.") {
  490. bool is_normals_correct = true;
  491. for (int index = 0; index < points.size(); ++index) {
  492. if (!Math::is_equal_approx(normals[index].normalized().dot(points[index].normalized()), 1)) {
  493. is_normals_correct = false;
  494. break;
  495. }
  496. }
  497. CHECK(is_normals_correct);
  498. }
  499. }
  500. }
  501. TEST_CASE("[SceneTree][Primitive][Torus] Torus Primitive") {
  502. Ref<TorusMesh> torus = memnew(TorusMesh);
  503. Ref<PrimitiveMesh> prim = memnew(PrimitiveMesh);
  504. SUBCASE("[Primitive][Torus] There are valid values of properties on initialization.") {
  505. CHECK(torus->get_inner_radius() > 0);
  506. CHECK(torus->get_outer_radius() > 0);
  507. CHECK(torus->get_rings() >= 0);
  508. CHECK(torus->get_ring_segments() >= 0);
  509. }
  510. SUBCASE("[Primitive][Torus] Are able to change properties.") {
  511. torus->set_inner_radius(3.2f);
  512. torus->set_outer_radius(9.5f);
  513. torus->set_rings(19);
  514. torus->set_ring_segments(43);
  515. CHECK(torus->get_inner_radius() == doctest::Approx(3.2f));
  516. CHECK(torus->get_outer_radius() == doctest::Approx(9.5f));
  517. CHECK(torus->get_rings() == 19);
  518. CHECK(torus->get_ring_segments() == 43);
  519. }
  520. }
  521. TEST_CASE("[SceneTree][Primitive][TubeTrail] TubeTrail Primitive") {
  522. Ref<TubeTrailMesh> tube = memnew(TubeTrailMesh);
  523. SUBCASE("[Primitive][TubeTrail] There are valid values of properties on initialization.") {
  524. CHECK(tube->get_radius() > 0);
  525. CHECK(tube->get_radial_steps() >= 0);
  526. CHECK(tube->get_sections() >= 0);
  527. CHECK(tube->get_section_length() > 0);
  528. CHECK(tube->get_section_rings() >= 0);
  529. CHECK(tube->get_curve().is_null());
  530. CHECK(tube->get_builtin_bind_pose_count() >= 0);
  531. }
  532. SUBCASE("[Primitive][TubeTrail] Are able to change properties.") {
  533. tube->set_radius(7.2f);
  534. tube->set_radial_steps(9);
  535. tube->set_sections(33);
  536. tube->set_section_length(5.5f);
  537. tube->set_section_rings(12);
  538. Ref<Curve> curve = memnew(Curve);
  539. tube->set_curve(curve);
  540. CHECK(tube->get_radius() == doctest::Approx(7.2f));
  541. CHECK(tube->get_section_length() == doctest::Approx(5.5f));
  542. CHECK(tube->get_radial_steps() == 9);
  543. CHECK(tube->get_sections() == 33);
  544. CHECK(tube->get_section_rings() == 12);
  545. CHECK(tube->get_curve() == curve);
  546. }
  547. SUBCASE("[Primitive][TubeTrail] Setting same curve more than once, it remains the same.") {
  548. Ref<Curve> curve = memnew(Curve);
  549. tube->set_curve(curve);
  550. tube->set_curve(curve);
  551. tube->set_curve(curve);
  552. CHECK(tube->get_curve() == curve);
  553. }
  554. SUBCASE("[Primitive][TubeTrail] Setting curve, then changing to different curve.") {
  555. Ref<Curve> curve1 = memnew(Curve);
  556. Ref<Curve> curve2 = memnew(Curve);
  557. tube->set_curve(curve1);
  558. CHECK(tube->get_curve() == curve1);
  559. tube->set_curve(curve2);
  560. CHECK(tube->get_curve() == curve2);
  561. }
  562. SUBCASE("[Primitive][TubeTrail] Assign same curve to two different tube trails") {
  563. Ref<TubeTrailMesh> tube2 = memnew(TubeTrailMesh);
  564. Ref<Curve> curve = memnew(Curve);
  565. tube->set_curve(curve);
  566. tube2->set_curve(curve);
  567. CHECK(tube->get_curve() == curve);
  568. CHECK(tube2->get_curve() == curve);
  569. }
  570. }
  571. TEST_CASE("[SceneTree][Primitive][RibbonTrail] RibbonTrail Primitive") {
  572. Ref<RibbonTrailMesh> ribbon = memnew(RibbonTrailMesh);
  573. SUBCASE("[Primitive][RibbonTrail] There are valid values of properties on initialization.") {
  574. CHECK(ribbon->get_size() > 0);
  575. CHECK(ribbon->get_sections() >= 0);
  576. CHECK(ribbon->get_section_length() > 0);
  577. CHECK(ribbon->get_section_segments() >= 0);
  578. CHECK(ribbon->get_builtin_bind_pose_count() >= 0);
  579. CHECK(ribbon->get_curve().is_null());
  580. CHECK((ribbon->get_shape() == RibbonTrailMesh::SHAPE_CROSS ||
  581. ribbon->get_shape() == RibbonTrailMesh::SHAPE_FLAT));
  582. }
  583. SUBCASE("[Primitive][RibbonTrail] Able to change properties.") {
  584. Ref<Curve> curve = memnew(Curve);
  585. ribbon->set_size(4.3f);
  586. ribbon->set_sections(16);
  587. ribbon->set_section_length(1.3f);
  588. ribbon->set_section_segments(9);
  589. ribbon->set_curve(curve);
  590. CHECK(ribbon->get_size() == doctest::Approx(4.3f));
  591. CHECK(ribbon->get_section_length() == doctest::Approx(1.3f));
  592. CHECK(ribbon->get_sections() == 16);
  593. CHECK(ribbon->get_section_segments() == 9);
  594. CHECK(ribbon->get_curve() == curve);
  595. }
  596. SUBCASE("[Primitive][RibbonTrail] Setting same curve more than once, it remains the same.") {
  597. Ref<Curve> curve = memnew(Curve);
  598. ribbon->set_curve(curve);
  599. ribbon->set_curve(curve);
  600. ribbon->set_curve(curve);
  601. CHECK(ribbon->get_curve() == curve);
  602. }
  603. SUBCASE("[Primitive][RibbonTrail] Setting curve, then changing to different curve.") {
  604. Ref<Curve> curve1 = memnew(Curve);
  605. Ref<Curve> curve2 = memnew(Curve);
  606. ribbon->set_curve(curve1);
  607. CHECK(ribbon->get_curve() == curve1);
  608. ribbon->set_curve(curve2);
  609. CHECK(ribbon->get_curve() == curve2);
  610. }
  611. SUBCASE("[Primitive][RibbonTrail] Assign same curve to two different ribbon trails") {
  612. Ref<RibbonTrailMesh> ribbon2 = memnew(RibbonTrailMesh);
  613. Ref<Curve> curve = memnew(Curve);
  614. ribbon->set_curve(curve);
  615. ribbon2->set_curve(curve);
  616. CHECK(ribbon->get_curve() == curve);
  617. CHECK(ribbon2->get_curve() == curve);
  618. }
  619. }
  620. TEST_CASE("[SceneTree][Primitive][Text] Text Primitive") {
  621. Ref<TextMesh> text = memnew(TextMesh);
  622. SUBCASE("[Primitive][Text] There are valid values of properties on initialization.") {
  623. CHECK((text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_CENTER ||
  624. text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_LEFT ||
  625. text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_RIGHT ||
  626. text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_FILL));
  627. CHECK((text->get_vertical_alignment() == VERTICAL_ALIGNMENT_BOTTOM ||
  628. text->get_vertical_alignment() == VERTICAL_ALIGNMENT_TOP ||
  629. text->get_vertical_alignment() == VERTICAL_ALIGNMENT_CENTER ||
  630. text->get_vertical_alignment() == VERTICAL_ALIGNMENT_FILL));
  631. CHECK(text->get_font().is_null());
  632. CHECK(text->get_font_size() > 0);
  633. CHECK(text->get_line_spacing() >= 0);
  634. CHECK((text->get_autowrap_mode() == TextServer::AUTOWRAP_OFF ||
  635. text->get_autowrap_mode() == TextServer::AUTOWRAP_ARBITRARY ||
  636. text->get_autowrap_mode() == TextServer::AUTOWRAP_WORD ||
  637. text->get_autowrap_mode() == TextServer::AUTOWRAP_WORD_SMART));
  638. CHECK((text->get_text_direction() == TextServer::DIRECTION_AUTO ||
  639. text->get_text_direction() == TextServer::DIRECTION_LTR ||
  640. text->get_text_direction() == TextServer::DIRECTION_RTL));
  641. CHECK((text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_DEFAULT ||
  642. text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_URI ||
  643. text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_FILE ||
  644. text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_EMAIL ||
  645. text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_LIST ||
  646. text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_GDSCRIPT ||
  647. text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_CUSTOM));
  648. CHECK(text->get_structured_text_bidi_override_options().size() >= 0);
  649. CHECK(text->get_width() > 0);
  650. CHECK(text->get_depth() > 0);
  651. CHECK(text->get_curve_step() > 0);
  652. CHECK(text->get_pixel_size() > 0);
  653. }
  654. SUBCASE("[Primitive][Text] Change the properties of the mesh.") {
  655. Ref<Font> font = memnew(Font);
  656. Array options{};
  657. Point2 offset{ 30.8, 104.23 };
  658. text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
  659. text->set_vertical_alignment(VERTICAL_ALIGNMENT_BOTTOM);
  660. text->set_text("Hello");
  661. text->set_font(font);
  662. text->set_font_size(12);
  663. text->set_line_spacing(1.7f);
  664. text->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
  665. text->set_text_direction(TextServer::DIRECTION_RTL);
  666. text->set_language("French");
  667. text->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_EMAIL);
  668. text->set_structured_text_bidi_override_options(options);
  669. text->set_uppercase(true);
  670. real_t width{ 0.6 };
  671. real_t depth{ 1.7 };
  672. real_t pixel_size{ 2.8 };
  673. real_t curve_step{ 4.8 };
  674. text->set_width(width);
  675. text->set_depth(depth);
  676. text->set_curve_step(curve_step);
  677. text->set_pixel_size(pixel_size);
  678. text->set_offset(offset);
  679. CHECK(text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_RIGHT);
  680. CHECK(text->get_vertical_alignment() == VERTICAL_ALIGNMENT_BOTTOM);
  681. CHECK(text->get_text_direction() == TextServer::DIRECTION_RTL);
  682. CHECK(text->get_text() == "Hello");
  683. CHECK(text->get_font() == font);
  684. CHECK(text->get_font_size() == 12);
  685. CHECK(text->get_autowrap_mode() == TextServer::AUTOWRAP_WORD_SMART);
  686. CHECK(text->get_language() == "French");
  687. CHECK(text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_EMAIL);
  688. CHECK(text->get_structured_text_bidi_override_options() == options);
  689. CHECK(text->is_uppercase() == true);
  690. CHECK(text->get_offset() == offset);
  691. CHECK(text->get_line_spacing() == doctest::Approx(1.7f));
  692. CHECK(text->get_width() == doctest::Approx(width));
  693. CHECK(text->get_depth() == doctest::Approx(depth));
  694. CHECK(text->get_curve_step() == doctest::Approx(curve_step));
  695. CHECK(text->get_pixel_size() == doctest::Approx(pixel_size));
  696. }
  697. SUBCASE("[Primitive][Text] Set objects multiple times.") {
  698. Ref<Font> font = memnew(Font);
  699. Array options{};
  700. Point2 offset{ 30.8, 104.23 };
  701. text->set_font(font);
  702. text->set_font(font);
  703. text->set_font(font);
  704. text->set_structured_text_bidi_override_options(options);
  705. text->set_structured_text_bidi_override_options(options);
  706. text->set_structured_text_bidi_override_options(options);
  707. text->set_offset(offset);
  708. text->set_offset(offset);
  709. text->set_offset(offset);
  710. CHECK(text->get_font() == font);
  711. CHECK(text->get_structured_text_bidi_override_options() == options);
  712. CHECK(text->get_offset() == offset);
  713. }
  714. SUBCASE("[Primitive][Text] Set then change objects.") {
  715. Ref<Font> font1 = memnew(Font);
  716. Ref<Font> font2 = memnew(Font);
  717. Array options1{};
  718. Array options2{};
  719. Point2 offset1{ 30.8, 104.23 };
  720. Point2 offset2{ -30.8, -104.23 };
  721. text->set_font(font1);
  722. text->set_structured_text_bidi_override_options(options1);
  723. text->set_offset(offset1);
  724. CHECK(text->get_font() == font1);
  725. CHECK(text->get_structured_text_bidi_override_options() == options1);
  726. CHECK(text->get_offset() == offset1);
  727. text->set_font(font2);
  728. text->set_structured_text_bidi_override_options(options2);
  729. text->set_offset(offset2);
  730. CHECK(text->get_font() == font2);
  731. CHECK(text->get_structured_text_bidi_override_options() == options2);
  732. CHECK(text->get_offset() == offset2);
  733. }
  734. SUBCASE("[Primitive][Text] Assign same font to two Textmeshes.") {
  735. Ref<TextMesh> text2 = memnew(TextMesh);
  736. Ref<Font> font = memnew(Font);
  737. text->set_font(font);
  738. text2->set_font(font);
  739. CHECK(text->get_font() == font);
  740. CHECK(text2->get_font() == font);
  741. }
  742. }
  743. } // namespace TestPrimitives
  744. #endif // TEST_PRIMITIVES_H