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