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