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));
1230DOCTEST_TEST_CASE(
"PlantArchitecture removeShootFloralBuds") {
1233 plantarchitecture.disableMessages();
1237 DOCTEST_CHECK_THROWS(plantarchitecture.removeShootFloralBuds(9999, 0));
1240 uint plantID = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1241 DOCTEST_CHECK(plantID != -1);
1244 DOCTEST_CHECK_THROWS(plantarchitecture.removeShootFloralBuds(plantID, 9999));
1247DOCTEST_TEST_CASE(
"PlantArchitecture XML write with flowers and fruit") {
1250 plantarchitecture.disableMessages();
1253 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"tomato"));
1256 vec3 base_position(1.0f, 2.0f, 0.5f);
1257 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(base_position, 180);
1258 DOCTEST_CHECK(plantID !=
uint(-1));
1261 std::string xml_filename =
"test_plant_xml_write.xml";
1262 DOCTEST_CHECK_NOTHROW(plantarchitecture.writePlantStructureXML(plantID, xml_filename));
1265 std::remove(xml_filename.c_str());
1268DOCTEST_TEST_CASE(
"PlantArchitecture child shoot rotation with multiple petioles per internode") {
1271 plantarchitecture.disableMessages();
1278 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1279 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1280 DOCTEST_CHECK(plantID !=
uint(-1));
1283 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1286 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1287 DOCTEST_CHECK(all_primitives.size() > 0);
1294DOCTEST_TEST_CASE(
"PlantArchitecture plant_name optional object data") {
1297 plantarchitecture.disableMessages();
1300 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_name"));
1303 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1304 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1305 DOCTEST_CHECK(plantID !=
uint(-1));
1308 std::string plant_name = plantarchitecture.getPlantName(plantID);
1309 DOCTEST_CHECK(plant_name ==
"bean");
1312 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1315 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1316 DOCTEST_CHECK(all_primitives.size() > 0);
1319 bool found_plant_name_data =
false;
1320 for (
uint objID: all_primitives) {
1322 std::string obj_plant_name;
1324 DOCTEST_CHECK(obj_plant_name ==
"bean");
1325 found_plant_name_data =
true;
1328 DOCTEST_CHECK(found_plant_name_data);
1331DOCTEST_TEST_CASE(
"PlantArchitecture plant_type tree classification") {
1334 plantarchitecture.disableMessages();
1337 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1340 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"almond"));
1341 uint treeID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1342 DOCTEST_CHECK(treeID !=
uint(-1));
1344 std::vector<uint> tree_primitives = plantarchitecture.getAllObjectIDs();
1345 DOCTEST_CHECK(tree_primitives.size() > 0);
1346 bool found_tree_type =
false;
1347 for (
uint objID: tree_primitives) {
1349 std::string plant_type;
1351 DOCTEST_CHECK(plant_type ==
"tree");
1352 found_tree_type =
true;
1355 DOCTEST_CHECK(found_tree_type);
1358DOCTEST_TEST_CASE(
"PlantArchitecture plant_type weed classification") {
1361 plantarchitecture.disableMessages();
1364 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1367 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bindweed"));
1368 uint weedID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1369 DOCTEST_CHECK(weedID !=
uint(-1));
1371 std::vector<uint> weed_primitives = plantarchitecture.getAllObjectIDs();
1372 DOCTEST_CHECK(weed_primitives.size() > 0);
1373 bool found_weed_type =
false;
1374 for (
uint objID: weed_primitives) {
1376 std::string plant_type;
1378 DOCTEST_CHECK(plant_type ==
"weed");
1379 found_weed_type =
true;
1382 DOCTEST_CHECK(found_weed_type);
1385DOCTEST_TEST_CASE(
"PlantArchitecture plant_type herbaceous classification") {
1388 plantarchitecture.disableMessages();
1391 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1394 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1395 uint herbaceousID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1396 DOCTEST_CHECK(herbaceousID !=
uint(-1));
1398 std::vector<uint> herbaceous_primitives = plantarchitecture.getAllObjectIDs();
1399 DOCTEST_CHECK(herbaceous_primitives.size() > 0);
1400 bool found_herbaceous_type =
false;
1401 for (
uint objID: herbaceous_primitives) {
1403 std::string plant_type;
1405 DOCTEST_CHECK(plant_type ==
"herbaceous");
1406 found_herbaceous_type =
true;
1409 DOCTEST_CHECK(found_herbaceous_type);
1412DOCTEST_TEST_CASE(
"PlantArchitecture plant_height optional object data") {
1415 plantarchitecture.disableMessages();
1418 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_height"));
1421 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1422 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1423 DOCTEST_CHECK(plantID !=
uint(-1));
1426 float initial_height = plantarchitecture.getPlantHeight(plantID);
1427 DOCTEST_CHECK(initial_height > 0);
1430 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1433 float final_height = plantarchitecture.getPlantHeight(plantID);
1434 DOCTEST_CHECK(final_height > initial_height);
1437 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1438 DOCTEST_CHECK(all_primitives.size() > 0);
1439 bool found_height_data =
false;
1440 for (
uint objID: all_primitives) {
1445 DOCTEST_CHECK(obj_height > initial_height);
1446 DOCTEST_CHECK(std::abs(obj_height - final_height) < 0.01f);
1447 found_height_data =
true;
1451 DOCTEST_CHECK(found_height_data);
1454DOCTEST_TEST_CASE(
"PlantArchitecture phenology_stage optional object data") {
1457 plantarchitecture.disableMessages();
1460 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"phenology_stage"));
1463 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1464 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1465 DOCTEST_CHECK(plantID !=
uint(-1));
1468 std::string initial_stage = plantarchitecture.determinePhenologyStage(plantID);
1469 DOCTEST_CHECK(initial_stage ==
"vegetative");
1472 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 20.0f));
1475 std::string current_stage = plantarchitecture.determinePhenologyStage(plantID);
1476 DOCTEST_CHECK((current_stage ==
"vegetative" || current_stage ==
"reproductive" || current_stage ==
"senescent" || current_stage ==
"dormant"));
1479 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1480 DOCTEST_CHECK(all_primitives.size() > 0);
1481 bool found_stage_data =
false;
1482 for (
uint objID: all_primitives) {
1484 std::string obj_stage;
1486 DOCTEST_CHECK(obj_stage == current_stage);
1487 found_stage_data =
true;
1490 DOCTEST_CHECK(found_stage_data);
1493DOCTEST_TEST_CASE(
"Build Parameters - Backward Compatibility (Grapevine VSP)") {
1497 plantarchitecture.disableMessages();
1500 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1501 std::map<std::string, float> empty_params;
1502 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, empty_params);
1505 DOCTEST_CHECK(plantID !=
uint(-1));
1508 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1509 DOCTEST_CHECK(plant_primitives.size() > 0);
1512DOCTEST_TEST_CASE(
"Build Parameters - Parameter Override (Grapevine VSP)") {
1516 plantarchitecture.disableMessages();
1520 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1521 std::map<std::string, float> custom_params = {
1522 {
"vine_spacing", 2.5f},
1523 {
"trunk_height", 0.15f}
1525 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, custom_params);
1528 DOCTEST_CHECK(plantID !=
uint(-1));
1529 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1530 DOCTEST_CHECK(plant_primitives.size() > 0);
1533DOCTEST_TEST_CASE(
"Build Parameters - Validation Catches Invalid Values (Grapevine VSP)") {
1538 plantarchitecture.disableMessages();
1540 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1543 std::map<std::string, float> invalid_params1 = {{
"vine_spacing", 10.0f}};
1544 DOCTEST_CHECK_THROWS(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, invalid_params1));
1547 std::map<std::string, float> invalid_params2 = {{
"trunk_height", 2.0f}};
1548 DOCTEST_CHECK_THROWS(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, invalid_params2));
1551DOCTEST_TEST_CASE(
"Build Parameters - Grapevine Wye Trellis Parameters") {
1555 plantarchitecture.disableMessages();
1557 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_Wye");
1558 std::map<std::string, float> trellis_params = {
1559 {
"trunk_height", 0.2f},
1560 {
"cordon_spacing", 0.8f},
1561 {
"vine_spacing", 2.0f},
1562 {
"catch_wire_height", 2.5f}
1564 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, trellis_params);
1566 DOCTEST_CHECK(plantID !=
uint(-1));
1567 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1568 DOCTEST_CHECK(plant_primitives.size() > 0);
1571DOCTEST_TEST_CASE(
"Build Parameters - Tree Training System (Almond)") {
1575 plantarchitecture.disableMessages();
1578 plantarchitecture.loadPlantModelFromLibrary(
"almond");
1579 std::map<std::string, float> tree_params = {
1580 {
"trunk_height", 0.5f},
1581 {
"num_scaffolds", 5.0f},
1582 {
"scaffold_angle", 35.0f}
1584 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, tree_params);
1586 DOCTEST_CHECK(plantID !=
uint(-1));
1587 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1588 DOCTEST_CHECK(plant_primitives.size() > 0);
1591DOCTEST_TEST_CASE(
"Build Parameters - Apple Tree") {
1595 plantarchitecture.disableMessages();
1598 plantarchitecture.loadPlantModelFromLibrary(
"apple");
1599 std::map<std::string, float> apple_params = {
1600 {
"trunk_height", 0.7f},
1601 {
"num_scaffolds", 6.0f},
1602 {
"scaffold_angle", 45.0f}
1604 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, apple_params);
1606 DOCTEST_CHECK(plantID !=
uint(-1));
1609DOCTEST_TEST_CASE(
"Build Parameters - Pistachio Tree Fixed Scaffold System") {
1613 plantarchitecture.disableMessages();
1615 plantarchitecture.loadPlantModelFromLibrary(
"pistachio");
1618 std::map<std::string, float> pistachio_params_min = {{
"num_scaffolds", 2.0f}};
1619 uint plantID_min = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, pistachio_params_min);
1620 DOCTEST_CHECK(plantID_min !=
uint(-1));
1623 std::map<std::string, float> pistachio_params_def = {{
"num_scaffolds", 4.0f}};
1624 uint plantID_def = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(5, 0, 0), 5000, pistachio_params_def);
1625 DOCTEST_CHECK(plantID_def !=
uint(-1));
1628DOCTEST_TEST_CASE(
"Build Parameters - Canopy Building with Parameters") {
1632 plantarchitecture.disableMessages();
1634 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1635 std::map<std::string, float> canopy_params = {
1636 {
"vine_spacing", 2.0f},
1637 {
"trunk_height", 0.12f}
1641 std::vector<uint> plantIDs = plantarchitecture.buildPlantCanopyFromLibrary(
make_vec3(0, 0, 0),
make_vec2(2, 2),
make_int2(2, 2), 0, 1.0f, canopy_params);
1643 DOCTEST_CHECK(plantIDs.size() == 4);
1644 for (
uint plantID: plantIDs) {
1645 DOCTEST_CHECK(plantID !=
uint(-1));
1649DOCTEST_TEST_CASE(
"Build Parameters - Type Casting Float to Uint") {
1653 plantarchitecture.disableMessages();
1655 plantarchitecture.loadPlantModelFromLibrary(
"almond");
1659 std::map<std::string, float> float_params = {
1660 {
"trunk_height", 0.5f},
1661 {
"num_scaffolds", 5.0f},
1662 {
"scaffold_angle", 42.5f}
1665 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, float_params);
1666 DOCTEST_CHECK(plantID !=
uint(-1));
1669DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData 'all' keyword") {
1672 plantarchitecture.disableMessages();
1675 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"all"));
1678 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1679 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1680 DOCTEST_CHECK(plantID !=
uint(-1));
1683 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1686 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1687 DOCTEST_CHECK(all_primitives.size() > 0);
1692 std::vector<std::string> expected_labels = {
"age",
"rank",
"plantID",
"plant_name",
"plant_height",
"plant_type",
"phenology_stage",
"leafID"};
1694 for (
const auto &label: expected_labels) {
1696 for (
uint objID: all_primitives) {
1702 DOCTEST_CHECK_MESSAGE(found,
"Label '" << label <<
"' was not found on any primitive");
1706DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData 'all' case-insensitive") {
1711 plantarchitecture.disableMessages();
1712 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"ALL"));
1719 plantarchitecture.disableMessages();
1720 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"All"));
1727 plantarchitecture.disableMessages();
1728 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"aLl"));
1732DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData invalid label throws error") {
1735 plantarchitecture.disableMessages();
1738 bool caught_error =
false;
1740 plantarchitecture.optionalOutputObjectData(
"invalid_label");
1741 }
catch (
const std::exception &e) {
1742 caught_error =
true;
1743 std::string error_msg(e.what());
1744 DOCTEST_CHECK(error_msg.find(
"invalid_label") != std::string::npos);
1745 DOCTEST_CHECK(error_msg.find(
"not a valid option") != std::string::npos);
1747 DOCTEST_CHECK(caught_error);
1753DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData vector with 'all'") {
1756 plantarchitecture.disableMessages();
1759 std::vector<std::string> labels = {
"all"};
1760 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(labels));
1763 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1764 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1765 DOCTEST_CHECK(plantID !=
uint(-1));
1768 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1771 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1772 DOCTEST_CHECK(all_primitives.size() > 0);
1775 bool found_age =
false;
1776 bool found_rank =
false;
1777 bool found_plant_name =
false;
1778 for (
uint objID: all_primitives) {
1784 found_plant_name =
true;
1786 DOCTEST_CHECK(found_age);
1787 DOCTEST_CHECK(found_rank);
1788 DOCTEST_CHECK(found_plant_name);
1791DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData normal labels still work") {
1794 plantarchitecture.disableMessages();
1797 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"age"));
1798 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"rank"));
1801 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1802 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1803 DOCTEST_CHECK(plantID !=
uint(-1));
1806 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
1809 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1810 DOCTEST_CHECK(all_primitives.size() > 0);
1812 bool found_age =
false;
1813 bool found_rank =
false;
1814 bool found_plant_name =
false;
1815 for (
uint objID: all_primitives) {
1821 found_plant_name =
true;
1823 DOCTEST_CHECK(found_age);
1824 DOCTEST_CHECK(found_rank);
1825 DOCTEST_CHECK_FALSE(found_plant_name);
1830DOCTEST_TEST_CASE(
"Nitrogen Model - Initialization") {
1833 plantarchitecture.disableMessages();
1836 plantarchitecture.enableNitrogenModel();
1837 DOCTEST_CHECK(plantarchitecture.isNitrogenModelEnabled());
1840 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1841 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1844 plantarchitecture.advanceTime(plantID, 5.0f);
1847 float initial_N_concentration = 1.5f;
1848 plantarchitecture.initializePlantNitrogenPools(plantID, initial_N_concentration);
1851 plantarchitecture.advanceTime(plantID, 0.1f);
1854 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
1855 DOCTEST_CHECK(all_objects.size() > 0);
1858 bool found_leaf_N =
false;
1859 for (
uint objID: all_objects) {
1862 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
1863 DOCTEST_CHECK(leaf_N_area == doctest::Approx(initial_N_concentration).epsilon(0.1));
1864 found_leaf_N =
true;
1867 DOCTEST_CHECK(found_leaf_N);
1870DOCTEST_TEST_CASE(
"Nitrogen Model - Application and Pool Splitting") {
1873 plantarchitecture.disableMessages();
1875 plantarchitecture.enableNitrogenModel();
1876 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1877 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1878 plantarchitecture.advanceTime(plantID, 3.0f);
1881 plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f);
1884 float N_applied = 10.0f;
1885 plantarchitecture.addPlantNitrogen(plantID, N_applied);
1890 plantarchitecture.advanceTime(plantID, 1.0f);
1893 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
1894 bool found_N_accumulation =
false;
1895 for (
uint objID: all_objects) {
1898 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
1899 if (leaf_N_area > 0) {
1900 found_N_accumulation =
true;
1905 DOCTEST_CHECK(found_N_accumulation);
1908DOCTEST_TEST_CASE(
"Nitrogen Model - Rate Limiting") {
1911 plantarchitecture.disableMessages();
1913 plantarchitecture.enableNitrogenModel();
1914 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1915 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1916 plantarchitecture.advanceTime(plantID, 5.0f);
1919 plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f);
1925 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
1928 plantarchitecture.addPlantNitrogen(plantID, 100.0f);
1932 plantarchitecture.advanceTime(plantID, dt);
1935 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
1936 for (
uint objID: all_objects) {
1939 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
1946DOCTEST_TEST_CASE(
"Nitrogen Model - Stress Factor Output") {
1949 plantarchitecture.disableMessages();
1951 plantarchitecture.enableNitrogenModel();
1952 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1953 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1954 plantarchitecture.advanceTime(plantID, 5.0f);
1957 plantarchitecture.initializePlantNitrogenPools(plantID, 0.5f);
1960 plantarchitecture.advanceTime(plantID, 0.1f);
1963 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
1964 DOCTEST_CHECK(plant_objects.size() > 0);
1966 bool found_stress_factor =
false;
1967 for (
uint objID: plant_objects) {
1969 float stress_factor;
1970 context.
getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
1971 DOCTEST_CHECK(stress_factor >= 0.0f);
1972 DOCTEST_CHECK(stress_factor <= 1.0f);
1974 DOCTEST_CHECK(stress_factor < 1.0f);
1975 found_stress_factor =
true;
1979 DOCTEST_CHECK(found_stress_factor);
1982DOCTEST_TEST_CASE(
"Nitrogen Model - Remobilization") {
1985 plantarchitecture.disableMessages();
1987 plantarchitecture.enableNitrogenModel();
1988 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1989 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1992 plantarchitecture.advanceTime(plantID, 15.0f);
1995 plantarchitecture.initializePlantNitrogenPools(plantID, 0.8f);
1998 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 25.0f));
2001 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2002 bool found_stress_factor =
false;
2003 for (
uint objID: plant_objects) {
2005 float stress_factor;
2006 context.
getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2007 DOCTEST_CHECK(stress_factor < 1.0f);
2008 found_stress_factor =
true;
2012 DOCTEST_CHECK(found_stress_factor);
2015DOCTEST_TEST_CASE(
"Nitrogen Model - Fruit Removal") {
2018 plantarchitecture.disableMessages();
2020 plantarchitecture.enableNitrogenModel();
2023 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2024 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2027 plantarchitecture.advanceTime(plantID, 30.0f);
2030 plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f);
2033 plantarchitecture.addPlantNitrogen(plantID, 50.0f);
2036 plantarchitecture.advanceTime(plantID, 40.0f);
2039 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2040 DOCTEST_CHECK(plant_objects.size() > 0);
2043 bool found_stress_factor =
false;
2044 for (
uint objID: plant_objects) {
2046 found_stress_factor =
true;
2050 DOCTEST_CHECK(found_stress_factor);
2053DOCTEST_TEST_CASE(
"Nitrogen Model - Full Growth Cycle Integration") {
2056 plantarchitecture.disableMessages();
2058 plantarchitecture.enableNitrogenModel();
2059 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2060 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2063 plantarchitecture.advanceTime(plantID, 5.0f);
2066 plantarchitecture.initializePlantNitrogenPools(plantID, 1.0f);
2069 for (
int i = 0; i < 5; i++) {
2070 plantarchitecture.addPlantNitrogen(plantID, 5.0f);
2071 plantarchitecture.advanceTime(plantID, 5.0f);
2075 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2076 DOCTEST_CHECK(plant_objects.size() > 0);
2079 bool found_stress_factor =
false;
2080 float final_stress = 0;
2081 for (
uint objID: plant_objects) {
2083 context.
getObjectData(objID,
"nitrogen_stress_factor", final_stress);
2084 found_stress_factor =
true;
2088 DOCTEST_CHECK(found_stress_factor);
2089 DOCTEST_CHECK(final_stress >= 0.0f);
2090 DOCTEST_CHECK(final_stress <= 1.0f);
2093 bool found_leaf_N =
false;
2094 for (
uint objID: plant_objects) {
2097 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N);
2098 DOCTEST_CHECK(leaf_N >= 0.0f);
2099 found_leaf_N =
true;
2102 DOCTEST_CHECK(found_leaf_N);
2105DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: Zero Nitrogen") {
2108 plantarchitecture.disableMessages();
2110 plantarchitecture.enableNitrogenModel();
2111 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2112 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2113 plantarchitecture.advanceTime(plantID, 5.0f);
2116 DOCTEST_CHECK_NOTHROW(plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f));
2119 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2122 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2123 bool found_stress_factor =
false;
2124 for (
uint objID: plant_objects) {
2126 float stress_factor;
2127 context.
getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2128 DOCTEST_CHECK(stress_factor < 0.2f);
2129 found_stress_factor =
true;
2133 DOCTEST_CHECK(found_stress_factor);
2136DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: Excessive Nitrogen") {
2139 plantarchitecture.disableMessages();
2141 plantarchitecture.enableNitrogenModel();
2142 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2143 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2144 plantarchitecture.advanceTime(plantID, 5.0f);
2147 plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f);
2152 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
2155 DOCTEST_CHECK_NOTHROW(plantarchitecture.addPlantNitrogen(plantID, 1000.0f));
2158 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2161 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2162 bool found_stress_factor =
false;
2163 for (
uint objID: plant_objects) {
2165 float stress_factor;
2166 context.
getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2167 DOCTEST_CHECK(stress_factor <= 1.0f);
2168 DOCTEST_CHECK(stress_factor >= 0.90f);
2169 found_stress_factor =
true;
2173 DOCTEST_CHECK(found_stress_factor);
2176DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: No Leaves") {
2179 plantarchitecture.disableMessages();
2181 plantarchitecture.enableNitrogenModel();
2184 uint plantID = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
2187 DOCTEST_CHECK_NOTHROW(plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f));
2190 DOCTEST_CHECK_NOTHROW(plantarchitecture.addPlantNitrogen(plantID, 10.0f));
2193 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 1.0f));
2196DOCTEST_TEST_CASE(
"Nitrogen Model - Division by Zero Prevention") {
2199 plantarchitecture.disableMessages();
2201 plantarchitecture.enableNitrogenModel();
2202 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2203 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2206 plantarchitecture.advanceTime(plantID, 0.5f);
2209 plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f);
2212 plantarchitecture.addPlantNitrogen(plantID, 10.0f);
2215 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 1.0f));
2218 plantarchitecture.advanceTime(plantID, 20.0f);
2219 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2222DOCTEST_TEST_CASE(
"Nitrogen Model - Enable/Disable") {
2225 plantarchitecture.disableMessages();
2228 DOCTEST_CHECK_FALSE(plantarchitecture.isNitrogenModelEnabled());
2231 plantarchitecture.enableNitrogenModel();
2232 DOCTEST_CHECK(plantarchitecture.isNitrogenModelEnabled());
2235 plantarchitecture.disableNitrogenModel();
2236 DOCTEST_CHECK_FALSE(plantarchitecture.isNitrogenModelEnabled());
2239 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2240 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2241 plantarchitecture.advanceTime(plantID, 5.0f);
2243 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2244 bool found_nitrogen_data =
false;
2245 for (
uint objID: plant_objects) {
2247 found_nitrogen_data =
true;
2251 DOCTEST_CHECK_FALSE(found_nitrogen_data);
2256DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - no parameter success") {
2260 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2261 std::vector<std::string> labels = plantarchitecture.listShootTypeLabels();
2263 DOCTEST_CHECK(labels.size() == 2);
2264 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"unifoliate") != labels.end());
2265 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"trifoliate") != labels.end());
2268DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - no parameter error") {
2269 std::string error_message;
2276 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels()));
2280DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - string parameter success") {
2285 std::vector<std::string> bean_labels = plantarchitecture.listShootTypeLabels(
"bean");
2286 DOCTEST_CHECK(bean_labels.size() == 2);
2287 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"unifoliate") != bean_labels.end());
2288 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"trifoliate") != bean_labels.end());
2291 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(
"tomato");
2292 DOCTEST_CHECK(tomato_labels.size() == 1);
2293 DOCTEST_CHECK(std::find(tomato_labels.begin(), tomato_labels.end(),
"mainstem") != tomato_labels.end());
2296DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - string parameter error") {
2297 std::string error_message;
2304 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels(
"nonexistent_plant")));
2308DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - state preservation") {
2313 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2316 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(
"tomato");
2319 std::vector<std::string> current_labels = plantarchitecture.listShootTypeLabels();
2320 DOCTEST_CHECK(current_labels.size() == 2);
2321 DOCTEST_CHECK(std::find(current_labels.begin(), current_labels.end(),
"unifoliate") != current_labels.end());
2322 DOCTEST_CHECK(std::find(current_labels.begin(), current_labels.end(),
"trifoliate") != current_labels.end());
2325DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - all plant models") {
2329 std::vector<std::string> all_plants = plantarchitecture.getAvailablePlantModels();
2332 for (
const auto &plant: all_plants) {
2333 std::vector<std::string> labels;
2334 DOCTEST_CHECK_NOTHROW(labels = plantarchitecture.listShootTypeLabels(plant));
2335 DOCTEST_CHECK(!labels.empty());
2339DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - uint parameter success") {
2344 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2345 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2348 std::vector<std::string> labels = plantarchitecture.listShootTypeLabels(plantID);
2351 DOCTEST_CHECK(labels.size() == 2);
2352 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"unifoliate") != labels.end());
2353 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"trifoliate") != labels.end());
2356DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - uint parameter error") {
2357 std::string error_message;
2364 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels(999)));
2368DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - multiple instances") {
2373 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2374 uint bean_plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2377 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2378 uint tomato_plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(1, 0, 0), 0);
2381 std::vector<std::string> bean_labels = plantarchitecture.listShootTypeLabels(bean_plantID);
2382 DOCTEST_CHECK(bean_labels.size() == 2);
2383 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"unifoliate") != bean_labels.end());
2384 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"trifoliate") != bean_labels.end());
2386 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(tomato_plantID);
2387 DOCTEST_CHECK(tomato_labels.size() == 1);
2388 DOCTEST_CHECK(std::find(tomato_labels.begin(), tomato_labels.end(),
"mainstem") != tomato_labels.end());
2391DOCTEST_TEST_CASE(
"PlantArchitecture getPlantInternodeObjectIDs with shoot type filter") {
2396 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2397 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0.0);
2400 std::vector<uint> all_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID);
2401 DOCTEST_CHECK(all_internodes.size() > 0);
2404 std::vector<uint> unifoliate_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID,
"unifoliate");
2405 DOCTEST_CHECK(unifoliate_internodes.size() > 0);
2408 std::vector<uint> trifoliate_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID,
"trifoliate");
2409 DOCTEST_CHECK(trifoliate_internodes.size() > 0);
2412 for (
uint objID : unifoliate_internodes) {
2413 DOCTEST_CHECK(std::find(all_internodes.begin(), all_internodes.end(), objID) != all_internodes.end());
2415 for (
uint objID : trifoliate_internodes) {
2416 DOCTEST_CHECK(std::find(all_internodes.begin(), all_internodes.end(), objID) != all_internodes.end());
2420 for (
uint objID : unifoliate_internodes) {
2421 DOCTEST_CHECK(std::find(trifoliate_internodes.begin(), trifoliate_internodes.end(), objID) == trifoliate_internodes.end());
2425 DOCTEST_CHECK(unifoliate_internodes.size() + trifoliate_internodes.size() == all_internodes.size());
2428DOCTEST_TEST_CASE(
"PlantArchitecture getPlantInternodeObjectIDs with shoot type filter - error cases") {
2429 std::string error_message;
2435 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2436 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0.0);
2439 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.getPlantInternodeObjectIDs(plantID,
"nonexistent_shoot_type")));
2442 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.getPlantInternodeObjectIDs(9999,
"unifoliate")));
2447 return helios::runDoctestWithValidation(argc, argv);