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(
"Material Naming - bean plant materials have descriptive names") {
115 plantarchitecture.disableMessages();
116 plantarchitecture.loadPlantModelFromLibrary(
"bean");
117 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000);
120 std::vector<uint> all_UUIDs = plantarchitecture.getAllPlantUUIDs(plantID);
121 DOCTEST_CHECK(all_UUIDs.size() > 0);
122 for (
uint UUID : all_UUIDs) {
124 DOCTEST_CHECK(label.substr(0, 7) !=
"__auto_");
128 std::vector<std::string> materials = context.
listMaterials();
131 bool found_trifoliate_leaf =
false;
132 bool found_unifoliate_leaf =
false;
133 bool found_stem =
false;
134 for (
const auto &label : materials) {
135 if (label.find(
"bean") != std::string::npos && label.find(
"trifoliate") != std::string::npos && label.find(
"leaf") != std::string::npos) {
136 found_trifoliate_leaf =
true;
138 if (label.find(
"bean") != std::string::npos && label.find(
"unifoliate") != std::string::npos && label.find(
"leaf") != std::string::npos) {
139 found_unifoliate_leaf =
true;
141 if (label.find(
"bean") != std::string::npos && label.find(
"stem") != std::string::npos) {
145 DOCTEST_CHECK(found_trifoliate_leaf);
146 DOCTEST_CHECK(found_unifoliate_leaf);
147 DOCTEST_CHECK(found_stem);
150DOCTEST_TEST_CASE(
"Plant Library Model Building - cheeseweed") {
153 plantarchitecture.disableMessages();
154 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"cheeseweed"));
155 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
158DOCTEST_TEST_CASE(
"Plant Library Model Building - cowpea") {
161 plantarchitecture.disableMessages();
162 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"cowpea"));
163 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
166DOCTEST_TEST_CASE(
"Plant Library Model Building - grapevine_VSP") {
169 plantarchitecture.disableMessages();
170 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP"));
171 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
174DOCTEST_TEST_CASE(
"Plant Library Model Building - maize") {
177 plantarchitecture.disableMessages();
178 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"maize"));
179 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
182DOCTEST_TEST_CASE(
"Plant Library Model Building - olive") {
185 plantarchitecture.disableMessages();
186 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"olive"));
187 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
190DOCTEST_TEST_CASE(
"Plant Library Model Building - pistachio") {
193 plantarchitecture.disableMessages();
194 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"pistachio"));
195 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
198DOCTEST_TEST_CASE(
"Plant Library Model Building - puncturevine") {
201 plantarchitecture.disableMessages();
202 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"puncturevine"));
203 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
206DOCTEST_TEST_CASE(
"Plant Library Model Building - easternredbud") {
209 plantarchitecture.disableMessages();
210 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"easternredbud"));
211 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
214DOCTEST_TEST_CASE(
"Plant Library Model Building - rice") {
217 plantarchitecture.disableMessages();
218 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"rice"));
219 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
222DOCTEST_TEST_CASE(
"Plant Library Model Building - butterlettuce") {
225 plantarchitecture.disableMessages();
226 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"butterlettuce"));
227 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
230DOCTEST_TEST_CASE(
"Plant Library Model Building - sorghum") {
233 plantarchitecture.disableMessages();
234 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"sorghum"));
235 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
238DOCTEST_TEST_CASE(
"Plant Library Model Building - soybean") {
241 plantarchitecture.disableMessages();
242 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"soybean"));
243 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
246DOCTEST_TEST_CASE(
"Plant Library Model Building - strawberry") {
249 plantarchitecture.disableMessages();
250 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"strawberry"));
251 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
254DOCTEST_TEST_CASE(
"Plant Library Model Building - sugarbeet") {
257 plantarchitecture.disableMessages();
258 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"sugarbeet"));
259 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
262DOCTEST_TEST_CASE(
"Plant Library Model Building - tomato") {
265 plantarchitecture.disableMessages();
266 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"tomato"));
267 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
270DOCTEST_TEST_CASE(
"Plant Library Model Building - walnut") {
273 plantarchitecture.disableMessages();
274 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"walnut"));
275 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
278DOCTEST_TEST_CASE(
"Plant Library Model Building - wheat") {
281 plantarchitecture.disableMessages();
282 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"wheat"));
283 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
286DOCTEST_TEST_CASE(
"PlantArchitecture writeTreeQSM") {
289 plantarchitecture.disableMessages();
292 plantarchitecture.loadPlantModelFromLibrary(
"bean");
293 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 50);
296 std::string filename =
"test_plant_qsm.txt";
297 DOCTEST_CHECK_NOTHROW(plantarchitecture.writeQSMCylinderFile(plantID, filename));
300 std::ifstream file(filename);
301 DOCTEST_CHECK(file.good());
304 std::string header_line;
305 std::getline(file, header_line);
308 DOCTEST_CHECK(header_line.find(
"radius (m)") != std::string::npos);
309 DOCTEST_CHECK(header_line.find(
"length (m)") != std::string::npos);
310 DOCTEST_CHECK(header_line.find(
"start_point") != std::string::npos);
311 DOCTEST_CHECK(header_line.find(
"axis_direction") != std::string::npos);
312 DOCTEST_CHECK(header_line.find(
"branch") != std::string::npos);
313 DOCTEST_CHECK(header_line.find(
"branch_order") != std::string::npos);
316 std::string data_line;
317 bool has_data =
static_cast<bool>(std::getline(file, data_line));
318 DOCTEST_CHECK(has_data);
322 size_t tab_count = std::count(data_line.begin(), data_line.end(),
'\t');
323 DOCTEST_CHECK(tab_count >= 12);
329 std::remove(filename.c_str());
333DOCTEST_TEST_CASE(
"PlantArchitecture writeTreeQSM invalid plant") {
337 plantarchitecture.disableMessages();
340 DOCTEST_CHECK_THROWS(plantarchitecture.writeQSMCylinderFile(999,
"invalid_plant.txt"));
343DOCTEST_TEST_CASE(
"PlantArchitecture pruneSolidBoundaryCollisions") {
346 plantarchitecture.disableMessages();
349 plantarchitecture.enableSoftCollisionAvoidance();
352 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
355 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
356 plantarchitecture.advanceTime(plantID, 15);
359 std::vector<uint> objects_before_boundaries = plantarchitecture.getAllObjectIDs();
360 uint count_before_boundaries = objects_before_boundaries.size();
363 DOCTEST_CHECK(count_before_boundaries > 0);
367 std::vector<uint> boundary_UUIDs;
368 for (
int i = -2; i <= 2; i++) {
369 for (
int j = -2; j <= 2; j++) {
376 plantarchitecture.enableSolidObstacleAvoidance(boundary_UUIDs, 0.2f);
380 plantarchitecture.advanceTime(plantID, 0.1f);
383 std::vector<uint> final_objects = plantarchitecture.getAllObjectIDs();
384 uint final_count = final_objects.size();
391 DOCTEST_CHECK(final_count > 0);
394DOCTEST_TEST_CASE(
"PlantArchitecture pruneSolidBoundaryCollisions no boundaries") {
397 plantarchitecture.disableMessages();
400 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
403 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
404 plantarchitecture.advanceTime(plantID, 5);
407 std::vector<uint> initial_objects = plantarchitecture.getAllObjectIDs();
408 uint initial_count = initial_objects.size();
411 plantarchitecture.advanceTime(plantID, 2);
414 std::vector<uint> final_objects = plantarchitecture.getAllObjectIDs();
415 uint final_count = final_objects.size();
417 DOCTEST_CHECK(final_count >= initial_count);
420DOCTEST_TEST_CASE(
"PlantArchitecture hard collision avoidance base stem protection") {
423 plantarchitecture.disableMessages();
426 plantarchitecture.enableSoftCollisionAvoidance();
429 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
433 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, -0.05f), 0);
436 std::vector<uint> ground_UUIDs;
439 for (
int i = -2; i <= 2; i++) {
440 for (
int j = -2; j <= 2; j++) {
442 make_vec3((i + 1) * 0.2f, j * 0.2f, 0.0f),
make_vec3(i * 0.2f, (j + 1) * 0.2f, 0.0f)));
448 plantarchitecture.enableSolidObstacleAvoidance(ground_UUIDs, 0.3f);
452 plantarchitecture.advanceTime(plantID, 10);
455 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
456 DOCTEST_CHECK(plant_objects.size() > 0);
461 uint total_objects = 0;
463 for (
uint objID: plant_objects) {
466 vec3 min_corner, max_corner;
469 vec3 object_center = (min_corner + max_corner) / 2.0f;
471 center_of_mass = center_of_mass + object_center;
476 if (total_objects > 0) {
477 center_of_mass = center_of_mass / float(total_objects);
481 DOCTEST_CHECK(center_of_mass.
z > -0.075f);
486 DOCTEST_CHECK(center_of_mass.
z > -0.075f);
491 DOCTEST_CHECK(plant_objects.size() >= 5);
494DOCTEST_TEST_CASE(
"PlantArchitecture enableSolidObstacleAvoidance fruit adjustment control") {
497 plantarchitecture.disableMessages();
500 std::vector<uint> obstacle_UUIDs;
505 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f));
508 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f,
true));
511 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f,
false));
514 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.3f,
false));
517DOCTEST_TEST_CASE(
"PlantArchitecture base stem protection with short internodes") {
520 plantarchitecture.disableMessages();
523 plantarchitecture.enableSoftCollisionAvoidance();
526 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
529 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
532 plantarchitecture.advanceTime(plantID, 2);
535 std::vector<uint> ground_UUIDs;
536 for (
int i = -1; i <= 1; i++) {
537 for (
int j = -1; j <= 1; j++) {
539 make_vec3((i + 1) * 0.3f, j * 0.3f, -0.01f),
make_vec3(i * 0.3f, (j + 1) * 0.3f, -0.01f)));
545 plantarchitecture.enableSolidObstacleAvoidance(ground_UUIDs, 0.2f);
549 plantarchitecture.advanceTime(plantID, 8);
552 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
553 DOCTEST_CHECK(plant_objects.size() > 0);
557 uint total_objects = 0;
559 for (
uint objID: plant_objects) {
561 vec3 min_corner, max_corner;
563 vec3 object_center = (min_corner + max_corner) / 2.0f;
564 center_of_mass = center_of_mass + object_center;
569 if (total_objects > 0) {
570 center_of_mass = center_of_mass / float(total_objects);
573 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
577 DOCTEST_CHECK(center_of_mass.
z > 0.05f);
581 DOCTEST_CHECK(plant_objects.size() >= 10);
584DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Basic Functionality") {
587 plantarchitecture.disableMessages();
590 plantarchitecture.enableSoftCollisionAvoidance();
593 std::vector<vec3> attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
596 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points, 60.0f, 0.15f, 0.7f));
599 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(0.0f, 0.1f, 0.5f));
600 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(190.0f, 0.1f, 0.5f));
603 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.0f, 0.5f));
604 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, -0.1f, 0.5f));
607 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.1f, -0.1f));
608 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.1f, 1.1f));
611 std::vector<vec3> new_attraction_points = {
make_vec3(2.0f, 0.0f, 2.0f)};
612 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(new_attraction_points));
615 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints());
618 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(new_attraction_points));
621DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Independent of Collision Detection") {
624 plantarchitecture.disableMessages();
626 std::vector<vec3> attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f)};
629 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points));
632DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Empty Vector") {
635 plantarchitecture.disableMessages();
637 std::vector<vec3> empty_attraction_points;
640 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(empty_attraction_points));
643 std::vector<vec3> valid_points = {
make_vec3(1.0f, 0.0f, 1.0f)};
644 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(valid_points));
647 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(empty_attraction_points));
650DOCTEST_TEST_CASE(
"PlantArchitecture Native Attraction Point Cone Detection") {
653 plantarchitecture.disableMessages();
656 std::vector<vec3> attraction_points = {
664 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points, 60.0f, 3.0f, 0.7f));
669 vec3 direction_to_closest;
671 bool found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 60.0f, direction_to_closest);
672 DOCTEST_CHECK(found);
676 float dot_product = direction_to_closest * expected_direction;
677 DOCTEST_CHECK(dot_product > 0.99f);
680 look_direction =
make_vec3(1.0f, 0.0f, 0.0f);
681 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 30.0f, direction_to_closest);
687 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, -1.0f, 60.0f, direction_to_closest);
688 DOCTEST_CHECK(!found);
690 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 0.0f, direction_to_closest);
691 DOCTEST_CHECK(!found);
693 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 180.0f, direction_to_closest);
694 DOCTEST_CHECK(!found);
697DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Plant Growth Integration") {
700 plantarchitecture.disableMessages();
703 plantarchitecture.enableSoftCollisionAvoidance();
706 std::vector<vec3> attraction_points = {
712 plantarchitecture.enableAttractionPoints(attraction_points, 80.0f, 0.2f, 0.6f);
715 plantarchitecture.loadPlantModelFromLibrary(
"bean");
716 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
719 plantarchitecture.advanceTime(plantID, 5);
722 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
723 DOCTEST_CHECK(plant_objects.size() > 0);
727 uint total_objects = 0;
729 for (
uint objID: plant_objects) {
731 vec3 min_corner, max_corner;
733 vec3 object_center = (min_corner + max_corner) / 2.0f;
734 center_of_mass = center_of_mass + object_center;
739 if (total_objects > 0) {
740 center_of_mass = center_of_mass / float(total_objects);
744 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
748 float lateral_distance = sqrt(center_of_mass.
x * center_of_mass.
x + center_of_mass.
y * center_of_mass.
y);
749 DOCTEST_CHECK(lateral_distance >= 0.0f);
753 plantarchitecture.disableAttractionPoints();
756 plantarchitecture.advanceTime(plantID, 3);
759 std::vector<uint> final_plant_objects = plantarchitecture.getAllObjectIDs();
760 DOCTEST_CHECK(final_plant_objects.size() >= plant_objects.size());
763DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Priority Over Collision Avoidance") {
766 plantarchitecture.disableMessages();
769 std::vector<uint> obstacle_UUIDs;
770 for (
int i = 0; i < 3; i++) {
771 for (
int j = 0; j < 3; j++) {
772 obstacle_UUIDs.push_back(
773 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)));
778 plantarchitecture.enableSoftCollisionAvoidance(obstacle_UUIDs);
781 std::vector<vec3> attraction_points = {
786 plantarchitecture.enableAttractionPoints(attraction_points, 90.0f, 0.3f, 0.8f);
789 plantarchitecture.loadPlantModelFromLibrary(
"bean");
790 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0.3f, 0.3f, 0), 0);
793 plantarchitecture.advanceTime(plantID, 4);
796 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
797 DOCTEST_CHECK(plant_objects.size() > 0);
801 uint total_objects = 0;
803 for (
uint objID: plant_objects) {
805 vec3 min_corner, max_corner;
807 vec3 object_center = (min_corner + max_corner) / 2.0f;
808 center_of_mass = center_of_mass + object_center;
813 if (total_objects > 0) {
814 center_of_mass = center_of_mass / float(total_objects);
817 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
824DOCTEST_TEST_CASE(
"PlantArchitecture Hard Obstacle Avoidance Takes Priority Over Attraction Points") {
827 plantarchitecture.disableMessages();
830 std::vector<uint> solid_obstacle_UUIDs;
831 for (
int i = -1; i <= 1; i++) {
832 for (
int j = -1; j <= 1; j++) {
838 plantarchitecture.enableSoftCollisionAvoidance();
841 plantarchitecture.enableSolidObstacleAvoidance(solid_obstacle_UUIDs, 0.15f);
844 std::vector<vec3> attraction_points = {
849 plantarchitecture.enableAttractionPoints(attraction_points, 70.0f, 0.1f, 0.9f);
852 plantarchitecture.loadPlantModelFromLibrary(
"bean");
853 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
856 plantarchitecture.advanceTime(plantID, 3);
859 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
860 DOCTEST_CHECK(plant_objects.size() > 0);
864 uint total_objects = 0;
866 for (
uint objID: plant_objects) {
868 vec3 min_corner, max_corner;
870 vec3 object_center = (min_corner + max_corner) / 2.0f;
871 center_of_mass = center_of_mass + object_center;
876 if (total_objects > 0) {
877 center_of_mass = center_of_mass / float(total_objects);
880 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
884 DOCTEST_CHECK(center_of_mass.
z > 0.005f);
888DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points with Surface Following") {
891 plantarchitecture.disableMessages();
894 std::vector<uint> wall_obstacle_UUIDs;
895 std::vector<vec3> wall_attraction_points;
898 for (
int i = 0; i < 5; i++) {
899 for (
int j = 0; j < 3; j++) {
901 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)));
904 wall_attraction_points.push_back(
make_vec3(0.29f, i * 0.05f + 0.025f, j * 0.05f + 0.025f));
909 plantarchitecture.enableSoftCollisionAvoidance();
912 plantarchitecture.enableSolidObstacleAvoidance(wall_obstacle_UUIDs, 0.05f);
916 plantarchitecture.enableAttractionPoints(wall_attraction_points, 60.0f, 0.1f, 0.8f);
917 plantarchitecture.setAttractionParameters(60.0f, 0.1f, 0.8f, 0.5f);
920 plantarchitecture.loadPlantModelFromLibrary(
"bean");
921 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
924 plantarchitecture.advanceTime(plantID, 4);
927 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
928 DOCTEST_CHECK(plant_objects.size() > 0);
932 uint total_objects = 0;
934 for (
uint objID: plant_objects) {
936 vec3 min_corner, max_corner;
938 vec3 object_center = (min_corner + max_corner) / 2.0f;
939 center_of_mass = center_of_mass + object_center;
944 if (total_objects > 0) {
945 center_of_mass = center_of_mass / float(total_objects);
948 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
959DOCTEST_TEST_CASE(
"PlantArchitecture Smooth Hard Obstacle Avoidance") {
962 plantarchitecture.disableMessages();
964 plantarchitecture.enableSoftCollisionAvoidance();
965 plantarchitecture.loadPlantModelFromLibrary(
"bean");
968 std::vector<uint> obstacle_UUIDs;
972 for (
int i = 0; i < 4; i++) {
973 float z_height = 0.1f + i * 0.05f;
976 float x_distance = 0.05f + i * 0.02f;
982 plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.25f);
984 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
985 plantarchitecture.advanceTime(plantID, 8);
987 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
988 DOCTEST_CHECK(plant_objects.size() > 0);
992 uint total_objects = 0;
994 for (
uint objID: plant_objects) {
996 vec3 min_corner, max_corner;
998 vec3 object_center = (min_corner + max_corner) / 2.0f;
999 center_of_mass = center_of_mass + object_center;
1004 if (total_objects > 0) {
1005 center_of_mass = center_of_mass / float(total_objects);
1008 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
1012 DOCTEST_CHECK(center_of_mass.
x <= 0.01f);
1020DOCTEST_TEST_CASE(
"PlantArchitecture Hard Obstacle Avoidance Buffer Zone") {
1023 plantarchitecture.disableMessages();
1025 plantarchitecture.enableSoftCollisionAvoidance();
1026 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1029 std::vector<uint> post_UUIDs;
1030 float post_radius = 0.02f;
1031 float post_height = 0.5f;
1035 for (
int i = 0; i < segments; i++) {
1036 float theta1 = 2.0f *
M_PI * float(i) / float(segments);
1037 float theta2 = 2.0f *
M_PI * float(i + 1) / float(segments);
1039 vec3 p1_bottom =
make_vec3(0.1f + post_radius * cos(theta1), post_radius * sin(theta1), 0);
1040 vec3 p2_bottom =
make_vec3(0.1f + post_radius * cos(theta2), post_radius * sin(theta2), 0);
1041 vec3 p1_top =
make_vec3(0.1f + post_radius * cos(theta1), post_radius * sin(theta1), post_height);
1042 vec3 p2_top =
make_vec3(0.1f + post_radius * cos(theta2), post_radius * sin(theta2), post_height);
1045 post_UUIDs.push_back(context.
addTriangle(p1_bottom, p2_bottom, p1_top));
1046 post_UUIDs.push_back(context.
addTriangle(p2_bottom, p2_top, p1_top));
1050 float detection_distance = 0.2f;
1051 float expected_buffer = detection_distance * 0.05f;
1053 plantarchitecture.enableSolidObstacleAvoidance(post_UUIDs, detection_distance);
1056 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1057 plantarchitecture.advanceTime(plantID, 8);
1059 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
1060 DOCTEST_CHECK(plant_objects.size() > 0);
1063 float min_distance_to_post = std::numeric_limits<float>::max();
1066 for (
uint objID: plant_objects) {
1068 vec3 min_corner, max_corner;
1072 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),
1073 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)};
1075 for (
int i = 0; i < 8; i++) {
1076 float distance = (corners[i] - post_center).magnitude();
1077 min_distance_to_post = std::min(min_distance_to_post, distance);
1083 float expected_min_distance = post_radius + expected_buffer;
1084 DOCTEST_CHECK(min_distance_to_post >= expected_min_distance * 0.8f);
1088 uint plant_object_count = 0;
1090 for (
uint objID: plant_objects) {
1092 vec3 min_corner, max_corner;
1094 vec3 object_center = (min_corner + max_corner) / 2.0f;
1095 plant_center = plant_center + object_center;
1096 plant_object_count++;
1100 if (plant_object_count > 0) {
1101 plant_center = plant_center / float(plant_object_count);
1102 DOCTEST_CHECK(plant_center.
z > 0.01f);
1106 DOCTEST_CHECK(fabs(plant_center.
x - 0.1f) > expected_buffer * 0.5f);
1110DOCTEST_TEST_CASE(
"PlantArchitecture solid obstacle avoidance works independently") {
1113 plantarchitecture.disableMessages();
1116 std::vector<uint> obstacle_UUIDs;
1122 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.2f));
1125 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1126 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1129 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
1132 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
1133 DOCTEST_CHECK(plant_objects.size() > 0);
1137 uint plant_object_count = 0;
1139 for (
uint objID: plant_objects) {
1141 vec3 min_corner, max_corner;
1143 vec3 object_center = (min_corner + max_corner) / 2.0f;
1144 plant_center = plant_center + object_center;
1145 plant_object_count++;
1149 if (plant_object_count > 0) {
1150 plant_center = plant_center / float(plant_object_count);
1152 DOCTEST_CHECK(plant_center.
z > 0.01f);
1157 std::vector<uint> soft_target_UUIDs;
1158 std::vector<uint> soft_target_IDs;
1159 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSoftCollisionAvoidance(soft_target_UUIDs, soft_target_IDs));
1162 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 2.0f));
1165 std::vector<uint> final_plant_objects = plantarchitecture.getAllObjectIDs();
1166 DOCTEST_CHECK(final_plant_objects.size() >= plant_objects.size());
1169DOCTEST_TEST_CASE(
"PlantArchitecture Per-Plant Attraction Points") {
1174 plantarchitecture.disableMessages();
1177 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1178 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1181 std::vector<vec3> attraction_points_1 = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
1182 std::vector<vec3> attraction_points_2 = {
make_vec3(6.0f, 0.0f, 1.0f),
make_vec3(5.0f, 1.0f, 1.5f)};
1185 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, attraction_points_1, 60.0f, 0.2f, 0.7f));
1186 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID2, attraction_points_2, 45.0f, 0.15f, 0.5f));
1189 DOCTEST_CHECK_NOTHROW(plantarchitecture.setAttractionParameters(plantID1, 80.0f, 0.25f, 0.8f, 0.6f));
1190 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(plantID2, {make_vec3(6.5f, 0.5f, 2.0f)}));
1191 DOCTEST_CHECK_NOTHROW(plantarchitecture.appendAttractionPoints(plantID1, {make_vec3(1.5f, 1.5f, 2.0f)}));
1194 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints(plantID1));
1197 DOCTEST_CHECK_THROWS(plantarchitecture.enableAttractionPoints(9999, attraction_points_1));
1198 DOCTEST_CHECK_THROWS(plantarchitecture.disableAttractionPoints(9999));
1199 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(9999, attraction_points_1));
1200 DOCTEST_CHECK_THROWS(plantarchitecture.appendAttractionPoints(9999, attraction_points_1));
1201 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(9999, 60.0f, 0.15f, 0.7f, 0.75f));
1204DOCTEST_TEST_CASE(
"PlantArchitecture Global vs Per-Plant Interaction") {
1209 plantarchitecture.disableMessages();
1212 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1215 std::vector<vec3> global_attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
1216 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(global_attraction_points, 60.0f, 0.15f, 0.7f));
1219 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1222 std::vector<vec3> specific_attraction_points = {
make_vec3(2.0f, 0.0f, 2.0f)};
1223 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, specific_attraction_points, 45.0f, 0.1f, 0.5f));
1226 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints({make_vec3(3.0f, 0.0f, 3.0f)}));
1229 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints());
1232 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(global_attraction_points));
1235DOCTEST_TEST_CASE(
"PlantArchitecture Plant-Specific Attraction Points Validation") {
1240 plantarchitecture.disableMessages();
1243 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1244 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1247 std::vector<vec3> attraction_points_1 = {
make_vec3(1.0f, 0.0f, 1.0f)};
1248 std::vector<vec3> attraction_points_2 = {
make_vec3(6.0f, 0.0f, 1.0f)};
1251 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, attraction_points_1));
1252 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID2, attraction_points_2));
1255 DOCTEST_CHECK_THROWS(plantarchitecture.enableAttractionPoints(plantID1, {}, 60.0f, 0.15f, 0.7f));
1256 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(plantID1, 0.0f, 0.15f, 0.7f));
1257 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(plantID1, 60.0f, 0.0f, 0.7f));
1260 DOCTEST_CHECK_NOTHROW(plantarchitecture.setAttractionParameters(plantID1, 80.0f, 0.25f, 0.8f, 0.6f));
1261 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(plantID2, {make_vec3(6.5f, 0.5f, 2.0f)}));
1262 DOCTEST_CHECK_NOTHROW(plantarchitecture.appendAttractionPoints(plantID1, {make_vec3(1.5f, 1.5f, 2.0f)}));
1265 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints(plantID1));
1268DOCTEST_TEST_CASE(
"PlantArchitecture removeShootFloralBuds") {
1271 plantarchitecture.disableMessages();
1275 DOCTEST_CHECK_THROWS(plantarchitecture.removeShootFloralBuds(9999, 0));
1278 uint plantID = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1279 DOCTEST_CHECK(plantID != -1);
1282 DOCTEST_CHECK_THROWS(plantarchitecture.removeShootFloralBuds(plantID, 9999));
1285DOCTEST_TEST_CASE(
"PlantArchitecture XML write with flowers and fruit") {
1288 plantarchitecture.disableMessages();
1291 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"tomato"));
1294 vec3 base_position(1.0f, 2.0f, 0.5f);
1295 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(base_position, 180);
1296 DOCTEST_CHECK(plantID !=
uint(-1));
1299 std::string xml_filename =
"test_plant_xml_write.xml";
1300 DOCTEST_CHECK_NOTHROW(plantarchitecture.writePlantStructureXML(plantID, xml_filename));
1303 std::remove(xml_filename.c_str());
1306DOCTEST_TEST_CASE(
"PlantArchitecture child shoot rotation with multiple petioles per internode") {
1309 plantarchitecture.disableMessages();
1316 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1317 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1318 DOCTEST_CHECK(plantID !=
uint(-1));
1321 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1324 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1325 DOCTEST_CHECK(all_primitives.size() > 0);
1332DOCTEST_TEST_CASE(
"PlantArchitecture plant_name optional object data") {
1335 plantarchitecture.disableMessages();
1338 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_name"));
1341 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1342 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1343 DOCTEST_CHECK(plantID !=
uint(-1));
1346 std::string plant_name = plantarchitecture.getPlantName(plantID);
1347 DOCTEST_CHECK(plant_name ==
"bean");
1350 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1353 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1354 DOCTEST_CHECK(all_primitives.size() > 0);
1357 bool found_plant_name_data =
false;
1358 for (
uint objID: all_primitives) {
1360 std::string obj_plant_name;
1362 DOCTEST_CHECK(obj_plant_name ==
"bean");
1363 found_plant_name_data =
true;
1366 DOCTEST_CHECK(found_plant_name_data);
1369DOCTEST_TEST_CASE(
"PlantArchitecture plant_type tree classification") {
1372 plantarchitecture.disableMessages();
1375 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1378 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"almond"));
1379 uint treeID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1380 DOCTEST_CHECK(treeID !=
uint(-1));
1382 std::vector<uint> tree_primitives = plantarchitecture.getAllObjectIDs();
1383 DOCTEST_CHECK(tree_primitives.size() > 0);
1384 bool found_tree_type =
false;
1385 for (
uint objID: tree_primitives) {
1387 std::string plant_type;
1389 DOCTEST_CHECK(plant_type ==
"tree");
1390 found_tree_type =
true;
1393 DOCTEST_CHECK(found_tree_type);
1396DOCTEST_TEST_CASE(
"PlantArchitecture plant_type weed classification") {
1399 plantarchitecture.disableMessages();
1402 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1405 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bindweed"));
1406 uint weedID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1407 DOCTEST_CHECK(weedID !=
uint(-1));
1409 std::vector<uint> weed_primitives = plantarchitecture.getAllObjectIDs();
1410 DOCTEST_CHECK(weed_primitives.size() > 0);
1411 bool found_weed_type =
false;
1412 for (
uint objID: weed_primitives) {
1414 std::string plant_type;
1416 DOCTEST_CHECK(plant_type ==
"weed");
1417 found_weed_type =
true;
1420 DOCTEST_CHECK(found_weed_type);
1423DOCTEST_TEST_CASE(
"PlantArchitecture plant_type herbaceous classification") {
1426 plantarchitecture.disableMessages();
1429 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1432 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1433 uint herbaceousID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1434 DOCTEST_CHECK(herbaceousID !=
uint(-1));
1436 std::vector<uint> herbaceous_primitives = plantarchitecture.getAllObjectIDs();
1437 DOCTEST_CHECK(herbaceous_primitives.size() > 0);
1438 bool found_herbaceous_type =
false;
1439 for (
uint objID: herbaceous_primitives) {
1441 std::string plant_type;
1443 DOCTEST_CHECK(plant_type ==
"herbaceous");
1444 found_herbaceous_type =
true;
1447 DOCTEST_CHECK(found_herbaceous_type);
1450DOCTEST_TEST_CASE(
"PlantArchitecture plant_height optional object data") {
1453 plantarchitecture.disableMessages();
1456 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_height"));
1459 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1460 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1461 DOCTEST_CHECK(plantID !=
uint(-1));
1464 float initial_height = plantarchitecture.getPlantHeight(plantID);
1465 DOCTEST_CHECK(initial_height > 0);
1468 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1471 float final_height = plantarchitecture.getPlantHeight(plantID);
1472 DOCTEST_CHECK(final_height > initial_height);
1475 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1476 DOCTEST_CHECK(all_primitives.size() > 0);
1477 bool found_height_data =
false;
1478 for (
uint objID: all_primitives) {
1483 DOCTEST_CHECK(obj_height > initial_height);
1484 DOCTEST_CHECK(std::abs(obj_height - final_height) < 0.01f);
1485 found_height_data =
true;
1489 DOCTEST_CHECK(found_height_data);
1492DOCTEST_TEST_CASE(
"PlantArchitecture phenology_stage optional object data") {
1495 plantarchitecture.disableMessages();
1498 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"phenology_stage"));
1501 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1502 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1503 DOCTEST_CHECK(plantID !=
uint(-1));
1506 std::string initial_stage = plantarchitecture.determinePhenologyStage(plantID);
1507 DOCTEST_CHECK(initial_stage ==
"vegetative");
1510 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 20.0f));
1513 std::string current_stage = plantarchitecture.determinePhenologyStage(plantID);
1514 DOCTEST_CHECK((current_stage ==
"vegetative" || current_stage ==
"reproductive" || current_stage ==
"senescent" || current_stage ==
"dormant"));
1517 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1518 DOCTEST_CHECK(all_primitives.size() > 0);
1519 bool found_stage_data =
false;
1520 for (
uint objID: all_primitives) {
1522 std::string obj_stage;
1524 DOCTEST_CHECK(obj_stage == current_stage);
1525 found_stage_data =
true;
1528 DOCTEST_CHECK(found_stage_data);
1531DOCTEST_TEST_CASE(
"Build Parameters - Backward Compatibility (Grapevine VSP)") {
1535 plantarchitecture.disableMessages();
1538 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1539 std::map<std::string, float> empty_params;
1540 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, empty_params);
1543 DOCTEST_CHECK(plantID !=
uint(-1));
1546 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1547 DOCTEST_CHECK(plant_primitives.size() > 0);
1550DOCTEST_TEST_CASE(
"Build Parameters - Parameter Override (Grapevine VSP)") {
1554 plantarchitecture.disableMessages();
1558 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1559 std::map<std::string, float> custom_params = {
1560 {
"vine_spacing", 2.5f},
1561 {
"trunk_height", 0.15f}
1563 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, custom_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 - Validation Catches Invalid Values (Grapevine VSP)") {
1576 plantarchitecture.disableMessages();
1578 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1581 std::map<std::string, float> invalid_params1 = {{
"vine_spacing", 10.0f}};
1582 DOCTEST_CHECK_THROWS(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, invalid_params1));
1585 std::map<std::string, float> invalid_params2 = {{
"trunk_height", 2.0f}};
1586 DOCTEST_CHECK_THROWS(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, invalid_params2));
1589DOCTEST_TEST_CASE(
"Build Parameters - Grapevine Wye Trellis Parameters") {
1593 plantarchitecture.disableMessages();
1595 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_Wye");
1596 std::map<std::string, float> trellis_params = {
1597 {
"trunk_height", 0.2f},
1598 {
"cordon_spacing", 0.8f},
1599 {
"vine_spacing", 2.0f},
1600 {
"catch_wire_height", 2.5f}
1602 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, trellis_params);
1604 DOCTEST_CHECK(plantID !=
uint(-1));
1605 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1606 DOCTEST_CHECK(plant_primitives.size() > 0);
1609DOCTEST_TEST_CASE(
"Build Parameters - Tree Training System (Almond)") {
1613 plantarchitecture.disableMessages();
1616 plantarchitecture.loadPlantModelFromLibrary(
"almond");
1617 std::map<std::string, float> tree_params = {
1618 {
"trunk_height", 0.5f},
1619 {
"num_scaffolds", 5.0f},
1620 {
"scaffold_angle", 35.0f}
1622 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, tree_params);
1624 DOCTEST_CHECK(plantID !=
uint(-1));
1625 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1626 DOCTEST_CHECK(plant_primitives.size() > 0);
1629DOCTEST_TEST_CASE(
"Build Parameters - Apple Tree") {
1633 plantarchitecture.disableMessages();
1636 plantarchitecture.loadPlantModelFromLibrary(
"apple");
1637 std::map<std::string, float> apple_params = {
1638 {
"trunk_height", 0.7f},
1639 {
"num_scaffolds", 6.0f},
1640 {
"scaffold_angle", 45.0f}
1642 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, apple_params);
1644 DOCTEST_CHECK(plantID !=
uint(-1));
1647DOCTEST_TEST_CASE(
"Build Parameters - Pistachio Tree Fixed Scaffold System") {
1651 plantarchitecture.disableMessages();
1653 plantarchitecture.loadPlantModelFromLibrary(
"pistachio");
1656 std::map<std::string, float> pistachio_params_min = {{
"num_scaffolds", 2.0f}};
1657 uint plantID_min = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, pistachio_params_min);
1658 DOCTEST_CHECK(plantID_min !=
uint(-1));
1661 std::map<std::string, float> pistachio_params_def = {{
"num_scaffolds", 4.0f}};
1662 uint plantID_def = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(5, 0, 0), 5000, pistachio_params_def);
1663 DOCTEST_CHECK(plantID_def !=
uint(-1));
1666DOCTEST_TEST_CASE(
"Build Parameters - Canopy Building with Parameters") {
1670 plantarchitecture.disableMessages();
1672 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1673 std::map<std::string, float> canopy_params = {
1674 {
"vine_spacing", 2.0f},
1675 {
"trunk_height", 0.12f}
1679 std::vector<uint> plantIDs = plantarchitecture.buildPlantCanopyFromLibrary(
make_vec3(0, 0, 0),
make_vec2(2, 2),
make_int2(2, 2), 0, 1.0f, canopy_params);
1681 DOCTEST_CHECK(plantIDs.size() == 4);
1682 for (
uint plantID: plantIDs) {
1683 DOCTEST_CHECK(plantID !=
uint(-1));
1687DOCTEST_TEST_CASE(
"Build Parameters - Type Casting Float to Uint") {
1691 plantarchitecture.disableMessages();
1693 plantarchitecture.loadPlantModelFromLibrary(
"almond");
1697 std::map<std::string, float> float_params = {
1698 {
"trunk_height", 0.5f},
1699 {
"num_scaffolds", 5.0f},
1700 {
"scaffold_angle", 42.5f}
1703 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, float_params);
1704 DOCTEST_CHECK(plantID !=
uint(-1));
1707DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData 'all' keyword") {
1710 plantarchitecture.disableMessages();
1713 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"all"));
1716 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1717 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1718 DOCTEST_CHECK(plantID !=
uint(-1));
1721 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1724 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1725 DOCTEST_CHECK(all_primitives.size() > 0);
1730 std::vector<std::string> expected_labels = {
"age",
"rank",
"plantID",
"plant_name",
"plant_height",
"plant_type",
"phenology_stage",
"leafID"};
1732 for (
const auto &label: expected_labels) {
1734 for (
uint objID: all_primitives) {
1740 DOCTEST_CHECK_MESSAGE(found,
"Label '" << label <<
"' was not found on any primitive");
1744DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData 'all' case-insensitive") {
1749 plantarchitecture.disableMessages();
1750 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"ALL"));
1757 plantarchitecture.disableMessages();
1758 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"All"));
1765 plantarchitecture.disableMessages();
1766 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"aLl"));
1770DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData invalid label throws error") {
1773 plantarchitecture.disableMessages();
1776 bool caught_error =
false;
1778 plantarchitecture.optionalOutputObjectData(
"invalid_label");
1779 }
catch (
const std::exception &e) {
1780 caught_error =
true;
1781 std::string error_msg(e.what());
1782 DOCTEST_CHECK(error_msg.find(
"invalid_label") != std::string::npos);
1783 DOCTEST_CHECK(error_msg.find(
"not a valid option") != std::string::npos);
1785 DOCTEST_CHECK(caught_error);
1791DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData vector with 'all'") {
1794 plantarchitecture.disableMessages();
1797 std::vector<std::string> labels = {
"all"};
1798 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(labels));
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, 10.0f));
1809 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1810 DOCTEST_CHECK(all_primitives.size() > 0);
1813 bool found_age =
false;
1814 bool found_rank =
false;
1815 bool found_plant_name =
false;
1816 for (
uint objID: all_primitives) {
1822 found_plant_name =
true;
1824 DOCTEST_CHECK(found_age);
1825 DOCTEST_CHECK(found_rank);
1826 DOCTEST_CHECK(found_plant_name);
1829DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData normal labels still work") {
1832 plantarchitecture.disableMessages();
1835 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"age"));
1836 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"rank"));
1839 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1840 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1841 DOCTEST_CHECK(plantID !=
uint(-1));
1844 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
1847 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1848 DOCTEST_CHECK(all_primitives.size() > 0);
1850 bool found_age =
false;
1851 bool found_rank =
false;
1852 bool found_plant_name =
false;
1853 for (
uint objID: all_primitives) {
1859 found_plant_name =
true;
1861 DOCTEST_CHECK(found_age);
1862 DOCTEST_CHECK(found_rank);
1863 DOCTEST_CHECK_FALSE(found_plant_name);
1868DOCTEST_TEST_CASE(
"Nitrogen Model - Initialization") {
1871 plantarchitecture.disableMessages();
1874 plantarchitecture.enableNitrogenModel();
1875 DOCTEST_CHECK(plantarchitecture.isNitrogenModelEnabled());
1878 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1879 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1882 plantarchitecture.advanceTime(plantID, 5.0f);
1885 float initial_N_concentration = 1.5f;
1886 plantarchitecture.initializePlantNitrogenPools(plantID, initial_N_concentration);
1889 plantarchitecture.advanceTime(plantID, 0.1f);
1892 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
1893 DOCTEST_CHECK(all_objects.size() > 0);
1896 bool found_leaf_N =
false;
1897 for (
uint objID: all_objects) {
1900 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
1901 DOCTEST_CHECK(leaf_N_area == doctest::Approx(initial_N_concentration).epsilon(0.1));
1902 found_leaf_N =
true;
1905 DOCTEST_CHECK(found_leaf_N);
1908DOCTEST_TEST_CASE(
"Nitrogen Model - Application and Pool Splitting") {
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, 3.0f);
1919 plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f);
1922 float N_applied = 10.0f;
1923 plantarchitecture.addPlantNitrogen(plantID, N_applied);
1928 plantarchitecture.advanceTime(plantID, 1.0f);
1931 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
1932 bool found_N_accumulation =
false;
1933 for (
uint objID: all_objects) {
1936 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
1937 if (leaf_N_area > 0) {
1938 found_N_accumulation =
true;
1943 DOCTEST_CHECK(found_N_accumulation);
1946DOCTEST_TEST_CASE(
"Nitrogen Model - Rate Limiting") {
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.0f);
1963 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
1966 plantarchitecture.addPlantNitrogen(plantID, 100.0f);
1970 plantarchitecture.advanceTime(plantID, dt);
1973 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
1974 for (
uint objID: all_objects) {
1977 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
1984DOCTEST_TEST_CASE(
"Nitrogen Model - Stress Factor Output") {
1987 plantarchitecture.disableMessages();
1989 plantarchitecture.enableNitrogenModel();
1990 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1991 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1992 plantarchitecture.advanceTime(plantID, 5.0f);
1995 plantarchitecture.initializePlantNitrogenPools(plantID, 0.5f);
1998 plantarchitecture.advanceTime(plantID, 0.1f);
2001 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2002 DOCTEST_CHECK(plant_objects.size() > 0);
2004 bool found_stress_factor =
false;
2005 for (
uint objID: plant_objects) {
2007 float stress_factor;
2008 context.
getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2009 DOCTEST_CHECK(stress_factor >= 0.0f);
2010 DOCTEST_CHECK(stress_factor <= 1.0f);
2012 DOCTEST_CHECK(stress_factor < 1.0f);
2013 found_stress_factor =
true;
2017 DOCTEST_CHECK(found_stress_factor);
2020DOCTEST_TEST_CASE(
"Nitrogen Model - Remobilization") {
2023 plantarchitecture.disableMessages();
2025 plantarchitecture.enableNitrogenModel();
2026 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2027 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2030 plantarchitecture.advanceTime(plantID, 15.0f);
2033 plantarchitecture.initializePlantNitrogenPools(plantID, 0.8f);
2036 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 25.0f));
2039 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2040 bool found_stress_factor =
false;
2041 for (
uint objID: plant_objects) {
2043 float stress_factor;
2044 context.
getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2045 DOCTEST_CHECK(stress_factor < 1.0f);
2046 found_stress_factor =
true;
2050 DOCTEST_CHECK(found_stress_factor);
2053DOCTEST_TEST_CASE(
"Nitrogen Model - Fruit Removal") {
2056 plantarchitecture.disableMessages();
2058 plantarchitecture.enableNitrogenModel();
2061 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2062 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2065 plantarchitecture.advanceTime(plantID, 30.0f);
2068 plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f);
2071 plantarchitecture.addPlantNitrogen(plantID, 50.0f);
2074 plantarchitecture.advanceTime(plantID, 40.0f);
2077 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2078 DOCTEST_CHECK(plant_objects.size() > 0);
2081 bool found_stress_factor =
false;
2082 for (
uint objID: plant_objects) {
2084 found_stress_factor =
true;
2088 DOCTEST_CHECK(found_stress_factor);
2091DOCTEST_TEST_CASE(
"Nitrogen Model - Leaf-to-Fruit Translocation") {
2101 plantarchitecture.disableMessages();
2103 plantarchitecture.enableNitrogenModel();
2104 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2105 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2110 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
2114 plantarchitecture.advanceTime(plantID, 60.0f);
2119 std::vector<uint> fruit_objIDs = plantarchitecture.getPlantFruitObjectIDs(plantID);
2120 if (fruit_objIDs.empty()) {
2122 plantarchitecture.advanceTime(plantID, 30.0f);
2123 fruit_objIDs = plantarchitecture.getPlantFruitObjectIDs(plantID);
2125 if (fruit_objIDs.empty()) {
2132 plantarchitecture.initializePlantNitrogenPools(plantID, N_params.
target_leaf_N_area);
2135 plantarchitecture.advanceTime(plantID, 0.1f);
2138 bool any_leaf_at_target_pre =
false;
2139 for (
uint objID: plantarchitecture.getAllPlantObjectIDs(plantID)) {
2142 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2144 any_leaf_at_target_pre =
true;
2149 DOCTEST_CHECK(any_leaf_at_target_pre);
2153 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 30.0f));
2158 bool any_leaf_drained_below_target =
false;
2159 float min_leaf_N_observed = std::numeric_limits<float>::infinity();
2160 bool any_leaf_with_N =
false;
2161 for (
uint objID: plantarchitecture.getAllPlantObjectIDs(plantID)) {
2164 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2166 any_leaf_drained_below_target =
true;
2168 if (leaf_N_area > 1e-4f) {
2169 min_leaf_N_observed = std::min(min_leaf_N_observed, leaf_N_area);
2170 any_leaf_with_N =
true;
2177 fruit_objIDs = plantarchitecture.getPlantFruitObjectIDs(plantID);
2180 if (!fruit_objIDs.empty()) {
2181 DOCTEST_CHECK(any_leaf_drained_below_target);
2187 if (any_leaf_with_N) {
2192 bool found_stress_factor =
false;
2193 for (
uint objID: plantarchitecture.getAllPlantObjectIDs(plantID)) {
2195 found_stress_factor =
true;
2199 DOCTEST_CHECK(found_stress_factor);
2202DOCTEST_TEST_CASE(
"Nitrogen Model - No Translocation When Pool Adequate") {
2211 plantarchitecture.disableMessages();
2213 plantarchitecture.enableNitrogenModel();
2214 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2215 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2219 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
2221 plantarchitecture.advanceTime(plantID, 30.0f);
2222 plantarchitecture.initializePlantNitrogenPools(plantID, N_params.
target_leaf_N_area);
2223 plantarchitecture.addPlantNitrogen(plantID, 200.0f);
2224 plantarchitecture.advanceTime(plantID, 0.1f);
2227 std::vector<uint> leaves_at_target_pre;
2228 for (
uint objID: plantarchitecture.getAllPlantObjectIDs(plantID)) {
2231 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2233 leaves_at_target_pre.push_back(objID);
2237 DOCTEST_CHECK(leaves_at_target_pre.size() > 0);
2239 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 40.0f));
2243 int leaves_intact = 0;
2244 for (
uint objID: leaves_at_target_pre) {
2252 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2257 DOCTEST_CHECK(leaves_intact > 0);
2260DOCTEST_TEST_CASE(
"Nitrogen Model - Full Growth Cycle Integration") {
2263 plantarchitecture.disableMessages();
2265 plantarchitecture.enableNitrogenModel();
2266 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2267 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2270 plantarchitecture.advanceTime(plantID, 5.0f);
2273 plantarchitecture.initializePlantNitrogenPools(plantID, 1.0f);
2276 for (
int i = 0; i < 5; i++) {
2277 plantarchitecture.addPlantNitrogen(plantID, 5.0f);
2278 plantarchitecture.advanceTime(plantID, 5.0f);
2282 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2283 DOCTEST_CHECK(plant_objects.size() > 0);
2286 bool found_stress_factor =
false;
2287 float final_stress = 0;
2288 for (
uint objID: plant_objects) {
2290 context.
getObjectData(objID,
"nitrogen_stress_factor", final_stress);
2291 found_stress_factor =
true;
2295 DOCTEST_CHECK(found_stress_factor);
2296 DOCTEST_CHECK(final_stress >= 0.0f);
2297 DOCTEST_CHECK(final_stress <= 1.0f);
2300 bool found_leaf_N =
false;
2301 for (
uint objID: plant_objects) {
2304 context.
getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N);
2305 DOCTEST_CHECK(leaf_N >= 0.0f);
2306 found_leaf_N =
true;
2309 DOCTEST_CHECK(found_leaf_N);
2312DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: Zero Nitrogen") {
2315 plantarchitecture.disableMessages();
2317 plantarchitecture.enableNitrogenModel();
2318 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2319 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2320 plantarchitecture.advanceTime(plantID, 5.0f);
2323 DOCTEST_CHECK_NOTHROW(plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f));
2326 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2329 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2330 bool found_stress_factor =
false;
2331 for (
uint objID: plant_objects) {
2333 float stress_factor;
2334 context.
getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2335 DOCTEST_CHECK(stress_factor < 0.2f);
2336 found_stress_factor =
true;
2340 DOCTEST_CHECK(found_stress_factor);
2343DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: Excessive Nitrogen") {
2346 plantarchitecture.disableMessages();
2348 plantarchitecture.enableNitrogenModel();
2349 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2350 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2351 plantarchitecture.advanceTime(plantID, 5.0f);
2354 plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f);
2359 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
2362 DOCTEST_CHECK_NOTHROW(plantarchitecture.addPlantNitrogen(plantID, 1000.0f));
2365 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2368 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2369 bool found_stress_factor =
false;
2370 for (
uint objID: plant_objects) {
2372 float stress_factor;
2373 context.
getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2374 DOCTEST_CHECK(stress_factor <= 1.0f);
2375 DOCTEST_CHECK(stress_factor >= 0.90f);
2376 found_stress_factor =
true;
2380 DOCTEST_CHECK(found_stress_factor);
2383DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: No Leaves") {
2386 plantarchitecture.disableMessages();
2388 plantarchitecture.enableNitrogenModel();
2391 uint plantID = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
2394 DOCTEST_CHECK_NOTHROW(plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f));
2397 DOCTEST_CHECK_NOTHROW(plantarchitecture.addPlantNitrogen(plantID, 10.0f));
2400 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 1.0f));
2403DOCTEST_TEST_CASE(
"Nitrogen Model - Division by Zero Prevention") {
2406 plantarchitecture.disableMessages();
2408 plantarchitecture.enableNitrogenModel();
2409 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2410 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2413 plantarchitecture.advanceTime(plantID, 0.5f);
2416 plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f);
2419 plantarchitecture.addPlantNitrogen(plantID, 10.0f);
2422 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 1.0f));
2425 plantarchitecture.advanceTime(plantID, 20.0f);
2426 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2429DOCTEST_TEST_CASE(
"Nitrogen Model - Enable/Disable") {
2432 plantarchitecture.disableMessages();
2435 DOCTEST_CHECK_FALSE(plantarchitecture.isNitrogenModelEnabled());
2438 plantarchitecture.enableNitrogenModel();
2439 DOCTEST_CHECK(plantarchitecture.isNitrogenModelEnabled());
2442 plantarchitecture.disableNitrogenModel();
2443 DOCTEST_CHECK_FALSE(plantarchitecture.isNitrogenModelEnabled());
2446 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2447 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2448 plantarchitecture.advanceTime(plantID, 5.0f);
2450 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2451 bool found_nitrogen_data =
false;
2452 for (
uint objID: plant_objects) {
2454 found_nitrogen_data =
true;
2458 DOCTEST_CHECK_FALSE(found_nitrogen_data);
2463DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - no parameter success") {
2467 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2468 std::vector<std::string> labels = plantarchitecture.listShootTypeLabels();
2470 DOCTEST_CHECK(labels.size() == 2);
2471 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"unifoliate") != labels.end());
2472 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"trifoliate") != labels.end());
2475DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - no parameter error") {
2476 std::string error_message;
2483 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels()));
2487DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - string parameter success") {
2492 std::vector<std::string> bean_labels = plantarchitecture.listShootTypeLabels(
"bean");
2493 DOCTEST_CHECK(bean_labels.size() == 2);
2494 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"unifoliate") != bean_labels.end());
2495 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"trifoliate") != bean_labels.end());
2498 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(
"tomato");
2499 DOCTEST_CHECK(tomato_labels.size() == 1);
2500 DOCTEST_CHECK(std::find(tomato_labels.begin(), tomato_labels.end(),
"mainstem") != tomato_labels.end());
2503DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - string parameter error") {
2504 std::string error_message;
2511 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels(
"nonexistent_plant")));
2515DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - state preservation") {
2520 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2523 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(
"tomato");
2526 std::vector<std::string> current_labels = plantarchitecture.listShootTypeLabels();
2527 DOCTEST_CHECK(current_labels.size() == 2);
2528 DOCTEST_CHECK(std::find(current_labels.begin(), current_labels.end(),
"unifoliate") != current_labels.end());
2529 DOCTEST_CHECK(std::find(current_labels.begin(), current_labels.end(),
"trifoliate") != current_labels.end());
2532DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - all plant models") {
2536 std::vector<std::string> all_plants = plantarchitecture.getAvailablePlantModels();
2539 for (
const auto &plant: all_plants) {
2540 std::vector<std::string> labels;
2541 DOCTEST_CHECK_NOTHROW(labels = plantarchitecture.listShootTypeLabels(plant));
2542 DOCTEST_CHECK(!labels.empty());
2546DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - uint parameter success") {
2551 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2552 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2555 std::vector<std::string> labels = plantarchitecture.listShootTypeLabels(plantID);
2558 DOCTEST_CHECK(labels.size() == 2);
2559 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"unifoliate") != labels.end());
2560 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"trifoliate") != labels.end());
2563DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - uint parameter error") {
2564 std::string error_message;
2571 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels(999)));
2575DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - multiple instances") {
2580 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2581 uint bean_plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2584 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2585 uint tomato_plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(1, 0, 0), 0);
2588 std::vector<std::string> bean_labels = plantarchitecture.listShootTypeLabels(bean_plantID);
2589 DOCTEST_CHECK(bean_labels.size() == 2);
2590 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"unifoliate") != bean_labels.end());
2591 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"trifoliate") != bean_labels.end());
2593 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(tomato_plantID);
2594 DOCTEST_CHECK(tomato_labels.size() == 1);
2595 DOCTEST_CHECK(std::find(tomato_labels.begin(), tomato_labels.end(),
"mainstem") != tomato_labels.end());
2598DOCTEST_TEST_CASE(
"PlantArchitecture getPlantInternodeObjectIDs with shoot type filter") {
2603 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2604 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0.0);
2607 std::vector<uint> all_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID);
2608 DOCTEST_CHECK(all_internodes.size() > 0);
2611 std::vector<uint> unifoliate_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID,
"unifoliate");
2612 DOCTEST_CHECK(unifoliate_internodes.size() > 0);
2615 std::vector<uint> trifoliate_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID,
"trifoliate");
2616 DOCTEST_CHECK(trifoliate_internodes.size() > 0);
2619 for (
uint objID : unifoliate_internodes) {
2620 DOCTEST_CHECK(std::find(all_internodes.begin(), all_internodes.end(), objID) != all_internodes.end());
2622 for (
uint objID : trifoliate_internodes) {
2623 DOCTEST_CHECK(std::find(all_internodes.begin(), all_internodes.end(), objID) != all_internodes.end());
2627 for (
uint objID : unifoliate_internodes) {
2628 DOCTEST_CHECK(std::find(trifoliate_internodes.begin(), trifoliate_internodes.end(), objID) == trifoliate_internodes.end());
2632 DOCTEST_CHECK(unifoliate_internodes.size() + trifoliate_internodes.size() == all_internodes.size());
2635DOCTEST_TEST_CASE(
"PlantArchitecture getPlantInternodeObjectIDs with shoot type filter - error cases") {
2636 std::string error_message;
2642 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2643 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0.0);
2646 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.getPlantInternodeObjectIDs(plantID,
"nonexistent_shoot_type")));
2649 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.getPlantInternodeObjectIDs(9999,
"unifoliate")));
2653DOCTEST_TEST_CASE(
"PlantArchitecture setProgressCallback") {
2654 std::vector<float> progress_values;
2655 std::vector<std::string> messages;
2662 plantarchitecture.disableMessages();
2664 plantarchitecture.setProgressCallback([&](
float progress,
const std::string &msg) {
2665 progress_values.push_back(progress);
2666 messages.push_back(msg);
2669 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2670 plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5);
2673 plantarchitecture.advanceTime(1.f);
2677 DOCTEST_CHECK(progress_values.size() > 0);
2680 for (
float p : progress_values) {
2681 DOCTEST_CHECK(p >= 0.f);
2682 DOCTEST_CHECK(p <= 1.f);
2686 if (!progress_values.empty()) {
2687 DOCTEST_CHECK(progress_values.back() == doctest::Approx(1.0f));
2691 for (
const auto &msg : messages) {
2692 DOCTEST_CHECK(!msg.empty());
2696DOCTEST_TEST_CASE(
"getAllPlantUUIDs with include_hidden parameter") {
2699 plantarchitecture.disableMessages();
2700 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2701 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000);
2703 std::vector<uint> uuids_default = plantarchitecture.getAllPlantUUIDs(plantID);
2704 std::vector<uint> uuids_no_hidden = plantarchitecture.getAllPlantUUIDs(plantID,
false);
2705 std::vector<uint> uuids_with_hidden = plantarchitecture.getAllPlantUUIDs(plantID,
true);
2708 DOCTEST_CHECK(uuids_default.size() == uuids_no_hidden.size());
2711 DOCTEST_CHECK(uuids_with_hidden.size() > uuids_no_hidden.size());
2714DOCTEST_TEST_CASE(
"deletePlantInstance cleans up prototypes when all plants deleted") {
2717 plantarchitecture.disableMessages();
2718 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2720 uint plantID1 = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000);
2721 uint plantID2 = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(1, 0, 0), 5000);
2724 std::vector<uint> all_uuids = plantarchitecture.getAllPlantUUIDs(plantID1,
true);
2725 std::vector<uint> visible_uuids = plantarchitecture.getAllPlantUUIDs(plantID1,
false);
2726 DOCTEST_CHECK(all_uuids.size() > visible_uuids.size());
2729 std::set<uint> visible_set(visible_uuids.begin(), visible_uuids.end());
2730 std::vector<uint> prototype_uuids;
2731 for (
uint uuid : all_uuids) {
2732 if (visible_set.find(uuid) == visible_set.end()) {
2733 prototype_uuids.push_back(uuid);
2736 DOCTEST_CHECK(prototype_uuids.size() > 0);
2739 plantarchitecture.deletePlantInstance(plantID1);
2740 for (
uint uuid : prototype_uuids) {
2745 plantarchitecture.deletePlantInstance(plantID2);
2746 for (
uint uuid : prototype_uuids) {
2751DOCTEST_TEST_CASE(
"deletePlantInstance preserves prototypes when plants remain") {
2754 plantarchitecture.disableMessages();
2755 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2757 uint plantID1 = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000);
2758 uint plantID2 = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(1, 0, 0), 5000);
2761 std::vector<uint> uuids_with_hidden = plantarchitecture.getAllPlantUUIDs(plantID2,
true);
2762 std::vector<uint> uuids_without_hidden = plantarchitecture.getAllPlantUUIDs(plantID2,
false);
2763 DOCTEST_CHECK(uuids_with_hidden.size() > uuids_without_hidden.size());
2766 plantarchitecture.deletePlantInstance(plantID1);
2768 std::vector<uint> uuids_after = plantarchitecture.getAllPlantUUIDs(plantID2,
true);
2769 DOCTEST_CHECK(uuids_after.size() > plantarchitecture.getAllPlantUUIDs(plantID2,
false).size());
2772DOCTEST_TEST_CASE(
"USD export basic structure") {
2775 plantarchitecture.disableMessages();
2776 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2778 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2780 std::string filename =
"test_usd_basic.usda";
2781 plantarchitecture.writePlantStructureUSD(plantID, filename);
2784 std::ifstream file(filename);
2785 DOCTEST_CHECK(file.is_open());
2787 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
2789 DOCTEST_CHECK(!content.empty());
2792 DOCTEST_CHECK(content.find(
"PhysicsArticulationRootAPI") != std::string::npos);
2793 DOCTEST_CHECK(content.find(
"PhysxArticulationAPI") != std::string::npos);
2794 DOCTEST_CHECK(content.find(
"PhysicsScene") != std::string::npos);
2795 DOCTEST_CHECK(content.find(
"PhysicsMaterialAPI") != std::string::npos);
2796 DOCTEST_CHECK(content.find(
"PhysicsFixedJoint") != std::string::npos);
2797 DOCTEST_CHECK(content.find(
"PhysicsRigidBodyAPI") != std::string::npos);
2798 DOCTEST_CHECK(content.find(
"PhysicsSphericalJoint") != std::string::npos);
2799 DOCTEST_CHECK(content.find(
"PhysicsDriveAPI:angular") != std::string::npos);
2802 size_t link_count = 0;
2804 while ((pos = content.find(
"PhysicsRigidBodyAPI", pos)) != std::string::npos) {
2808 DOCTEST_CHECK(link_count > 0);
2811 size_t fixed_count = 0;
2813 while ((pos = content.find(
"PhysicsFixedJoint", pos)) != std::string::npos) {
2817 DOCTEST_CHECK(fixed_count == 1);
2819 std::remove(filename.c_str());
2822DOCTEST_TEST_CASE(
"USD export physics properties") {
2825 plantarchitecture.disableMessages();
2826 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2828 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2831 params.elastic_modulus = 1e9f;
2832 params.wood_density = 500.f;
2834 std::string filename =
"test_usd_physics.usda";
2835 plantarchitecture.writePlantStructureUSD(plantID, filename, params);
2838 std::ifstream file(filename);
2839 DOCTEST_CHECK(file.is_open());
2841 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
2845 DOCTEST_CHECK(content.find(
"physics:mass") != std::string::npos);
2846 DOCTEST_CHECK(content.find(
"drive:angular:physics:stiffness") != std::string::npos);
2847 DOCTEST_CHECK(content.find(
"drive:angular:physics:damping") != std::string::npos);
2850 DOCTEST_CHECK(content.find(
"physics:gravityMagnitude = 9.81") != std::string::npos);
2852 std::remove(filename.c_str());
2855DOCTEST_TEST_CASE(
"USD export branching topology") {
2858 plantarchitecture.disableMessages();
2861 plantarchitecture.loadPlantModelFromLibrary(
"almond");
2862 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2864 std::string filename =
"test_usd_branching.usda";
2865 plantarchitecture.writePlantStructureUSD(plantID, filename);
2867 std::ifstream file(filename);
2868 DOCTEST_CHECK(file.is_open());
2870 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
2874 size_t link_count = 0;
2876 while ((pos = content.find(
"PhysicsRigidBodyAPI", pos)) != std::string::npos) {
2880 DOCTEST_CHECK(link_count > 3);
2883 DOCTEST_CHECK(content.find(
"physics:body0") != std::string::npos);
2884 DOCTEST_CHECK(content.find(
"physics:body1") != std::string::npos);
2886 std::remove(filename.c_str());
2889DOCTEST_TEST_CASE(
"USD export error handling") {
2892 plantarchitecture.disableMessages();
2895 DOCTEST_CHECK_THROWS(plantarchitecture.writePlantStructureUSD(9999,
"test.usda"));
2898 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2899 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2902 DOCTEST_CHECK_THROWS(plantarchitecture.writePlantStructureUSD(plantID,
"test.txt"));
2905DOCTEST_TEST_CASE(
"USD export organs") {
2908 plantarchitecture.disableMessages();
2909 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2911 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2914 std::string filename =
"test_usd_organs.usda";
2915 plantarchitecture.writePlantStructureUSD(plantID, filename, params);
2917 std::ifstream file(filename);
2918 DOCTEST_CHECK(file.is_open());
2920 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
2924 DOCTEST_CHECK(content.find(
"Pet") != std::string::npos);
2927 DOCTEST_CHECK(content.find(
"Leaf") != std::string::npos);
2928 DOCTEST_CHECK(content.find(
"def Mesh \"Visual\"") != std::string::npos);
2929 DOCTEST_CHECK(content.find(
"def Mesh \"Collision\"") != std::string::npos);
2932 DOCTEST_CHECK(content.find(
"\"MaterialBindingAPI\"") != std::string::npos);
2935 DOCTEST_CHECK(content.find(
"bool doubleSided = 1") != std::string::npos);
2936 DOCTEST_CHECK(content.find(
"subdivisionScheme = \"none\"") != std::string::npos);
2939 DOCTEST_CHECK(content.find(
"primvars:normals") != std::string::npos);
2942 DOCTEST_CHECK(content.find(
"asset inputs:file = @/") == std::string::npos);
2944 std::remove(filename.c_str());
2947DOCTEST_TEST_CASE(
"USD export minimum segment filtering") {
2950 plantarchitecture.disableMessages();
2953 plantarchitecture.loadPlantModelFromLibrary(
"almond");
2955 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2959 std::string filename_default =
"test_usd_filter_default.usda";
2960 plantarchitecture.writePlantStructureUSD(plantID, filename_default, params_default);
2965 std::string filename_strict =
"test_usd_filter_strict.usda";
2966 plantarchitecture.writePlantStructureUSD(plantID, filename_strict, params_strict);
2969 auto countOccurrences = [](
const std::string &content,
const std::string &token) {
2972 while ((pos = content.find(token, pos)) != std::string::npos) {
2979 std::ifstream f1(filename_default);
2980 std::string content1((std::istreambuf_iterator<char>(f1)), std::istreambuf_iterator<char>());
2983 std::ifstream f2(filename_strict);
2984 std::string content2((std::istreambuf_iterator<char>(f2)), std::istreambuf_iterator<char>());
2987 size_t links_default = countOccurrences(content1,
"PhysicsRigidBodyAPI");
2988 size_t links_strict = countOccurrences(content2,
"PhysicsRigidBodyAPI");
2991 DOCTEST_CHECK(links_strict <= links_default);
2993 std::remove(filename_default.c_str());
2994 std::remove(filename_strict.c_str());
2997DOCTEST_TEST_CASE(
"Growth frame registration") {
3000 plantarchitecture.disableMessages();
3001 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3003 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3005 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 0);
3008 plantarchitecture.registerGrowthFrame(plantID);
3009 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 1);
3012 plantarchitecture.advanceTime(10);
3013 plantarchitecture.registerGrowthFrame(plantID);
3014 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 2);
3016 plantarchitecture.advanceTime(10);
3017 plantarchitecture.registerGrowthFrame(plantID);
3018 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 3);
3021 plantarchitecture.clearGrowthFrames(plantID);
3022 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 0);
3025 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(9999) == 0);
3028DOCTEST_TEST_CASE(
"Growth USD export basic") {
3031 plantarchitecture.disableMessages();
3032 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3034 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3036 for (
int i = 0; i < 3; i++) {
3037 plantarchitecture.advanceTime(10);
3038 plantarchitecture.registerGrowthFrame(plantID);
3041 std::string filename =
"test_growth_usd.usda";
3043 plantarchitecture.writePlantGrowthUSD(plantID, filename, 1.0f);
3046 std::ifstream f(filename);
3047 DOCTEST_CHECK(f.is_open());
3048 std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
3053 DOCTEST_CHECK(content.find(
"startTimeCode = 0") != std::string::npos);
3054 DOCTEST_CHECK(content.find(
"endTimeCode = 48") != std::string::npos);
3055 DOCTEST_CHECK(content.find(
"timeCodesPerSecond = 24") != std::string::npos);
3056 DOCTEST_CHECK(content.find(
"framesPerSecond = 24") != std::string::npos);
3057 DOCTEST_CHECK(content.find(
"upAxis = \"Z\"") != std::string::npos);
3060 DOCTEST_CHECK(content.find(
"xformOp:translate.timeSamples") != std::string::npos);
3061 DOCTEST_CHECK(content.find(
"xformOp:orient.timeSamples") != std::string::npos);
3062 DOCTEST_CHECK(content.find(
"visibility.timeSamples") != std::string::npos);
3065 DOCTEST_CHECK(content.find(
"PhysicsArticulationRootAPI") == std::string::npos);
3066 DOCTEST_CHECK(content.find(
"PhysicsRigidBodyAPI") == std::string::npos);
3067 DOCTEST_CHECK(content.find(
"PhysicsJoint") == std::string::npos);
3070 DOCTEST_CHECK(content.find(
"def Mesh \"Visual\"") != std::string::npos);
3072 std::remove(filename.c_str());
3075DOCTEST_TEST_CASE(
"Growth USD export visibility toggling") {
3078 plantarchitecture.disableMessages();
3079 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3082 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 1);
3083 plantarchitecture.registerGrowthFrame(plantID);
3086 plantarchitecture.advanceTime(30);
3087 plantarchitecture.registerGrowthFrame(plantID);
3089 std::string filename =
"test_growth_visibility.usda";
3090 plantarchitecture.writePlantGrowthUSD(plantID, filename);
3092 std::ifstream f(filename);
3093 DOCTEST_CHECK(f.is_open());
3094 std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
3099 DOCTEST_CHECK(content.find(
"\"invisible\"") != std::string::npos);
3100 DOCTEST_CHECK(content.find(
"\"inherited\"") != std::string::npos);
3102 std::remove(filename.c_str());
3105DOCTEST_TEST_CASE(
"Growth USD export error handling") {
3108 plantarchitecture.disableMessages();
3113 plantarchitecture.registerGrowthFrame(9999);
3117 DOCTEST_CHECK(threw);
3120 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3121 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3124 plantarchitecture.writePlantGrowthUSD(plantID,
"test_no_frames.usda");
3128 DOCTEST_CHECK(threw);
3132 return helios::runDoctestWithValidation(argc, argv);