3#define DOCTEST_CONFIG_IMPLEMENT
5#include "doctest_utils.h"
12DOCTEST_TEST_CASE(
"PlantArchitecture Constructor") {
17DOCTEST_TEST_CASE(
"ShootParameters defineChildShootTypes valid input") {
19 std::vector<std::string> labels = {
"typeA",
"typeB"};
20 std::vector<float> probabilities = {0.4f, 0.6f};
24DOCTEST_TEST_CASE(
"ShootParameters defineChildShootTypes size mismatch") {
27 std::vector<std::string> labels = {
"typeA",
"typeB"};
28 std::vector<float> probabilities = {0.4f};
32DOCTEST_TEST_CASE(
"ShootParameters defineChildShootTypes empty vectors") {
35 std::vector<std::string> labels = {};
36 std::vector<float> probabilities = {};
40DOCTEST_TEST_CASE(
"ShootParameters defineChildShootTypes probabilities sum not equal to 1") {
43 std::vector<std::string> labels = {
"typeA",
"typeB"};
44 std::vector<float> probabilities = {0.3f, 0.6f};
48DOCTEST_TEST_CASE(
"PlantArchitecture defineShootType") {
52 DOCTEST_CHECK_NOTHROW(pa_test.defineShootType(
"newShootType", sp_define));
55DOCTEST_TEST_CASE(
"LeafPrototype Constructor") {
59 DOCTEST_CHECK(lp_test.subdivisions == 1);
60 DOCTEST_CHECK(lp_test.unique_prototypes == 1);
61 DOCTEST_CHECK(lp_test.leaf_offset.x == doctest::Approx(0.0f).epsilon(err_tol));
62 DOCTEST_CHECK(lp_test.leaf_offset.y == doctest::Approx(0.0f).epsilon(err_tol));
63 DOCTEST_CHECK(lp_test.leaf_offset.z == doctest::Approx(0.0f).epsilon(err_tol));
66DOCTEST_TEST_CASE(
"PhytomerParameters Constructor") {
72DOCTEST_TEST_CASE(
"Plant Library Model Building - almond") {
75 plantarchitecture.disableMessages();
76 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"almond"));
77 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
80DOCTEST_TEST_CASE(
"Plant Library Model Building - apple") {
83 plantarchitecture.disableMessages();
84 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"apple"));
85 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
88DOCTEST_TEST_CASE(
"Plant Library Model Building - asparagus") {
91 plantarchitecture.disableMessages();
92 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"asparagus"));
93 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
96DOCTEST_TEST_CASE(
"Plant Library Model Building - bindweed") {
99 plantarchitecture.disableMessages();
100 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bindweed"));
101 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
104DOCTEST_TEST_CASE(
"Plant Library Model Building - bean") {
107 plantarchitecture.disableMessages();
108 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
109 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
112DOCTEST_TEST_CASE(
"Plant Library Model Building - cheeseweed") {
115 plantarchitecture.disableMessages();
116 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"cheeseweed"));
117 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
120DOCTEST_TEST_CASE(
"Plant Library Model Building - cowpea") {
123 plantarchitecture.disableMessages();
124 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"cowpea"));
125 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
128DOCTEST_TEST_CASE(
"Plant Library Model Building - grapevine_VSP") {
131 plantarchitecture.disableMessages();
132 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP"));
133 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
136DOCTEST_TEST_CASE(
"Plant Library Model Building - maize") {
139 plantarchitecture.disableMessages();
140 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"maize"));
141 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
144DOCTEST_TEST_CASE(
"Plant Library Model Building - olive") {
147 plantarchitecture.disableMessages();
148 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"olive"));
149 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
152DOCTEST_TEST_CASE(
"Plant Library Model Building - pistachio") {
155 plantarchitecture.disableMessages();
156 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"pistachio"));
157 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
160DOCTEST_TEST_CASE(
"Plant Library Model Building - puncturevine") {
163 plantarchitecture.disableMessages();
164 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"puncturevine"));
165 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
168DOCTEST_TEST_CASE(
"Plant Library Model Building - easternredbud") {
171 plantarchitecture.disableMessages();
172 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"easternredbud"));
173 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
176DOCTEST_TEST_CASE(
"Plant Library Model Building - rice") {
179 plantarchitecture.disableMessages();
180 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"rice"));
181 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
184DOCTEST_TEST_CASE(
"Plant Library Model Building - butterlettuce") {
187 plantarchitecture.disableMessages();
188 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"butterlettuce"));
189 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
192DOCTEST_TEST_CASE(
"Plant Library Model Building - sorghum") {
195 plantarchitecture.disableMessages();
196 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"sorghum"));
197 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
200DOCTEST_TEST_CASE(
"Plant Library Model Building - soybean") {
203 plantarchitecture.disableMessages();
204 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"soybean"));
205 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
208DOCTEST_TEST_CASE(
"Plant Library Model Building - strawberry") {
211 plantarchitecture.disableMessages();
212 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"strawberry"));
213 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
216DOCTEST_TEST_CASE(
"Plant Library Model Building - sugarbeet") {
219 plantarchitecture.disableMessages();
220 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"sugarbeet"));
221 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
224DOCTEST_TEST_CASE(
"Plant Library Model Building - tomato") {
227 plantarchitecture.disableMessages();
228 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"tomato"));
229 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
232DOCTEST_TEST_CASE(
"Plant Library Model Building - walnut") {
235 plantarchitecture.disableMessages();
236 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"walnut"));
237 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
240DOCTEST_TEST_CASE(
"Plant Library Model Building - wheat") {
243 plantarchitecture.disableMessages();
244 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"wheat"));
245 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
248DOCTEST_TEST_CASE(
"PlantArchitecture writeTreeQSM") {
251 plantarchitecture.disableMessages();
254 plantarchitecture.loadPlantModelFromLibrary(
"bean");
255 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 50);
258 std::string filename =
"test_plant_qsm.txt";
259 DOCTEST_CHECK_NOTHROW(plantarchitecture.writeQSMCylinderFile(plantID, filename));
262 std::ifstream file(filename);
263 DOCTEST_CHECK(file.good());
266 std::string header_line;
267 std::getline(file, header_line);
270 DOCTEST_CHECK(header_line.find(
"radius (m)") != std::string::npos);
271 DOCTEST_CHECK(header_line.find(
"length (m)") != std::string::npos);
272 DOCTEST_CHECK(header_line.find(
"start_point") != std::string::npos);
273 DOCTEST_CHECK(header_line.find(
"axis_direction") != std::string::npos);
274 DOCTEST_CHECK(header_line.find(
"branch") != std::string::npos);
275 DOCTEST_CHECK(header_line.find(
"branch_order") != std::string::npos);
278 std::string data_line;
279 bool has_data =
static_cast<bool>(std::getline(file, data_line));
280 DOCTEST_CHECK(has_data);
284 size_t tab_count = std::count(data_line.begin(), data_line.end(),
'\t');
285 DOCTEST_CHECK(tab_count >= 12);
291 std::remove(filename.c_str());
295DOCTEST_TEST_CASE(
"PlantArchitecture writeTreeQSM invalid plant") {
299 plantarchitecture.disableMessages();
302 DOCTEST_CHECK_THROWS(plantarchitecture.writeQSMCylinderFile(999,
"invalid_plant.txt"));
305DOCTEST_TEST_CASE(
"PlantArchitecture pruneSolidBoundaryCollisions") {
308 plantarchitecture.disableMessages();
311 plantarchitecture.enableSoftCollisionAvoidance();
314 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
317 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
318 plantarchitecture.advanceTime(plantID, 15);
321 std::vector<uint> objects_before_boundaries = plantarchitecture.getAllObjectIDs();
322 uint count_before_boundaries = objects_before_boundaries.size();
325 DOCTEST_CHECK(count_before_boundaries > 0);
329 std::vector<uint> boundary_UUIDs;
330 for (
int i = -2; i <= 2; i++) {
331 for (
int j = -2; j <= 2; j++) {
338 plantarchitecture.enableSolidObstacleAvoidance(boundary_UUIDs, 0.2f);
342 plantarchitecture.advanceTime(plantID, 0.1f);
345 std::vector<uint> final_objects = plantarchitecture.getAllObjectIDs();
346 uint final_count = final_objects.size();
353 DOCTEST_CHECK(final_count > 0);
356DOCTEST_TEST_CASE(
"PlantArchitecture pruneSolidBoundaryCollisions no boundaries") {
359 plantarchitecture.disableMessages();
362 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
365 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
366 plantarchitecture.advanceTime(plantID, 5);
369 std::vector<uint> initial_objects = plantarchitecture.getAllObjectIDs();
370 uint initial_count = initial_objects.size();
373 plantarchitecture.advanceTime(plantID, 2);
376 std::vector<uint> final_objects = plantarchitecture.getAllObjectIDs();
377 uint final_count = final_objects.size();
379 DOCTEST_CHECK(final_count >= initial_count);
382DOCTEST_TEST_CASE(
"PlantArchitecture hard collision avoidance base stem protection") {
385 plantarchitecture.disableMessages();
388 plantarchitecture.enableSoftCollisionAvoidance();
391 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
395 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, -0.05f), 0);
398 std::vector<uint> ground_UUIDs;
401 for (
int i = -2; i <= 2; i++) {
402 for (
int j = -2; j <= 2; j++) {
404 make_vec3((i + 1) * 0.2f, j * 0.2f, 0.0f),
make_vec3(i * 0.2f, (j + 1) * 0.2f, 0.0f)));
410 plantarchitecture.enableSolidObstacleAvoidance(ground_UUIDs, 0.3f);
414 plantarchitecture.advanceTime(plantID, 10);
417 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
418 DOCTEST_CHECK(plant_objects.size() > 0);
423 uint total_objects = 0;
425 for (
uint objID: plant_objects) {
428 vec3 min_corner, max_corner;
431 vec3 object_center = (min_corner + max_corner) / 2.0f;
433 center_of_mass = center_of_mass + object_center;
438 if (total_objects > 0) {
439 center_of_mass = center_of_mass / float(total_objects);
443 DOCTEST_CHECK(center_of_mass.
z > -0.075f);
448 DOCTEST_CHECK(center_of_mass.
z > -0.075f);
453 DOCTEST_CHECK(plant_objects.size() >= 5);
456DOCTEST_TEST_CASE(
"PlantArchitecture enableSolidObstacleAvoidance fruit adjustment control") {
459 plantarchitecture.disableMessages();
462 std::vector<uint> obstacle_UUIDs;
467 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f));
470 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f,
true));
473 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f,
false));
476 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.3f,
false));
479DOCTEST_TEST_CASE(
"PlantArchitecture base stem protection with short internodes") {
482 plantarchitecture.disableMessages();
485 plantarchitecture.enableSoftCollisionAvoidance();
488 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
491 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
494 plantarchitecture.advanceTime(plantID, 2);
497 std::vector<uint> ground_UUIDs;
498 for (
int i = -1; i <= 1; i++) {
499 for (
int j = -1; j <= 1; j++) {
501 make_vec3((i + 1) * 0.3f, j * 0.3f, -0.01f),
make_vec3(i * 0.3f, (j + 1) * 0.3f, -0.01f)));
507 plantarchitecture.enableSolidObstacleAvoidance(ground_UUIDs, 0.2f);
511 plantarchitecture.advanceTime(plantID, 8);
514 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
515 DOCTEST_CHECK(plant_objects.size() > 0);
519 uint total_objects = 0;
521 for (
uint objID: plant_objects) {
523 vec3 min_corner, max_corner;
525 vec3 object_center = (min_corner + max_corner) / 2.0f;
526 center_of_mass = center_of_mass + object_center;
531 if (total_objects > 0) {
532 center_of_mass = center_of_mass / float(total_objects);
535 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
539 DOCTEST_CHECK(center_of_mass.
z > 0.05f);
543 DOCTEST_CHECK(plant_objects.size() >= 10);
546DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Basic Functionality") {
549 plantarchitecture.disableMessages();
552 plantarchitecture.enableSoftCollisionAvoidance();
555 std::vector<vec3> attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
558 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points, 60.0f, 0.15f, 0.7f));
561 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(0.0f, 0.1f, 0.5f));
562 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(190.0f, 0.1f, 0.5f));
565 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.0f, 0.5f));
566 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, -0.1f, 0.5f));
569 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.1f, -0.1f));
570 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.1f, 1.1f));
573 std::vector<vec3> new_attraction_points = {
make_vec3(2.0f, 0.0f, 2.0f)};
574 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(new_attraction_points));
577 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints());
580 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(new_attraction_points));
583DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Independent of Collision Detection") {
586 plantarchitecture.disableMessages();
588 std::vector<vec3> attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f)};
591 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points));
594DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Empty Vector") {
597 plantarchitecture.disableMessages();
599 std::vector<vec3> empty_attraction_points;
602 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(empty_attraction_points));
605 std::vector<vec3> valid_points = {
make_vec3(1.0f, 0.0f, 1.0f)};
606 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(valid_points));
609 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(empty_attraction_points));
612DOCTEST_TEST_CASE(
"PlantArchitecture Native Attraction Point Cone Detection") {
615 plantarchitecture.disableMessages();
618 std::vector<vec3> attraction_points = {
626 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points, 60.0f, 3.0f, 0.7f));
631 vec3 direction_to_closest;
633 bool found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 60.0f, direction_to_closest);
634 DOCTEST_CHECK(found);
638 float dot_product = direction_to_closest * expected_direction;
639 DOCTEST_CHECK(dot_product > 0.99f);
642 look_direction =
make_vec3(1.0f, 0.0f, 0.0f);
643 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 30.0f, direction_to_closest);
649 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, -1.0f, 60.0f, direction_to_closest);
650 DOCTEST_CHECK(!found);
652 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 0.0f, direction_to_closest);
653 DOCTEST_CHECK(!found);
655 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 180.0f, direction_to_closest);
656 DOCTEST_CHECK(!found);
659DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Plant Growth Integration") {
662 plantarchitecture.disableMessages();
665 plantarchitecture.enableSoftCollisionAvoidance();
668 std::vector<vec3> attraction_points = {
674 plantarchitecture.enableAttractionPoints(attraction_points, 80.0f, 0.2f, 0.6f);
677 plantarchitecture.loadPlantModelFromLibrary(
"bean");
678 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
681 plantarchitecture.advanceTime(plantID, 5);
684 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
685 DOCTEST_CHECK(plant_objects.size() > 0);
689 uint total_objects = 0;
691 for (
uint objID: plant_objects) {
693 vec3 min_corner, max_corner;
695 vec3 object_center = (min_corner + max_corner) / 2.0f;
696 center_of_mass = center_of_mass + object_center;
701 if (total_objects > 0) {
702 center_of_mass = center_of_mass / float(total_objects);
706 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
710 float lateral_distance = sqrt(center_of_mass.
x * center_of_mass.
x + center_of_mass.
y * center_of_mass.
y);
711 DOCTEST_CHECK(lateral_distance >= 0.0f);
715 plantarchitecture.disableAttractionPoints();
718 plantarchitecture.advanceTime(plantID, 3);
721 std::vector<uint> final_plant_objects = plantarchitecture.getAllObjectIDs();
722 DOCTEST_CHECK(final_plant_objects.size() >= plant_objects.size());
725DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Priority Over Collision Avoidance") {
728 plantarchitecture.disableMessages();
731 std::vector<uint> obstacle_UUIDs;
732 for (
int i = 0; i < 3; i++) {
733 for (
int j = 0; j < 3; j++) {
734 obstacle_UUIDs.push_back(
735 context.
addTriangle(
make_vec3(i * 0.3f + 0.5f, j * 0.3f + 0.5f, 0.5f + i * 0.1f),
make_vec3((i + 1) * 0.3f + 0.5f, (j + 1) * 0.3f + 0.5f, 0.5f + i * 0.1f),
make_vec3((i + 1) * 0.3f + 0.5f, j * 0.3f + 0.5f, 0.5f + i * 0.1f)));
740 plantarchitecture.enableSoftCollisionAvoidance(obstacle_UUIDs);
743 std::vector<vec3> attraction_points = {
748 plantarchitecture.enableAttractionPoints(attraction_points, 90.0f, 0.3f, 0.8f);
751 plantarchitecture.loadPlantModelFromLibrary(
"bean");
752 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0.3f, 0.3f, 0), 0);
755 plantarchitecture.advanceTime(plantID, 4);
758 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
759 DOCTEST_CHECK(plant_objects.size() > 0);
763 uint total_objects = 0;
765 for (
uint objID: plant_objects) {
767 vec3 min_corner, max_corner;
769 vec3 object_center = (min_corner + max_corner) / 2.0f;
770 center_of_mass = center_of_mass + object_center;
775 if (total_objects > 0) {
776 center_of_mass = center_of_mass / float(total_objects);
779 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
786DOCTEST_TEST_CASE(
"PlantArchitecture Hard Obstacle Avoidance Takes Priority Over Attraction Points") {
789 plantarchitecture.disableMessages();
792 std::vector<uint> solid_obstacle_UUIDs;
793 for (
int i = -1; i <= 1; i++) {
794 for (
int j = -1; j <= 1; j++) {
800 plantarchitecture.enableSoftCollisionAvoidance();
803 plantarchitecture.enableSolidObstacleAvoidance(solid_obstacle_UUIDs, 0.15f);
806 std::vector<vec3> attraction_points = {
811 plantarchitecture.enableAttractionPoints(attraction_points, 70.0f, 0.1f, 0.9f);
814 plantarchitecture.loadPlantModelFromLibrary(
"bean");
815 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
818 plantarchitecture.advanceTime(plantID, 3);
821 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
822 DOCTEST_CHECK(plant_objects.size() > 0);
826 uint total_objects = 0;
828 for (
uint objID: plant_objects) {
830 vec3 min_corner, max_corner;
832 vec3 object_center = (min_corner + max_corner) / 2.0f;
833 center_of_mass = center_of_mass + object_center;
838 if (total_objects > 0) {
839 center_of_mass = center_of_mass / float(total_objects);
842 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
846 DOCTEST_CHECK(center_of_mass.
z > 0.005f);
850DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points with Surface Following") {
853 plantarchitecture.disableMessages();
856 std::vector<uint> wall_obstacle_UUIDs;
857 std::vector<vec3> wall_attraction_points;
860 for (
int i = 0; i < 5; i++) {
861 for (
int j = 0; j < 3; j++) {
863 wall_obstacle_UUIDs.push_back(context.
addTriangle(
make_vec3(0.3f, i * 0.05f, j * 0.05f),
make_vec3(0.3f, (i + 1) * 0.05f, (j + 1) * 0.05f),
make_vec3(0.3f, (i + 1) * 0.05f, j * 0.05f)));
866 wall_attraction_points.push_back(
make_vec3(0.29f, i * 0.05f + 0.025f, j * 0.05f + 0.025f));
871 plantarchitecture.enableSoftCollisionAvoidance();
874 plantarchitecture.enableSolidObstacleAvoidance(wall_obstacle_UUIDs, 0.05f);
878 plantarchitecture.enableAttractionPoints(wall_attraction_points, 60.0f, 0.1f, 0.8f);
879 plantarchitecture.setAttractionParameters(60.0f, 0.1f, 0.8f, 0.5f);
882 plantarchitecture.loadPlantModelFromLibrary(
"bean");
883 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
886 plantarchitecture.advanceTime(plantID, 4);
889 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
890 DOCTEST_CHECK(plant_objects.size() > 0);
894 uint total_objects = 0;
896 for (
uint objID: plant_objects) {
898 vec3 min_corner, max_corner;
900 vec3 object_center = (min_corner + max_corner) / 2.0f;
901 center_of_mass = center_of_mass + object_center;
906 if (total_objects > 0) {
907 center_of_mass = center_of_mass / float(total_objects);
910 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
921DOCTEST_TEST_CASE(
"PlantArchitecture Smooth Hard Obstacle Avoidance") {
924 plantarchitecture.disableMessages();
926 plantarchitecture.enableSoftCollisionAvoidance();
927 plantarchitecture.loadPlantModelFromLibrary(
"bean");
930 std::vector<uint> obstacle_UUIDs;
934 for (
int i = 0; i < 4; i++) {
935 float z_height = 0.1f + i * 0.05f;
938 float x_distance = 0.05f + i * 0.02f;
944 plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.25f);
946 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
947 plantarchitecture.advanceTime(plantID, 8);
949 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
950 DOCTEST_CHECK(plant_objects.size() > 0);
954 uint total_objects = 0;
956 for (
uint objID: plant_objects) {
958 vec3 min_corner, max_corner;
960 vec3 object_center = (min_corner + max_corner) / 2.0f;
961 center_of_mass = center_of_mass + object_center;
966 if (total_objects > 0) {
967 center_of_mass = center_of_mass / float(total_objects);
970 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
974 DOCTEST_CHECK(center_of_mass.
x <= 0.01f);
982DOCTEST_TEST_CASE(
"PlantArchitecture Hard Obstacle Avoidance Buffer Zone") {
985 plantarchitecture.disableMessages();
987 plantarchitecture.enableSoftCollisionAvoidance();
988 plantarchitecture.loadPlantModelFromLibrary(
"bean");
991 std::vector<uint> post_UUIDs;
992 float post_radius = 0.02f;
993 float post_height = 0.5f;
997 for (
int i = 0; i < segments; i++) {
998 float theta1 = 2.0f *
M_PI * float(i) / float(segments);
999 float theta2 = 2.0f *
M_PI * float(i + 1) / float(segments);
1001 vec3 p1_bottom =
make_vec3(0.1f + post_radius * cos(theta1), post_radius * sin(theta1), 0);
1002 vec3 p2_bottom =
make_vec3(0.1f + post_radius * cos(theta2), post_radius * sin(theta2), 0);
1003 vec3 p1_top =
make_vec3(0.1f + post_radius * cos(theta1), post_radius * sin(theta1), post_height);
1004 vec3 p2_top =
make_vec3(0.1f + post_radius * cos(theta2), post_radius * sin(theta2), post_height);
1007 post_UUIDs.push_back(context.
addTriangle(p1_bottom, p2_bottom, p1_top));
1008 post_UUIDs.push_back(context.
addTriangle(p2_bottom, p2_top, p1_top));
1012 float detection_distance = 0.2f;
1013 float expected_buffer = detection_distance * 0.05f;
1015 plantarchitecture.enableSolidObstacleAvoidance(post_UUIDs, detection_distance);
1018 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1019 plantarchitecture.advanceTime(plantID, 8);
1021 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
1022 DOCTEST_CHECK(plant_objects.size() > 0);
1025 float min_distance_to_post = std::numeric_limits<float>::max();
1028 for (
uint objID: plant_objects) {
1030 vec3 min_corner, max_corner;
1034 vec3 corners[8] = {
make_vec3(min_corner.
x, min_corner.
y, min_corner.
z),
make_vec3(max_corner.
x, min_corner.
y, min_corner.
z),
make_vec3(min_corner.
x, max_corner.
y, min_corner.
z),
make_vec3(min_corner.
x, min_corner.
y, max_corner.
z),
1035 make_vec3(max_corner.
x, max_corner.
y, min_corner.
z),
make_vec3(max_corner.
x, min_corner.
y, max_corner.
z),
make_vec3(min_corner.
x, max_corner.
y, max_corner.
z),
make_vec3(max_corner.
x, max_corner.
y, max_corner.
z)};
1037 for (
int i = 0; i < 8; i++) {
1038 float distance = (corners[i] - post_center).magnitude();
1039 min_distance_to_post = std::min(min_distance_to_post, distance);
1045 float expected_min_distance = post_radius + expected_buffer;
1046 DOCTEST_CHECK(min_distance_to_post >= expected_min_distance * 0.8f);
1050 uint plant_object_count = 0;
1052 for (
uint objID: plant_objects) {
1054 vec3 min_corner, max_corner;
1056 vec3 object_center = (min_corner + max_corner) / 2.0f;
1057 plant_center = plant_center + object_center;
1058 plant_object_count++;
1062 if (plant_object_count > 0) {
1063 plant_center = plant_center / float(plant_object_count);
1064 DOCTEST_CHECK(plant_center.
z > 0.01f);
1068 DOCTEST_CHECK(fabs(plant_center.
x - 0.1f) > expected_buffer * 0.5f);
1072DOCTEST_TEST_CASE(
"PlantArchitecture solid obstacle avoidance works independently") {
1075 plantarchitecture.disableMessages();
1078 std::vector<uint> obstacle_UUIDs;
1084 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.2f));
1087 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1088 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1091 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
1094 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
1095 DOCTEST_CHECK(plant_objects.size() > 0);
1099 uint plant_object_count = 0;
1101 for (
uint objID: plant_objects) {
1103 vec3 min_corner, max_corner;
1105 vec3 object_center = (min_corner + max_corner) / 2.0f;
1106 plant_center = plant_center + object_center;
1107 plant_object_count++;
1111 if (plant_object_count > 0) {
1112 plant_center = plant_center / float(plant_object_count);
1114 DOCTEST_CHECK(plant_center.
z > 0.01f);
1119 std::vector<uint> soft_target_UUIDs;
1120 std::vector<uint> soft_target_IDs;
1121 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSoftCollisionAvoidance(soft_target_UUIDs, soft_target_IDs));
1124 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 2.0f));
1127 std::vector<uint> final_plant_objects = plantarchitecture.getAllObjectIDs();
1128 DOCTEST_CHECK(final_plant_objects.size() >= plant_objects.size());
1131DOCTEST_TEST_CASE(
"PlantArchitecture Per-Plant Attraction Points") {
1136 plantarchitecture.disableMessages();
1139 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1140 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1143 std::vector<vec3> attraction_points_1 = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
1144 std::vector<vec3> attraction_points_2 = {
make_vec3(6.0f, 0.0f, 1.0f),
make_vec3(5.0f, 1.0f, 1.5f)};
1147 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, attraction_points_1, 60.0f, 0.2f, 0.7f));
1148 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID2, attraction_points_2, 45.0f, 0.15f, 0.5f));
1151 DOCTEST_CHECK_NOTHROW(plantarchitecture.setAttractionParameters(plantID1, 80.0f, 0.25f, 0.8f, 0.6f));
1152 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(plantID2, {make_vec3(6.5f, 0.5f, 2.0f)}));
1153 DOCTEST_CHECK_NOTHROW(plantarchitecture.appendAttractionPoints(plantID1, {make_vec3(1.5f, 1.5f, 2.0f)}));
1156 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints(plantID1));
1159 DOCTEST_CHECK_THROWS(plantarchitecture.enableAttractionPoints(9999, attraction_points_1));
1160 DOCTEST_CHECK_THROWS(plantarchitecture.disableAttractionPoints(9999));
1161 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(9999, attraction_points_1));
1162 DOCTEST_CHECK_THROWS(plantarchitecture.appendAttractionPoints(9999, attraction_points_1));
1163 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(9999, 60.0f, 0.15f, 0.7f, 0.75f));
1166DOCTEST_TEST_CASE(
"PlantArchitecture Global vs Per-Plant Interaction") {
1171 plantarchitecture.disableMessages();
1174 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1177 std::vector<vec3> global_attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
1178 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(global_attraction_points, 60.0f, 0.15f, 0.7f));
1181 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1184 std::vector<vec3> specific_attraction_points = {
make_vec3(2.0f, 0.0f, 2.0f)};
1185 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, specific_attraction_points, 45.0f, 0.1f, 0.5f));
1188 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints({make_vec3(3.0f, 0.0f, 3.0f)}));
1191 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints());
1194 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(global_attraction_points));
1197DOCTEST_TEST_CASE(
"PlantArchitecture Plant-Specific Attraction Points Validation") {
1202 plantarchitecture.disableMessages();
1205 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1206 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1209 std::vector<vec3> attraction_points_1 = {
make_vec3(1.0f, 0.0f, 1.0f)};
1210 std::vector<vec3> attraction_points_2 = {
make_vec3(6.0f, 0.0f, 1.0f)};
1213 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, attraction_points_1));
1214 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID2, attraction_points_2));
1217 DOCTEST_CHECK_THROWS(plantarchitecture.enableAttractionPoints(plantID1, {}, 60.0f, 0.15f, 0.7f));
1218 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(plantID1, 0.0f, 0.15f, 0.7f));
1219 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(plantID1, 60.0f, 0.0f, 0.7f));
1222 DOCTEST_CHECK_NOTHROW(plantarchitecture.setAttractionParameters(plantID1, 80.0f, 0.25f, 0.8f, 0.6f));
1223 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(plantID2, {make_vec3(6.5f, 0.5f, 2.0f)}));
1224 DOCTEST_CHECK_NOTHROW(plantarchitecture.appendAttractionPoints(plantID1, {make_vec3(1.5f, 1.5f, 2.0f)}));
1227 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints(plantID1));
1231 return helios::runDoctestWithValidation(argc, argv);