3#define DOCTEST_CONFIG_IMPLEMENT
5#include "doctest_utils.h"
12DOCTEST_TEST_CASE(
"PlantArchitecture Constructor") {
17DOCTEST_TEST_CASE(
"PlantArchitecture Cancel Flag") {
22 auto build = [](
bool cancel) -> std::size_t {
26 pa.loadPlantModelFromLibrary(
"bean");
27 int flag = cancel ? 1 : 0;
28 pa.setCancelFlag(&flag);
32 DOCTEST_CHECK(build(
false) == 9);
33 DOCTEST_CHECK(build(
true) == 0);
37 auto grow_height = [](
bool cancel) ->
float {
41 pa.loadPlantModelFromLibrary(
"bean");
42 uint pid = pa.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0.f);
43 int flag = cancel ? 1 : 0;
44 pa.setCancelFlag(&flag);
46 return pa.getPlantHeight(pid);
48 float full = grow_height(
false);
49 float cancelled = grow_height(
true);
50 DOCTEST_CHECK(full > 0.f);
51 DOCTEST_CHECK(cancelled < full);
54DOCTEST_TEST_CASE(
"ShootParameters defineChildShootTypes valid input") {
56 std::vector<std::string> labels = {
"typeA",
"typeB"};
57 std::vector<float> probabilities = {0.4f, 0.6f};
61DOCTEST_TEST_CASE(
"ShootParameters defineChildShootTypes size mismatch") {
64 std::vector<std::string> labels = {
"typeA",
"typeB"};
65 std::vector<float> probabilities = {0.4f};
69DOCTEST_TEST_CASE(
"ShootParameters defineChildShootTypes empty vectors") {
72 std::vector<std::string> labels = {};
73 std::vector<float> probabilities = {};
77DOCTEST_TEST_CASE(
"ShootParameters defineChildShootTypes probabilities sum not equal to 1") {
80 std::vector<std::string> labels = {
"typeA",
"typeB"};
81 std::vector<float> probabilities = {0.3f, 0.6f};
85DOCTEST_TEST_CASE(
"PlantArchitecture defineShootType") {
89 DOCTEST_CHECK_NOTHROW(pa_test.defineShootType(
"newShootType", sp_define));
92DOCTEST_TEST_CASE(
"LeafPrototype Constructor") {
94 std::minstd_rand0 *generator =
context.getRandomGenerator();
96 DOCTEST_CHECK(lp_test.subdivisions == 1);
97 DOCTEST_CHECK(lp_test.unique_prototypes == 1);
98 DOCTEST_CHECK(lp_test.leaf_offset.x == doctest::Approx(0.0f).epsilon(err_tol));
99 DOCTEST_CHECK(lp_test.leaf_offset.y == doctest::Approx(0.0f).epsilon(err_tol));
100 DOCTEST_CHECK(lp_test.leaf_offset.z == doctest::Approx(0.0f).epsilon(err_tol));
103DOCTEST_TEST_CASE(
"PhytomerParameters Constructor") {
105 std::minstd_rand0 *generator =
context.getRandomGenerator();
109DOCTEST_TEST_CASE(
"Plant Library Model Building - almond") {
112 plantarchitecture.disableMessages();
113 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"almond"));
114 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
117DOCTEST_TEST_CASE(
"Plant Library Model Building - apple") {
120 plantarchitecture.disableMessages();
121 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"apple"));
122 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
125DOCTEST_TEST_CASE(
"Plant Library Model Building - asparagus") {
128 plantarchitecture.disableMessages();
129 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"asparagus"));
130 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
133DOCTEST_TEST_CASE(
"Plant Library Model Building - bindweed") {
136 plantarchitecture.disableMessages();
137 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bindweed"));
138 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
141DOCTEST_TEST_CASE(
"Plant Library Model Building - bean") {
144 plantarchitecture.disableMessages();
145 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
146 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
149DOCTEST_TEST_CASE(
"Material Naming - bean plant materials have descriptive names") {
152 plantarchitecture.disableMessages();
153 plantarchitecture.loadPlantModelFromLibrary(
"bean");
154 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000);
157 std::vector<uint> all_UUIDs = plantarchitecture.getAllPlantUUIDs(plantID);
158 DOCTEST_CHECK(all_UUIDs.size() > 0);
159 for (
uint UUID : all_UUIDs) {
160 std::string label =
context.getPrimitiveMaterialLabel(UUID);
161 DOCTEST_CHECK(label.substr(0, 7) !=
"__auto_");
165 std::vector<std::string> materials =
context.listMaterials();
168 bool found_trifoliate_leaf =
false;
169 bool found_unifoliate_leaf =
false;
170 bool found_stem =
false;
171 for (
const auto &label : materials) {
172 if (label.find(
"bean") != std::string::npos && label.find(
"trifoliate") != std::string::npos && label.find(
"leaf") != std::string::npos) {
173 found_trifoliate_leaf =
true;
175 if (label.find(
"bean") != std::string::npos && label.find(
"unifoliate") != std::string::npos && label.find(
"leaf") != std::string::npos) {
176 found_unifoliate_leaf =
true;
178 if (label.find(
"bean") != std::string::npos && label.find(
"stem") != std::string::npos) {
182 DOCTEST_CHECK(found_trifoliate_leaf);
183 DOCTEST_CHECK(found_unifoliate_leaf);
184 DOCTEST_CHECK(found_stem);
187DOCTEST_TEST_CASE(
"Shoot Topology Accessors - getAllShootIDs and getPlantShoot") {
189 context.seedRandomGenerator(12345);
191 plantarchitecture.disableMessages();
192 plantarchitecture.loadPlantModelFromLibrary(
"bean");
193 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
196 std::vector<uint> shootIDs = plantarchitecture.getAllShootIDs(plantID);
197 DOCTEST_CHECK(shootIDs.size() > 0);
198 for (
uint i = 0; i < shootIDs.size(); i++) {
199 DOCTEST_CHECK(shootIDs.at(i) == i);
203 const std::shared_ptr<Shoot> &base_shoot = plantarchitecture.getPlantShoot(plantID, 0);
204 DOCTEST_CHECK(base_shoot->ID == 0);
205 DOCTEST_CHECK(base_shoot->rank == 0);
206 DOCTEST_CHECK(base_shoot->parent_shoot_ID == -1);
207 DOCTEST_CHECK(!base_shoot->shoot_internode_vertices.empty());
208 DOCTEST_CHECK(base_shoot->shoot_internode_vertices.size() == base_shoot->shoot_internode_radii.size());
213 for (
uint shootID : shootIDs) {
214 const std::shared_ptr<Shoot> &shoot = plantarchitecture.getPlantShoot(plantID, shootID);
215 if (shoot->parent_shoot_ID >= 0) {
216 const std::shared_ptr<Shoot> &parent = plantarchitecture.getPlantShoot(plantID,
static_cast<uint>(shoot->parent_shoot_ID));
217 DOCTEST_CHECK(shoot->rank >= parent->rank);
218 DOCTEST_CHECK(shoot->rank <= parent->rank + 1);
226 auto throws = [&](
const std::function<void()> &fn) {
238 DOCTEST_CHECK(
throws([&]() {
static_cast<void>(plantarchitecture.getAllShootIDs(plantID + 999)); }));
239 DOCTEST_CHECK(
throws([&]() {
static_cast<void>(plantarchitecture.getPlantShoot(plantID,
static_cast<uint>(shootIDs.size()))); }));
240 DOCTEST_CHECK(
throws([&]() {
static_cast<void>(plantarchitecture.getPlantShoot(plantID + 999, 0)); }));
243DOCTEST_TEST_CASE(
"Plant Library Model Building - cheeseweed") {
246 plantarchitecture.disableMessages();
247 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"cheeseweed"));
248 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
251DOCTEST_TEST_CASE(
"Plant Library Model Building - cowpea") {
254 plantarchitecture.disableMessages();
255 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"cowpea"));
256 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
259DOCTEST_TEST_CASE(
"Plant Library Model Building - grapevine_VSP") {
262 plantarchitecture.disableMessages();
263 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP"));
264 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
267DOCTEST_TEST_CASE(
"Plant Library Model Building - maize") {
270 plantarchitecture.disableMessages();
271 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"maize"));
272 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
275DOCTEST_TEST_CASE(
"Plant Library Model Building - olive") {
278 plantarchitecture.disableMessages();
279 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"olive"));
280 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
283DOCTEST_TEST_CASE(
"Plant Library Model Building - pistachio") {
286 plantarchitecture.disableMessages();
287 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"pistachio"));
288 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
291DOCTEST_TEST_CASE(
"Plant Library Model Building - puncturevine") {
294 plantarchitecture.disableMessages();
295 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"puncturevine"));
296 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
299DOCTEST_TEST_CASE(
"Plant Library Model Building - easternredbud") {
302 plantarchitecture.disableMessages();
303 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"easternredbud"));
304 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
307DOCTEST_TEST_CASE(
"Plant Library Model Building - rice") {
310 plantarchitecture.disableMessages();
311 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"rice"));
312 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
315DOCTEST_TEST_CASE(
"Plant Library Model Building - butterlettuce") {
318 plantarchitecture.disableMessages();
319 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"butterlettuce"));
320 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
323DOCTEST_TEST_CASE(
"Plant Library Model Building - sorghum") {
326 plantarchitecture.disableMessages();
327 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"sorghum"));
328 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
331DOCTEST_TEST_CASE(
"Plant Library Model Building - soybean") {
334 plantarchitecture.disableMessages();
335 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"soybean"));
336 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
339DOCTEST_TEST_CASE(
"Plant Library Model Building - strawberry") {
342 plantarchitecture.disableMessages();
343 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"strawberry"));
344 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
347DOCTEST_TEST_CASE(
"Plant Library Model Building - sugarbeet") {
350 plantarchitecture.disableMessages();
351 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"sugarbeet"));
352 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
355DOCTEST_TEST_CASE(
"Plant Library Model Building - tomato") {
358 plantarchitecture.disableMessages();
359 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"tomato"));
360 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
363DOCTEST_TEST_CASE(
"Plant Library Model Building - walnut") {
366 plantarchitecture.disableMessages();
367 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"walnut"));
368 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
371DOCTEST_TEST_CASE(
"Plant Library Model Building - wheat") {
374 plantarchitecture.disableMessages();
375 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"wheat"));
376 DOCTEST_CHECK_NOTHROW(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000));
379DOCTEST_TEST_CASE(
"PlantArchitecture writeTreeQSM") {
382 plantarchitecture.disableMessages();
385 plantarchitecture.loadPlantModelFromLibrary(
"bean");
386 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 50);
389 std::string filename =
"test_plant_qsm.txt";
390 DOCTEST_CHECK_NOTHROW(plantarchitecture.writeQSMCylinderFile(plantID, filename));
393 std::ifstream file(filename);
394 DOCTEST_CHECK(file.good());
397 std::string header_line;
398 std::getline(file, header_line);
401 DOCTEST_CHECK(header_line.find(
"radius (m)") != std::string::npos);
402 DOCTEST_CHECK(header_line.find(
"length (m)") != std::string::npos);
403 DOCTEST_CHECK(header_line.find(
"start_point") != std::string::npos);
404 DOCTEST_CHECK(header_line.find(
"axis_direction") != std::string::npos);
405 DOCTEST_CHECK(header_line.find(
"branch") != std::string::npos);
406 DOCTEST_CHECK(header_line.find(
"branch_order") != std::string::npos);
409 std::string data_line;
410 bool has_data =
static_cast<bool>(std::getline(file, data_line));
411 DOCTEST_CHECK(has_data);
415 size_t tab_count = std::count(data_line.begin(), data_line.end(),
'\t');
416 DOCTEST_CHECK(tab_count >= 12);
422 std::remove(filename.c_str());
426DOCTEST_TEST_CASE(
"PlantArchitecture writeTreeQSM invalid plant") {
430 plantarchitecture.disableMessages();
433 DOCTEST_CHECK_THROWS(plantarchitecture.writeQSMCylinderFile(999,
"invalid_plant.txt"));
436DOCTEST_TEST_CASE(
"PlantArchitecture pruneSolidBoundaryCollisions") {
439 plantarchitecture.disableMessages();
442 plantarchitecture.enableSoftCollisionAvoidance();
445 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
448 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
449 plantarchitecture.advanceTime(plantID, 15);
452 std::vector<uint> objects_before_boundaries = plantarchitecture.getAllObjectIDs();
453 uint count_before_boundaries = objects_before_boundaries.size();
456 DOCTEST_CHECK(count_before_boundaries > 0);
460 std::vector<uint> boundary_UUIDs;
461 for (
int i = -2; i <= 2; i++) {
462 for (
int j = -2; j <= 2; j++) {
464 boundary_UUIDs.push_back(
context.addTriangle(
make_vec3(i * 0.1f, j * 0.1f, 0.05f),
make_vec3((i + 1) * 0.1f, j * 0.1f, 0.05f),
make_vec3(i * 0.1f, (j + 1) * 0.1f, 0.05f)));
469 plantarchitecture.enableSolidObstacleAvoidance(boundary_UUIDs, 0.2f);
473 plantarchitecture.advanceTime(plantID, 0.1f);
476 std::vector<uint> final_objects = plantarchitecture.getAllObjectIDs();
477 uint final_count = final_objects.size();
484 DOCTEST_CHECK(final_count > 0);
487DOCTEST_TEST_CASE(
"PlantArchitecture pruneSolidBoundaryCollisions no boundaries") {
490 plantarchitecture.disableMessages();
493 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
496 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
497 plantarchitecture.advanceTime(plantID, 5);
500 std::vector<uint> initial_objects = plantarchitecture.getAllObjectIDs();
501 uint initial_count = initial_objects.size();
504 plantarchitecture.advanceTime(plantID, 2);
507 std::vector<uint> final_objects = plantarchitecture.getAllObjectIDs();
508 uint final_count = final_objects.size();
510 DOCTEST_CHECK(final_count >= initial_count);
513DOCTEST_TEST_CASE(
"PlantArchitecture advanceTime after pruneBranch leaves empty shoot shell") {
521 plantarchitecture.disableMessages();
523 plantarchitecture.loadPlantModelFromLibrary(
"apple");
524 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 365);
527 const std::shared_ptr<Shoot> trunk = plantarchitecture.getPlantShoot(plantID, 0);
529 for (
const auto &[node_index, shootIDs]: trunk->childIDs) {
530 for (
const int shootID: shootIDs) {
531 if (!plantarchitecture.getPlantShoot(plantID, shootID)->phytomers.empty()) {
540 DOCTEST_REQUIRE(branchID >= 0);
542 DOCTEST_CHECK_NOTHROW(plantarchitecture.pruneBranch(plantID, (
uint) branchID, 0));
543 DOCTEST_CHECK(plantarchitecture.getPlantShoot(plantID, (
uint) branchID)->phytomers.empty());
546 std::vector<uint> internode_objIDs = plantarchitecture.getShootInternodeObjectIDs(plantID);
547 for (
uint objID: internode_objIDs) {
548 DOCTEST_CHECK(
context.doesObjectExist(objID));
552 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 365));
555DOCTEST_TEST_CASE(
"PlantArchitecture hard collision avoidance base stem protection") {
558 plantarchitecture.disableMessages();
561 plantarchitecture.enableSoftCollisionAvoidance();
564 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
568 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, -0.05f), 0);
571 std::vector<uint> ground_UUIDs;
574 for (
int i = -2; i <= 2; i++) {
575 for (
int j = -2; j <= 2; j++) {
576 ground_UUIDs.push_back(
context.addTriangle(
make_vec3(i * 0.2f, j * 0.2f, 0.0f),
577 make_vec3((i + 1) * 0.2f, j * 0.2f, 0.0f),
make_vec3(i * 0.2f, (j + 1) * 0.2f, 0.0f)));
578 ground_UUIDs.push_back(
context.addTriangle(
make_vec3((i + 1) * 0.2f, (j + 1) * 0.2f, 0.0f),
make_vec3((i + 1) * 0.2f, j * 0.2f, 0.0f),
make_vec3(i * 0.2f, (j + 1) * 0.2f, 0.0f)));
583 plantarchitecture.enableSolidObstacleAvoidance(ground_UUIDs, 0.3f);
587 plantarchitecture.advanceTime(plantID, 10);
590 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
591 DOCTEST_CHECK(plant_objects.size() > 0);
596 uint total_objects = 0;
598 for (
uint objID: plant_objects) {
599 if (
context.doesObjectExist(objID)) {
601 vec3 min_corner, max_corner;
602 context.getObjectBoundingBox(objID, min_corner, max_corner);
604 vec3 object_center = (min_corner + max_corner) / 2.0f;
606 center_of_mass = center_of_mass + object_center;
611 if (total_objects > 0) {
612 center_of_mass = center_of_mass / float(total_objects);
616 DOCTEST_CHECK(center_of_mass.
z > -0.075f);
621 DOCTEST_CHECK(center_of_mass.
z > -0.075f);
626 DOCTEST_CHECK(plant_objects.size() >= 5);
629DOCTEST_TEST_CASE(
"PlantArchitecture enableSolidObstacleAvoidance fruit adjustment control") {
632 plantarchitecture.disableMessages();
635 std::vector<uint> obstacle_UUIDs;
640 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f));
643 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f,
true));
646 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.5f,
false));
649 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.3f,
false));
652DOCTEST_TEST_CASE(
"PlantArchitecture base stem protection with short internodes") {
655 plantarchitecture.disableMessages();
658 plantarchitecture.enableSoftCollisionAvoidance();
661 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
664 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
667 plantarchitecture.advanceTime(plantID, 2);
670 std::vector<uint> ground_UUIDs;
671 for (
int i = -1; i <= 1; i++) {
672 for (
int j = -1; j <= 1; j++) {
673 ground_UUIDs.push_back(
context.addTriangle(
make_vec3(i * 0.3f, j * 0.3f, -0.01f),
674 make_vec3((i + 1) * 0.3f, j * 0.3f, -0.01f),
make_vec3(i * 0.3f, (j + 1) * 0.3f, -0.01f)));
675 ground_UUIDs.push_back(
context.addTriangle(
make_vec3((i + 1) * 0.3f, (j + 1) * 0.3f, -0.01f),
make_vec3((i + 1) * 0.3f, j * 0.3f, -0.01f),
make_vec3(i * 0.3f, (j + 1) * 0.3f, -0.01f)));
680 plantarchitecture.enableSolidObstacleAvoidance(ground_UUIDs, 0.2f);
684 plantarchitecture.advanceTime(plantID, 8);
687 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
688 DOCTEST_CHECK(plant_objects.size() > 0);
692 uint total_objects = 0;
694 for (
uint objID: plant_objects) {
695 if (
context.doesObjectExist(objID)) {
696 vec3 min_corner, max_corner;
697 context.getObjectBoundingBox(objID, min_corner, max_corner);
698 vec3 object_center = (min_corner + max_corner) / 2.0f;
699 center_of_mass = center_of_mass + object_center;
704 if (total_objects > 0) {
705 center_of_mass = center_of_mass / float(total_objects);
708 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
712 DOCTEST_CHECK(center_of_mass.
z > 0.05f);
716 DOCTEST_CHECK(plant_objects.size() >= 10);
719DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Basic Functionality") {
722 plantarchitecture.disableMessages();
725 plantarchitecture.enableSoftCollisionAvoidance();
728 std::vector<vec3> attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
731 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points, 60.0f, 0.15f, 0.7f));
734 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(0.0f, 0.1f, 0.5f));
735 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(190.0f, 0.1f, 0.5f));
738 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.0f, 0.5f));
739 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, -0.1f, 0.5f));
742 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.1f, -0.1f));
743 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(80.0f, 0.1f, 1.1f));
746 std::vector<vec3> new_attraction_points = {
make_vec3(2.0f, 0.0f, 2.0f)};
747 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(new_attraction_points));
750 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints());
753 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(new_attraction_points));
756DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Independent of Collision Detection") {
759 plantarchitecture.disableMessages();
761 std::vector<vec3> attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f)};
764 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points));
767DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Empty Vector") {
770 plantarchitecture.disableMessages();
772 std::vector<vec3> empty_attraction_points;
775 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(empty_attraction_points));
778 std::vector<vec3> valid_points = {
make_vec3(1.0f, 0.0f, 1.0f)};
779 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(valid_points));
782 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(empty_attraction_points));
785DOCTEST_TEST_CASE(
"PlantArchitecture Native Attraction Point Cone Detection") {
788 plantarchitecture.disableMessages();
791 std::vector<vec3> attraction_points = {
799 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(attraction_points, 60.0f, 3.0f, 0.7f));
804 vec3 direction_to_closest;
806 bool found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 60.0f, direction_to_closest);
807 DOCTEST_CHECK(found);
811 float dot_product = direction_to_closest * expected_direction;
812 DOCTEST_CHECK(dot_product > 0.99f);
815 look_direction =
make_vec3(1.0f, 0.0f, 0.0f);
816 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 30.0f, direction_to_closest);
822 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, -1.0f, 60.0f, direction_to_closest);
823 DOCTEST_CHECK(!found);
825 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 0.0f, direction_to_closest);
826 DOCTEST_CHECK(!found);
828 found = plantarchitecture.detectAttractionPointsInCone(vertex, look_direction, 3.0f, 180.0f, direction_to_closest);
829 DOCTEST_CHECK(!found);
832DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Plant Growth Integration") {
835 plantarchitecture.disableMessages();
838 plantarchitecture.enableSoftCollisionAvoidance();
841 std::vector<vec3> attraction_points = {
847 plantarchitecture.enableAttractionPoints(attraction_points, 80.0f, 0.2f, 0.6f);
850 plantarchitecture.loadPlantModelFromLibrary(
"bean");
851 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
854 plantarchitecture.advanceTime(plantID, 5);
857 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
858 DOCTEST_CHECK(plant_objects.size() > 0);
862 uint total_objects = 0;
864 for (
uint objID: plant_objects) {
865 if (
context.doesObjectExist(objID)) {
866 vec3 min_corner, max_corner;
867 context.getObjectBoundingBox(objID, min_corner, max_corner);
868 vec3 object_center = (min_corner + max_corner) / 2.0f;
869 center_of_mass = center_of_mass + object_center;
874 if (total_objects > 0) {
875 center_of_mass = center_of_mass / float(total_objects);
879 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
883 float lateral_distance = sqrt(center_of_mass.
x * center_of_mass.
x + center_of_mass.
y * center_of_mass.
y);
884 DOCTEST_CHECK(lateral_distance >= 0.0f);
888 plantarchitecture.disableAttractionPoints();
891 plantarchitecture.advanceTime(plantID, 3);
894 std::vector<uint> final_plant_objects = plantarchitecture.getAllObjectIDs();
895 DOCTEST_CHECK(final_plant_objects.size() >= plant_objects.size());
898DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points Priority Over Collision Avoidance") {
901 plantarchitecture.disableMessages();
904 std::vector<uint> obstacle_UUIDs;
905 for (
int i = 0; i < 3; i++) {
906 for (
int j = 0; j < 3; j++) {
907 obstacle_UUIDs.push_back(
908 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)));
913 plantarchitecture.enableSoftCollisionAvoidance(obstacle_UUIDs);
916 std::vector<vec3> attraction_points = {
921 plantarchitecture.enableAttractionPoints(attraction_points, 90.0f, 0.3f, 0.8f);
924 plantarchitecture.loadPlantModelFromLibrary(
"bean");
925 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0.3f, 0.3f, 0), 0);
928 plantarchitecture.advanceTime(plantID, 4);
931 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
932 DOCTEST_CHECK(plant_objects.size() > 0);
936 uint total_objects = 0;
938 for (
uint objID: plant_objects) {
939 if (
context.doesObjectExist(objID)) {
940 vec3 min_corner, max_corner;
941 context.getObjectBoundingBox(objID, min_corner, max_corner);
942 vec3 object_center = (min_corner + max_corner) / 2.0f;
943 center_of_mass = center_of_mass + object_center;
948 if (total_objects > 0) {
949 center_of_mass = center_of_mass / float(total_objects);
952 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
959DOCTEST_TEST_CASE(
"PlantArchitecture Hard Obstacle Avoidance Takes Priority Over Attraction Points") {
962 plantarchitecture.disableMessages();
965 std::vector<uint> solid_obstacle_UUIDs;
966 for (
int i = -1; i <= 1; i++) {
967 for (
int j = -1; j <= 1; j++) {
968 solid_obstacle_UUIDs.push_back(
context.addTriangle(
make_vec3(i * 0.1f, j * 0.1f, 0.1f),
make_vec3((i + 1) * 0.1f, (j + 1) * 0.1f, 0.1f),
make_vec3((i + 1) * 0.1f, j * 0.1f, 0.1f)));
973 plantarchitecture.enableSoftCollisionAvoidance();
976 plantarchitecture.enableSolidObstacleAvoidance(solid_obstacle_UUIDs, 0.15f);
979 std::vector<vec3> attraction_points = {
984 plantarchitecture.enableAttractionPoints(attraction_points, 70.0f, 0.1f, 0.9f);
987 plantarchitecture.loadPlantModelFromLibrary(
"bean");
988 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
991 plantarchitecture.advanceTime(plantID, 3);
994 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
995 DOCTEST_CHECK(plant_objects.size() > 0);
999 uint total_objects = 0;
1001 for (
uint objID: plant_objects) {
1002 if (
context.doesObjectExist(objID)) {
1003 vec3 min_corner, max_corner;
1004 context.getObjectBoundingBox(objID, min_corner, max_corner);
1005 vec3 object_center = (min_corner + max_corner) / 2.0f;
1006 center_of_mass = center_of_mass + object_center;
1011 if (total_objects > 0) {
1012 center_of_mass = center_of_mass / float(total_objects);
1015 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
1019 DOCTEST_CHECK(center_of_mass.
z > 0.005f);
1023DOCTEST_TEST_CASE(
"PlantArchitecture Attraction Points with Surface Following") {
1026 plantarchitecture.disableMessages();
1029 std::vector<uint> wall_obstacle_UUIDs;
1030 std::vector<vec3> wall_attraction_points;
1033 for (
int i = 0; i < 5; i++) {
1034 for (
int j = 0; j < 3; j++) {
1036 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)));
1039 wall_attraction_points.push_back(
make_vec3(0.29f, i * 0.05f + 0.025f, j * 0.05f + 0.025f));
1044 plantarchitecture.enableSoftCollisionAvoidance();
1047 plantarchitecture.enableSolidObstacleAvoidance(wall_obstacle_UUIDs, 0.05f);
1051 plantarchitecture.enableAttractionPoints(wall_attraction_points, 60.0f, 0.1f, 0.8f);
1052 plantarchitecture.setAttractionParameters(60.0f, 0.1f, 0.8f, 0.5f);
1055 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1056 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1059 plantarchitecture.advanceTime(plantID, 4);
1062 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
1063 DOCTEST_CHECK(plant_objects.size() > 0);
1067 uint total_objects = 0;
1069 for (
uint objID: plant_objects) {
1070 if (
context.doesObjectExist(objID)) {
1071 vec3 min_corner, max_corner;
1072 context.getObjectBoundingBox(objID, min_corner, max_corner);
1073 vec3 object_center = (min_corner + max_corner) / 2.0f;
1074 center_of_mass = center_of_mass + object_center;
1079 if (total_objects > 0) {
1080 center_of_mass = center_of_mass / float(total_objects);
1083 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
1094DOCTEST_TEST_CASE(
"PlantArchitecture Smooth Hard Obstacle Avoidance") {
1097 plantarchitecture.disableMessages();
1099 plantarchitecture.enableSoftCollisionAvoidance();
1100 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1103 std::vector<uint> obstacle_UUIDs;
1107 for (
int i = 0; i < 4; i++) {
1108 float z_height = 0.1f + i * 0.05f;
1111 float x_distance = 0.05f + i * 0.02f;
1113 obstacle_UUIDs.push_back(
context.addTriangle(
make_vec3(x_distance, -0.02f, z_height),
make_vec3(x_distance + 0.04f, -0.02f, z_height),
make_vec3(x_distance, 0.02f, z_height)));
1114 obstacle_UUIDs.push_back(
context.addTriangle(
make_vec3(x_distance + 0.04f, 0.02f, z_height),
make_vec3(x_distance + 0.04f, -0.02f, z_height),
make_vec3(x_distance, 0.02f, z_height)));
1117 plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.25f);
1119 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1120 plantarchitecture.advanceTime(plantID, 8);
1122 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
1123 DOCTEST_CHECK(plant_objects.size() > 0);
1127 uint total_objects = 0;
1129 for (
uint objID: plant_objects) {
1130 if (
context.doesObjectExist(objID)) {
1131 vec3 min_corner, max_corner;
1132 context.getObjectBoundingBox(objID, min_corner, max_corner);
1133 vec3 object_center = (min_corner + max_corner) / 2.0f;
1134 center_of_mass = center_of_mass + object_center;
1139 if (total_objects > 0) {
1140 center_of_mass = center_of_mass / float(total_objects);
1143 DOCTEST_CHECK(center_of_mass.
z > 0.01f);
1147 DOCTEST_CHECK(center_of_mass.
x <= 0.01f);
1155DOCTEST_TEST_CASE(
"PlantArchitecture Hard Obstacle Avoidance Buffer Zone") {
1158 plantarchitecture.disableMessages();
1160 plantarchitecture.enableSoftCollisionAvoidance();
1161 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1164 std::vector<uint> post_UUIDs;
1165 float post_radius = 0.02f;
1166 float post_height = 0.5f;
1170 for (
int i = 0; i < segments; i++) {
1171 float theta1 = 2.0f *
M_PI * float(i) / float(segments);
1172 float theta2 = 2.0f *
M_PI * float(i + 1) / float(segments);
1174 vec3 p1_bottom =
make_vec3(0.1f + post_radius * cos(theta1), post_radius * sin(theta1), 0);
1175 vec3 p2_bottom =
make_vec3(0.1f + post_radius * cos(theta2), post_radius * sin(theta2), 0);
1176 vec3 p1_top =
make_vec3(0.1f + post_radius * cos(theta1), post_radius * sin(theta1), post_height);
1177 vec3 p2_top =
make_vec3(0.1f + post_radius * cos(theta2), post_radius * sin(theta2), post_height);
1180 post_UUIDs.push_back(
context.addTriangle(p1_bottom, p2_bottom, p1_top));
1181 post_UUIDs.push_back(
context.addTriangle(p2_bottom, p2_top, p1_top));
1185 float detection_distance = 0.2f;
1186 float expected_buffer = detection_distance * 0.05f;
1188 plantarchitecture.enableSolidObstacleAvoidance(post_UUIDs, detection_distance);
1191 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1192 plantarchitecture.advanceTime(plantID, 8);
1194 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
1195 DOCTEST_CHECK(plant_objects.size() > 0);
1198 float min_distance_to_post = std::numeric_limits<float>::max();
1201 for (
uint objID: plant_objects) {
1202 if (
context.doesObjectExist(objID)) {
1203 vec3 min_corner, max_corner;
1204 context.getObjectBoundingBox(objID, min_corner, max_corner);
1207 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),
1208 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)};
1210 for (
int i = 0; i < 8; i++) {
1211 float distance = (corners[i] - post_center).magnitude();
1212 min_distance_to_post = std::min(min_distance_to_post, distance);
1218 float expected_min_distance = post_radius + expected_buffer;
1219 DOCTEST_CHECK(min_distance_to_post >= expected_min_distance * 0.8f);
1223 uint plant_object_count = 0;
1225 for (
uint objID: plant_objects) {
1226 if (
context.doesObjectExist(objID)) {
1227 vec3 min_corner, max_corner;
1228 context.getObjectBoundingBox(objID, min_corner, max_corner);
1229 vec3 object_center = (min_corner + max_corner) / 2.0f;
1230 plant_center = plant_center + object_center;
1231 plant_object_count++;
1235 if (plant_object_count > 0) {
1236 plant_center = plant_center / float(plant_object_count);
1237 DOCTEST_CHECK(plant_center.
z > 0.01f);
1241 DOCTEST_CHECK(fabs(plant_center.
x - 0.1f) > expected_buffer * 0.5f);
1245DOCTEST_TEST_CASE(
"PlantArchitecture solid obstacle avoidance works independently") {
1248 plantarchitecture.disableMessages();
1251 std::vector<uint> obstacle_UUIDs;
1257 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSolidObstacleAvoidance(obstacle_UUIDs, 0.2f));
1260 plantarchitecture.loadPlantModelFromLibrary(
"bean");
1261 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1264 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
1267 std::vector<uint> plant_objects = plantarchitecture.getAllObjectIDs();
1268 DOCTEST_CHECK(plant_objects.size() > 0);
1272 uint plant_object_count = 0;
1274 for (
uint objID: plant_objects) {
1275 if (
context.doesObjectExist(objID)) {
1276 vec3 min_corner, max_corner;
1277 context.getObjectBoundingBox(objID, min_corner, max_corner);
1278 vec3 object_center = (min_corner + max_corner) / 2.0f;
1279 plant_center = plant_center + object_center;
1280 plant_object_count++;
1284 if (plant_object_count > 0) {
1285 plant_center = plant_center / float(plant_object_count);
1287 DOCTEST_CHECK(plant_center.
z > 0.01f);
1292 std::vector<uint> soft_target_UUIDs;
1293 std::vector<uint> soft_target_IDs;
1294 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableSoftCollisionAvoidance(soft_target_UUIDs, soft_target_IDs));
1297 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 2.0f));
1300 std::vector<uint> final_plant_objects = plantarchitecture.getAllObjectIDs();
1301 DOCTEST_CHECK(final_plant_objects.size() >= plant_objects.size());
1304DOCTEST_TEST_CASE(
"PlantArchitecture Per-Plant Attraction Points") {
1309 plantarchitecture.disableMessages();
1312 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1313 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1316 std::vector<vec3> attraction_points_1 = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
1317 std::vector<vec3> attraction_points_2 = {
make_vec3(6.0f, 0.0f, 1.0f),
make_vec3(5.0f, 1.0f, 1.5f)};
1320 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, attraction_points_1, 60.0f, 0.2f, 0.7f));
1321 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID2, attraction_points_2, 45.0f, 0.15f, 0.5f));
1324 DOCTEST_CHECK_NOTHROW(plantarchitecture.setAttractionParameters(plantID1, 80.0f, 0.25f, 0.8f, 0.6f));
1325 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(plantID2, {make_vec3(6.5f, 0.5f, 2.0f)}));
1326 DOCTEST_CHECK_NOTHROW(plantarchitecture.appendAttractionPoints(plantID1, {make_vec3(1.5f, 1.5f, 2.0f)}));
1329 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints(plantID1));
1332 DOCTEST_CHECK_THROWS(plantarchitecture.enableAttractionPoints(9999, attraction_points_1));
1333 DOCTEST_CHECK_THROWS(plantarchitecture.disableAttractionPoints(9999));
1334 DOCTEST_CHECK_THROWS(plantarchitecture.updateAttractionPoints(9999, attraction_points_1));
1335 DOCTEST_CHECK_THROWS(plantarchitecture.appendAttractionPoints(9999, attraction_points_1));
1336 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(9999, 60.0f, 0.15f, 0.7f, 0.75f));
1339DOCTEST_TEST_CASE(
"PlantArchitecture Global vs Per-Plant Interaction") {
1344 plantarchitecture.disableMessages();
1347 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1350 std::vector<vec3> global_attraction_points = {
make_vec3(1.0f, 0.0f, 1.0f),
make_vec3(0.0f, 1.0f, 1.5f)};
1351 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(global_attraction_points, 60.0f, 0.15f, 0.7f));
1354 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1357 std::vector<vec3> specific_attraction_points = {
make_vec3(2.0f, 0.0f, 2.0f)};
1358 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, specific_attraction_points, 45.0f, 0.1f, 0.5f));
1361 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints({make_vec3(3.0f, 0.0f, 3.0f)}));
1364 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints());
1367 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(global_attraction_points));
1370DOCTEST_TEST_CASE(
"PlantArchitecture Plant-Specific Attraction Points Validation") {
1375 plantarchitecture.disableMessages();
1378 uint plantID1 = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1379 uint plantID2 = plantarchitecture.addPlantInstance(
make_vec3(5, 0, 0), 0);
1382 std::vector<vec3> attraction_points_1 = {
make_vec3(1.0f, 0.0f, 1.0f)};
1383 std::vector<vec3> attraction_points_2 = {
make_vec3(6.0f, 0.0f, 1.0f)};
1386 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID1, attraction_points_1));
1387 DOCTEST_CHECK_NOTHROW(plantarchitecture.enableAttractionPoints(plantID2, attraction_points_2));
1390 DOCTEST_CHECK_THROWS(plantarchitecture.enableAttractionPoints(plantID1, {}, 60.0f, 0.15f, 0.7f));
1391 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(plantID1, 0.0f, 0.15f, 0.7f));
1392 DOCTEST_CHECK_THROWS(plantarchitecture.setAttractionParameters(plantID1, 60.0f, 0.0f, 0.7f));
1395 DOCTEST_CHECK_NOTHROW(plantarchitecture.setAttractionParameters(plantID1, 80.0f, 0.25f, 0.8f, 0.6f));
1396 DOCTEST_CHECK_NOTHROW(plantarchitecture.updateAttractionPoints(plantID2, {make_vec3(6.5f, 0.5f, 2.0f)}));
1397 DOCTEST_CHECK_NOTHROW(plantarchitecture.appendAttractionPoints(plantID1, {make_vec3(1.5f, 1.5f, 2.0f)}));
1400 DOCTEST_CHECK_NOTHROW(plantarchitecture.disableAttractionPoints(plantID1));
1403DOCTEST_TEST_CASE(
"PlantArchitecture removeShootFloralBuds") {
1406 plantarchitecture.disableMessages();
1410 DOCTEST_CHECK_THROWS(plantarchitecture.removeShootFloralBuds(9999, 0));
1413 uint plantID = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
1414 DOCTEST_CHECK(plantID != -1);
1417 DOCTEST_CHECK_THROWS(plantarchitecture.removeShootFloralBuds(plantID, 9999));
1420DOCTEST_TEST_CASE(
"PlantArchitecture XML write with flowers and fruit") {
1423 plantarchitecture.disableMessages();
1426 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"tomato"));
1429 vec3 base_position(1.0f, 2.0f, 0.5f);
1430 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(base_position, 180);
1431 DOCTEST_CHECK(plantID !=
uint(-1));
1434 std::string xml_filename =
"test_plant_xml_write.xml";
1435 DOCTEST_CHECK_NOTHROW(plantarchitecture.writePlantStructureXML(plantID, xml_filename));
1438 std::remove(xml_filename.c_str());
1441DOCTEST_TEST_CASE(
"PlantArchitecture child shoot rotation with multiple petioles per internode") {
1444 plantarchitecture.disableMessages();
1451 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1452 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1453 DOCTEST_CHECK(plantID !=
uint(-1));
1456 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1459 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1460 DOCTEST_CHECK(all_primitives.size() > 0);
1467DOCTEST_TEST_CASE(
"PlantArchitecture plant_name optional object data") {
1470 plantarchitecture.disableMessages();
1473 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_name"));
1476 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1477 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1478 DOCTEST_CHECK(plantID !=
uint(-1));
1481 std::string plant_name = plantarchitecture.getPlantName(plantID);
1482 DOCTEST_CHECK(plant_name ==
"bean");
1485 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1488 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1489 DOCTEST_CHECK(all_primitives.size() > 0);
1492 bool found_plant_name_data =
false;
1493 for (
uint objID: all_primitives) {
1494 if (
context.doesObjectDataExist(objID,
"plant_name")) {
1495 std::string obj_plant_name;
1496 context.getObjectData(objID,
"plant_name", obj_plant_name);
1497 DOCTEST_CHECK(obj_plant_name ==
"bean");
1498 found_plant_name_data =
true;
1501 DOCTEST_CHECK(found_plant_name_data);
1504DOCTEST_TEST_CASE(
"PlantArchitecture plant_type tree classification") {
1507 plantarchitecture.disableMessages();
1510 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1513 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"almond"));
1514 uint treeID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1515 DOCTEST_CHECK(treeID !=
uint(-1));
1517 std::vector<uint> tree_primitives = plantarchitecture.getAllObjectIDs();
1518 DOCTEST_CHECK(tree_primitives.size() > 0);
1519 bool found_tree_type =
false;
1520 for (
uint objID: tree_primitives) {
1521 if (
context.doesObjectDataExist(objID,
"plant_type")) {
1522 std::string plant_type;
1523 context.getObjectData(objID,
"plant_type", plant_type);
1524 DOCTEST_CHECK(plant_type ==
"tree");
1525 found_tree_type =
true;
1528 DOCTEST_CHECK(found_tree_type);
1531DOCTEST_TEST_CASE(
"PlantArchitecture plant_type weed classification") {
1534 plantarchitecture.disableMessages();
1537 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1540 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bindweed"));
1541 uint weedID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1542 DOCTEST_CHECK(weedID !=
uint(-1));
1544 std::vector<uint> weed_primitives = plantarchitecture.getAllObjectIDs();
1545 DOCTEST_CHECK(weed_primitives.size() > 0);
1546 bool found_weed_type =
false;
1547 for (
uint objID: weed_primitives) {
1548 if (
context.doesObjectDataExist(objID,
"plant_type")) {
1549 std::string plant_type;
1550 context.getObjectData(objID,
"plant_type", plant_type);
1551 DOCTEST_CHECK(plant_type ==
"weed");
1552 found_weed_type =
true;
1555 DOCTEST_CHECK(found_weed_type);
1558DOCTEST_TEST_CASE(
"PlantArchitecture plant_type herbaceous classification") {
1561 plantarchitecture.disableMessages();
1564 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_type"));
1567 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1568 uint herbaceousID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1569 DOCTEST_CHECK(herbaceousID !=
uint(-1));
1571 std::vector<uint> herbaceous_primitives = plantarchitecture.getAllObjectIDs();
1572 DOCTEST_CHECK(herbaceous_primitives.size() > 0);
1573 bool found_herbaceous_type =
false;
1574 for (
uint objID: herbaceous_primitives) {
1575 if (
context.doesObjectDataExist(objID,
"plant_type")) {
1576 std::string plant_type;
1577 context.getObjectData(objID,
"plant_type", plant_type);
1578 DOCTEST_CHECK(plant_type ==
"herbaceous");
1579 found_herbaceous_type =
true;
1582 DOCTEST_CHECK(found_herbaceous_type);
1585DOCTEST_TEST_CASE(
"PlantArchitecture plant_height optional object data") {
1588 plantarchitecture.disableMessages();
1591 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"plant_height"));
1594 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1595 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1596 DOCTEST_CHECK(plantID !=
uint(-1));
1599 float initial_height = plantarchitecture.getPlantHeight(plantID);
1600 DOCTEST_CHECK(initial_height > 0);
1603 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1606 float final_height = plantarchitecture.getPlantHeight(plantID);
1607 DOCTEST_CHECK(final_height > initial_height);
1610 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1611 DOCTEST_CHECK(all_primitives.size() > 0);
1612 bool found_height_data =
false;
1613 for (
uint objID: all_primitives) {
1614 if (
context.doesObjectDataExist(objID,
"plant_height")) {
1616 context.getObjectData(objID,
"plant_height", obj_height);
1618 DOCTEST_CHECK(obj_height > initial_height);
1619 DOCTEST_CHECK(std::abs(obj_height - final_height) < 0.01f);
1620 found_height_data =
true;
1624 DOCTEST_CHECK(found_height_data);
1627DOCTEST_TEST_CASE(
"PlantArchitecture phenology_stage optional object data") {
1630 plantarchitecture.disableMessages();
1633 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"phenology_stage"));
1636 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1637 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1638 DOCTEST_CHECK(plantID !=
uint(-1));
1641 std::string initial_stage = plantarchitecture.determinePhenologyStage(plantID);
1642 DOCTEST_CHECK(initial_stage ==
"vegetative");
1645 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 20.0f));
1648 std::string current_stage = plantarchitecture.determinePhenologyStage(plantID);
1649 DOCTEST_CHECK((current_stage ==
"vegetative" || current_stage ==
"reproductive" || current_stage ==
"senescent" || current_stage ==
"dormant"));
1652 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1653 DOCTEST_CHECK(all_primitives.size() > 0);
1654 bool found_stage_data =
false;
1655 for (
uint objID: all_primitives) {
1656 if (
context.doesObjectDataExist(objID,
"phenology_stage")) {
1657 std::string obj_stage;
1658 context.getObjectData(objID,
"phenology_stage", obj_stage);
1659 DOCTEST_CHECK(obj_stage == current_stage);
1660 found_stage_data =
true;
1663 DOCTEST_CHECK(found_stage_data);
1666DOCTEST_TEST_CASE(
"Build Parameters - Backward Compatibility (Grapevine VSP)") {
1670 plantarchitecture.disableMessages();
1673 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1674 std::map<std::string, float> empty_params;
1675 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, empty_params);
1678 DOCTEST_CHECK(plantID !=
uint(-1));
1681 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1682 DOCTEST_CHECK(plant_primitives.size() > 0);
1685DOCTEST_TEST_CASE(
"Build Parameters - Parameter Override (Grapevine VSP)") {
1689 plantarchitecture.disableMessages();
1693 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1694 std::map<std::string, float> custom_params = {
1695 {
"vine_spacing", 2.5f},
1696 {
"trunk_height", 0.15f}
1698 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, custom_params);
1701 DOCTEST_CHECK(plantID !=
uint(-1));
1702 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1703 DOCTEST_CHECK(plant_primitives.size() > 0);
1706DOCTEST_TEST_CASE(
"Build Parameters - Validation Catches Invalid Values (Grapevine VSP)") {
1711 plantarchitecture.disableMessages();
1713 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1716 std::map<std::string, float> invalid_params1 = {{
"vine_spacing", 10.0f}};
1717 DOCTEST_CHECK_THROWS(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, invalid_params1));
1720 std::map<std::string, float> invalid_params2 = {{
"trunk_height", 2.0f}};
1721 DOCTEST_CHECK_THROWS(plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, invalid_params2));
1724DOCTEST_TEST_CASE(
"Build Parameters - Grapevine Wye Trellis Parameters") {
1728 plantarchitecture.disableMessages();
1730 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_Wye");
1731 std::map<std::string, float> trellis_params = {
1732 {
"trunk_height", 0.2f},
1733 {
"cordon_spacing", 0.8f},
1734 {
"vine_spacing", 2.0f},
1735 {
"catch_wire_height", 2.5f}
1737 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0, trellis_params);
1739 DOCTEST_CHECK(plantID !=
uint(-1));
1740 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1741 DOCTEST_CHECK(plant_primitives.size() > 0);
1744DOCTEST_TEST_CASE(
"Build Parameters - Tree Training System (Almond)") {
1748 plantarchitecture.disableMessages();
1751 plantarchitecture.loadPlantModelFromLibrary(
"almond");
1752 std::map<std::string, float> tree_params = {
1753 {
"trunk_height", 0.5f},
1754 {
"num_scaffolds", 5.0f},
1755 {
"scaffold_angle", 35.0f}
1757 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, tree_params);
1759 DOCTEST_CHECK(plantID !=
uint(-1));
1760 std::vector<uint> plant_primitives = plantarchitecture.getAllPlantObjectIDs(plantID);
1761 DOCTEST_CHECK(plant_primitives.size() > 0);
1764DOCTEST_TEST_CASE(
"Build Parameters - Apple Tree") {
1768 plantarchitecture.disableMessages();
1771 plantarchitecture.loadPlantModelFromLibrary(
"apple");
1772 std::map<std::string, float> apple_params = {
1773 {
"trunk_height", 0.7f},
1774 {
"num_scaffolds", 6.0f},
1775 {
"scaffold_angle", 45.0f}
1777 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, apple_params);
1779 DOCTEST_CHECK(plantID !=
uint(-1));
1782DOCTEST_TEST_CASE(
"Build Parameters - Pistachio Tree Fixed Scaffold System") {
1786 plantarchitecture.disableMessages();
1788 plantarchitecture.loadPlantModelFromLibrary(
"pistachio");
1791 std::map<std::string, float> pistachio_params_min = {{
"num_scaffolds", 2.0f}};
1792 uint plantID_min = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, pistachio_params_min);
1793 DOCTEST_CHECK(plantID_min !=
uint(-1));
1796 std::map<std::string, float> pistachio_params_def = {{
"num_scaffolds", 4.0f}};
1797 uint plantID_def = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(5, 0, 0), 5000, pistachio_params_def);
1798 DOCTEST_CHECK(plantID_def !=
uint(-1));
1801DOCTEST_TEST_CASE(
"Build Parameters - Canopy Building with Parameters") {
1805 plantarchitecture.disableMessages();
1807 plantarchitecture.loadPlantModelFromLibrary(
"grapevine_VSP");
1808 std::map<std::string, float> canopy_params = {
1809 {
"vine_spacing", 2.0f},
1810 {
"trunk_height", 0.12f}
1814 std::vector<uint> plantIDs = plantarchitecture.buildPlantCanopyFromLibrary(
make_vec3(0, 0, 0),
make_vec2(2, 2),
make_int2(2, 2), 0, 1.0f, canopy_params);
1816 DOCTEST_CHECK(plantIDs.size() == 4);
1817 for (
uint plantID: plantIDs) {
1818 DOCTEST_CHECK(plantID !=
uint(-1));
1822DOCTEST_TEST_CASE(
"Build Parameters - Type Casting Float to Uint") {
1826 plantarchitecture.disableMessages();
1828 plantarchitecture.loadPlantModelFromLibrary(
"almond");
1832 std::map<std::string, float> float_params = {
1833 {
"trunk_height", 0.5f},
1834 {
"num_scaffolds", 5.0f},
1835 {
"scaffold_angle", 42.5f}
1838 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000, float_params);
1839 DOCTEST_CHECK(plantID !=
uint(-1));
1842DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData 'all' keyword") {
1845 plantarchitecture.disableMessages();
1848 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"all"));
1851 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1852 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1853 DOCTEST_CHECK(plantID !=
uint(-1));
1856 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1859 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1860 DOCTEST_CHECK(all_primitives.size() > 0);
1865 std::vector<std::string> expected_labels = {
"age",
"rank",
"plantID",
"plant_name",
"plant_height",
"plant_type",
"phenology_stage",
"leafID"};
1867 for (
const auto &label: expected_labels) {
1869 for (
uint objID: all_primitives) {
1870 if (
context.doesObjectDataExist(objID, label.c_str())) {
1875 DOCTEST_CHECK_MESSAGE(found,
"Label '" << label <<
"' was not found on any primitive");
1879DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData 'all' case-insensitive") {
1884 plantarchitecture.disableMessages();
1885 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"ALL"));
1892 plantarchitecture.disableMessages();
1893 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"All"));
1900 plantarchitecture.disableMessages();
1901 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"aLl"));
1905DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData invalid label throws error") {
1908 plantarchitecture.disableMessages();
1911 bool caught_error =
false;
1913 plantarchitecture.optionalOutputObjectData(
"invalid_label");
1914 }
catch (
const std::exception &e) {
1915 caught_error =
true;
1916 std::string error_msg(e.what());
1917 DOCTEST_CHECK(error_msg.find(
"invalid_label") != std::string::npos);
1918 DOCTEST_CHECK(error_msg.find(
"not a valid option") != std::string::npos);
1920 DOCTEST_CHECK(caught_error);
1926DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData vector with 'all'") {
1929 plantarchitecture.disableMessages();
1932 std::vector<std::string> labels = {
"all"};
1933 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(labels));
1936 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1937 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1938 DOCTEST_CHECK(plantID !=
uint(-1));
1941 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 10.0f));
1944 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1945 DOCTEST_CHECK(all_primitives.size() > 0);
1948 bool found_age =
false;
1949 bool found_rank =
false;
1950 bool found_plant_name =
false;
1951 for (
uint objID: all_primitives) {
1952 if (
context.doesObjectDataExist(objID,
"age"))
1954 if (
context.doesObjectDataExist(objID,
"rank"))
1956 if (
context.doesObjectDataExist(objID,
"plant_name"))
1957 found_plant_name =
true;
1959 DOCTEST_CHECK(found_age);
1960 DOCTEST_CHECK(found_rank);
1961 DOCTEST_CHECK(found_plant_name);
1964DOCTEST_TEST_CASE(
"PlantArchitecture optionalOutputObjectData normal labels still work") {
1967 plantarchitecture.disableMessages();
1970 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"age"));
1971 DOCTEST_CHECK_NOTHROW(plantarchitecture.optionalOutputObjectData(
"rank"));
1974 DOCTEST_CHECK_NOTHROW(plantarchitecture.loadPlantModelFromLibrary(
"bean"));
1975 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
1976 DOCTEST_CHECK(plantID !=
uint(-1));
1979 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
1982 std::vector<uint> all_primitives = plantarchitecture.getAllObjectIDs();
1983 DOCTEST_CHECK(all_primitives.size() > 0);
1985 bool found_age =
false;
1986 bool found_rank =
false;
1987 bool found_plant_name =
false;
1988 for (
uint objID: all_primitives) {
1989 if (
context.doesObjectDataExist(objID,
"age"))
1991 if (
context.doesObjectDataExist(objID,
"rank"))
1993 if (
context.doesObjectDataExist(objID,
"plant_name"))
1994 found_plant_name =
true;
1996 DOCTEST_CHECK(found_age);
1997 DOCTEST_CHECK(found_rank);
1998 DOCTEST_CHECK_FALSE(found_plant_name);
2003DOCTEST_TEST_CASE(
"Nitrogen Model - Initialization") {
2006 plantarchitecture.disableMessages();
2009 plantarchitecture.enableNitrogenModel();
2010 DOCTEST_CHECK(plantarchitecture.isNitrogenModelEnabled());
2013 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2014 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2017 plantarchitecture.advanceTime(plantID, 5.0f);
2020 float initial_N_concentration = 1.5f;
2021 plantarchitecture.initializePlantNitrogenPools(plantID, initial_N_concentration);
2024 plantarchitecture.advanceTime(plantID, 0.1f);
2027 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2028 DOCTEST_CHECK(all_objects.size() > 0);
2031 bool found_leaf_N =
false;
2032 for (
uint objID: all_objects) {
2033 if (
context.doesObjectDataExist(objID,
"leaf_nitrogen_gN_m2")) {
2035 context.getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2036 DOCTEST_CHECK(leaf_N_area == doctest::Approx(initial_N_concentration).epsilon(0.1));
2037 found_leaf_N =
true;
2040 DOCTEST_CHECK(found_leaf_N);
2043DOCTEST_TEST_CASE(
"Nitrogen Model - Application and Pool Splitting") {
2046 plantarchitecture.disableMessages();
2048 plantarchitecture.enableNitrogenModel();
2049 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2050 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2051 plantarchitecture.advanceTime(plantID, 3.0f);
2054 plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f);
2057 float N_applied = 10.0f;
2058 plantarchitecture.addPlantNitrogen(plantID, N_applied);
2063 plantarchitecture.advanceTime(plantID, 1.0f);
2066 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2067 bool found_N_accumulation =
false;
2068 for (
uint objID: all_objects) {
2069 if (
context.doesObjectDataExist(objID,
"leaf_nitrogen_gN_m2")) {
2071 context.getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2072 if (leaf_N_area > 0) {
2073 found_N_accumulation =
true;
2078 DOCTEST_CHECK(found_N_accumulation);
2081DOCTEST_TEST_CASE(
"Nitrogen Model - Rate Limiting") {
2084 plantarchitecture.disableMessages();
2086 plantarchitecture.enableNitrogenModel();
2087 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2088 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2089 plantarchitecture.advanceTime(plantID, 5.0f);
2092 plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f);
2098 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
2101 plantarchitecture.addPlantNitrogen(plantID, 100.0f);
2105 plantarchitecture.advanceTime(plantID, dt);
2108 std::vector<uint> all_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2109 for (
uint objID: all_objects) {
2110 if (
context.doesObjectDataExist(objID,
"leaf_nitrogen_gN_m2")) {
2112 context.getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2119DOCTEST_TEST_CASE(
"Nitrogen Model - Stress Factor Output") {
2122 plantarchitecture.disableMessages();
2124 plantarchitecture.enableNitrogenModel();
2125 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2126 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2127 plantarchitecture.advanceTime(plantID, 5.0f);
2130 plantarchitecture.initializePlantNitrogenPools(plantID, 0.5f);
2133 plantarchitecture.advanceTime(plantID, 0.1f);
2136 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2137 DOCTEST_CHECK(plant_objects.size() > 0);
2139 bool found_stress_factor =
false;
2140 for (
uint objID: plant_objects) {
2141 if (
context.doesObjectDataExist(objID,
"nitrogen_stress_factor")) {
2142 float stress_factor;
2143 context.getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2144 DOCTEST_CHECK(stress_factor >= 0.0f);
2145 DOCTEST_CHECK(stress_factor <= 1.0f);
2147 DOCTEST_CHECK(stress_factor < 1.0f);
2148 found_stress_factor =
true;
2152 DOCTEST_CHECK(found_stress_factor);
2155DOCTEST_TEST_CASE(
"Nitrogen Model - Remobilization") {
2158 plantarchitecture.disableMessages();
2160 plantarchitecture.enableNitrogenModel();
2161 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2162 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2165 plantarchitecture.advanceTime(plantID, 15.0f);
2168 plantarchitecture.initializePlantNitrogenPools(plantID, 0.8f);
2171 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 25.0f));
2174 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2175 bool found_stress_factor =
false;
2176 for (
uint objID: plant_objects) {
2177 if (
context.doesObjectDataExist(objID,
"nitrogen_stress_factor")) {
2178 float stress_factor;
2179 context.getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2180 DOCTEST_CHECK(stress_factor < 1.0f);
2181 found_stress_factor =
true;
2185 DOCTEST_CHECK(found_stress_factor);
2188DOCTEST_TEST_CASE(
"Nitrogen Model - Fruit Removal") {
2191 plantarchitecture.disableMessages();
2193 plantarchitecture.enableNitrogenModel();
2196 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2197 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2200 plantarchitecture.advanceTime(plantID, 30.0f);
2203 plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f);
2206 plantarchitecture.addPlantNitrogen(plantID, 50.0f);
2209 plantarchitecture.advanceTime(plantID, 40.0f);
2212 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2213 DOCTEST_CHECK(plant_objects.size() > 0);
2216 bool found_stress_factor =
false;
2217 for (
uint objID: plant_objects) {
2218 if (
context.doesObjectDataExist(objID,
"nitrogen_stress_factor")) {
2219 found_stress_factor =
true;
2223 DOCTEST_CHECK(found_stress_factor);
2226DOCTEST_TEST_CASE(
"Nitrogen Model - Leaf-to-Fruit Translocation") {
2236 plantarchitecture.disableMessages();
2238 plantarchitecture.enableNitrogenModel();
2239 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2240 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2245 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
2249 plantarchitecture.advanceTime(plantID, 60.0f);
2254 std::vector<uint> fruit_objIDs = plantarchitecture.getPlantFruitObjectIDs(plantID);
2255 if (fruit_objIDs.empty()) {
2257 plantarchitecture.advanceTime(plantID, 30.0f);
2258 fruit_objIDs = plantarchitecture.getPlantFruitObjectIDs(plantID);
2260 if (fruit_objIDs.empty()) {
2267 plantarchitecture.initializePlantNitrogenPools(plantID, N_params.
target_leaf_N_area);
2270 plantarchitecture.advanceTime(plantID, 0.1f);
2273 bool any_leaf_at_target_pre =
false;
2274 for (
uint objID: plantarchitecture.getAllPlantObjectIDs(plantID)) {
2275 if (
context.doesObjectDataExist(objID,
"leaf_nitrogen_gN_m2")) {
2277 context.getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2279 any_leaf_at_target_pre =
true;
2284 DOCTEST_CHECK(any_leaf_at_target_pre);
2288 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 30.0f));
2293 bool any_leaf_drained_below_target =
false;
2294 float min_leaf_N_observed = std::numeric_limits<float>::infinity();
2295 bool any_leaf_with_N =
false;
2296 for (
uint objID: plantarchitecture.getAllPlantObjectIDs(plantID)) {
2297 if (
context.doesObjectDataExist(objID,
"leaf_nitrogen_gN_m2")) {
2299 context.getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2301 any_leaf_drained_below_target =
true;
2303 if (leaf_N_area > 1e-4f) {
2304 min_leaf_N_observed = std::min(min_leaf_N_observed, leaf_N_area);
2305 any_leaf_with_N =
true;
2312 fruit_objIDs = plantarchitecture.getPlantFruitObjectIDs(plantID);
2315 if (!fruit_objIDs.empty()) {
2316 DOCTEST_CHECK(any_leaf_drained_below_target);
2322 if (any_leaf_with_N) {
2327 bool found_stress_factor =
false;
2328 for (
uint objID: plantarchitecture.getAllPlantObjectIDs(plantID)) {
2329 if (
context.doesObjectDataExist(objID,
"nitrogen_stress_factor")) {
2330 found_stress_factor =
true;
2334 DOCTEST_CHECK(found_stress_factor);
2337DOCTEST_TEST_CASE(
"Nitrogen Model - No Translocation When Pool Adequate") {
2346 plantarchitecture.disableMessages();
2348 plantarchitecture.enableNitrogenModel();
2349 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2350 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2354 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
2356 plantarchitecture.advanceTime(plantID, 30.0f);
2357 plantarchitecture.initializePlantNitrogenPools(plantID, N_params.
target_leaf_N_area);
2358 plantarchitecture.addPlantNitrogen(plantID, 200.0f);
2359 plantarchitecture.advanceTime(plantID, 0.1f);
2362 std::vector<uint> leaves_at_target_pre;
2363 for (
uint objID: plantarchitecture.getAllPlantObjectIDs(plantID)) {
2364 if (
context.doesObjectDataExist(objID,
"leaf_nitrogen_gN_m2")) {
2366 context.getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2368 leaves_at_target_pre.push_back(objID);
2372 DOCTEST_CHECK(leaves_at_target_pre.size() > 0);
2374 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 40.0f));
2378 int leaves_intact = 0;
2379 for (
uint objID: leaves_at_target_pre) {
2380 if (!
context.doesObjectExist(objID)) {
2383 if (!
context.doesObjectDataExist(objID,
"leaf_nitrogen_gN_m2")) {
2387 context.getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
2392 DOCTEST_CHECK(leaves_intact > 0);
2395DOCTEST_TEST_CASE(
"Nitrogen Model - Full Growth Cycle Integration") {
2398 plantarchitecture.disableMessages();
2400 plantarchitecture.enableNitrogenModel();
2401 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2402 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2405 plantarchitecture.advanceTime(plantID, 5.0f);
2408 plantarchitecture.initializePlantNitrogenPools(plantID, 1.0f);
2411 for (
int i = 0; i < 5; i++) {
2412 plantarchitecture.addPlantNitrogen(plantID, 5.0f);
2413 plantarchitecture.advanceTime(plantID, 5.0f);
2417 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2418 DOCTEST_CHECK(plant_objects.size() > 0);
2421 bool found_stress_factor =
false;
2422 float final_stress = 0;
2423 for (
uint objID: plant_objects) {
2424 if (
context.doesObjectDataExist(objID,
"nitrogen_stress_factor")) {
2425 context.getObjectData(objID,
"nitrogen_stress_factor", final_stress);
2426 found_stress_factor =
true;
2430 DOCTEST_CHECK(found_stress_factor);
2431 DOCTEST_CHECK(final_stress >= 0.0f);
2432 DOCTEST_CHECK(final_stress <= 1.0f);
2435 bool found_leaf_N =
false;
2436 for (
uint objID: plant_objects) {
2437 if (
context.doesObjectDataExist(objID,
"leaf_nitrogen_gN_m2")) {
2439 context.getObjectData(objID,
"leaf_nitrogen_gN_m2", leaf_N);
2440 DOCTEST_CHECK(leaf_N >= 0.0f);
2441 found_leaf_N =
true;
2444 DOCTEST_CHECK(found_leaf_N);
2447DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: Zero Nitrogen") {
2450 plantarchitecture.disableMessages();
2452 plantarchitecture.enableNitrogenModel();
2453 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2454 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2455 plantarchitecture.advanceTime(plantID, 5.0f);
2458 DOCTEST_CHECK_NOTHROW(plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f));
2461 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2464 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2465 bool found_stress_factor =
false;
2466 for (
uint objID: plant_objects) {
2467 if (
context.doesObjectDataExist(objID,
"nitrogen_stress_factor")) {
2468 float stress_factor;
2469 context.getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2470 DOCTEST_CHECK(stress_factor < 0.2f);
2471 found_stress_factor =
true;
2475 DOCTEST_CHECK(found_stress_factor);
2478DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: Excessive Nitrogen") {
2481 plantarchitecture.disableMessages();
2483 plantarchitecture.enableNitrogenModel();
2484 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2485 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2486 plantarchitecture.advanceTime(plantID, 5.0f);
2489 plantarchitecture.initializePlantNitrogenPools(plantID, 0.0f);
2494 plantarchitecture.setPlantNitrogenParameters(plantID, N_params);
2497 DOCTEST_CHECK_NOTHROW(plantarchitecture.addPlantNitrogen(plantID, 1000.0f));
2500 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2503 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2504 bool found_stress_factor =
false;
2505 for (
uint objID: plant_objects) {
2506 if (
context.doesObjectDataExist(objID,
"nitrogen_stress_factor")) {
2507 float stress_factor;
2508 context.getObjectData(objID,
"nitrogen_stress_factor", stress_factor);
2509 DOCTEST_CHECK(stress_factor <= 1.0f);
2510 DOCTEST_CHECK(stress_factor >= 0.90f);
2511 found_stress_factor =
true;
2515 DOCTEST_CHECK(found_stress_factor);
2518DOCTEST_TEST_CASE(
"Nitrogen Model - Edge Case: No Leaves") {
2521 plantarchitecture.disableMessages();
2523 plantarchitecture.enableNitrogenModel();
2526 uint plantID = plantarchitecture.addPlantInstance(
make_vec3(0, 0, 0), 0);
2529 DOCTEST_CHECK_NOTHROW(plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f));
2532 DOCTEST_CHECK_NOTHROW(plantarchitecture.addPlantNitrogen(plantID, 10.0f));
2535 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 1.0f));
2538DOCTEST_TEST_CASE(
"Nitrogen Model - Division by Zero Prevention") {
2541 plantarchitecture.disableMessages();
2543 plantarchitecture.enableNitrogenModel();
2544 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2545 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2548 plantarchitecture.advanceTime(plantID, 0.5f);
2551 plantarchitecture.initializePlantNitrogenPools(plantID, 1.5f);
2554 plantarchitecture.addPlantNitrogen(plantID, 10.0f);
2557 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 1.0f));
2560 plantarchitecture.advanceTime(plantID, 20.0f);
2561 DOCTEST_CHECK_NOTHROW(plantarchitecture.advanceTime(plantID, 5.0f));
2564DOCTEST_TEST_CASE(
"Nitrogen Model - Enable/Disable") {
2567 plantarchitecture.disableMessages();
2570 DOCTEST_CHECK_FALSE(plantarchitecture.isNitrogenModelEnabled());
2573 plantarchitecture.enableNitrogenModel();
2574 DOCTEST_CHECK(plantarchitecture.isNitrogenModelEnabled());
2577 plantarchitecture.disableNitrogenModel();
2578 DOCTEST_CHECK_FALSE(plantarchitecture.isNitrogenModelEnabled());
2581 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2582 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2583 plantarchitecture.advanceTime(plantID, 5.0f);
2585 std::vector<uint> plant_objects = plantarchitecture.getAllPlantObjectIDs(plantID);
2586 bool found_nitrogen_data =
false;
2587 for (
uint objID: plant_objects) {
2588 if (
context.doesObjectDataExist(objID,
"nitrogen_stress_factor")) {
2589 found_nitrogen_data =
true;
2593 DOCTEST_CHECK_FALSE(found_nitrogen_data);
2598DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - no parameter success") {
2602 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2603 std::vector<std::string> labels = plantarchitecture.listShootTypeLabels();
2605 DOCTEST_CHECK(labels.size() == 2);
2606 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"unifoliate") != labels.end());
2607 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"trifoliate") != labels.end());
2610DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - no parameter error") {
2611 std::string error_message;
2618 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels()));
2622DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - string parameter success") {
2627 std::vector<std::string> bean_labels = plantarchitecture.listShootTypeLabels(
"bean");
2628 DOCTEST_CHECK(bean_labels.size() == 2);
2629 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"unifoliate") != bean_labels.end());
2630 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"trifoliate") != bean_labels.end());
2633 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(
"tomato");
2634 DOCTEST_CHECK(tomato_labels.size() == 1);
2635 DOCTEST_CHECK(std::find(tomato_labels.begin(), tomato_labels.end(),
"mainstem") != tomato_labels.end());
2638DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - string parameter error") {
2639 std::string error_message;
2646 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels(
"nonexistent_plant")));
2650DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - state preservation") {
2655 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2658 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(
"tomato");
2661 std::vector<std::string> current_labels = plantarchitecture.listShootTypeLabels();
2662 DOCTEST_CHECK(current_labels.size() == 2);
2663 DOCTEST_CHECK(std::find(current_labels.begin(), current_labels.end(),
"unifoliate") != current_labels.end());
2664 DOCTEST_CHECK(std::find(current_labels.begin(), current_labels.end(),
"trifoliate") != current_labels.end());
2667DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - all plant models") {
2671 std::vector<std::string> all_plants = plantarchitecture.getAvailablePlantModels();
2674 for (
const auto &plant: all_plants) {
2675 std::vector<std::string> labels;
2676 DOCTEST_CHECK_NOTHROW(labels = plantarchitecture.listShootTypeLabels(plant));
2677 DOCTEST_CHECK(!labels.empty());
2681DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - uint parameter success") {
2686 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2687 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2690 std::vector<std::string> labels = plantarchitecture.listShootTypeLabels(plantID);
2693 DOCTEST_CHECK(labels.size() == 2);
2694 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"unifoliate") != labels.end());
2695 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"trifoliate") != labels.end());
2698DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - uint parameter error") {
2699 std::string error_message;
2706 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.listShootTypeLabels(999)));
2710DOCTEST_TEST_CASE(
"PlantArchitecture listShootTypeLabels - multiple instances") {
2715 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2716 uint bean_plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0);
2719 plantarchitecture.loadPlantModelFromLibrary(
"tomato");
2720 uint tomato_plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(1, 0, 0), 0);
2723 std::vector<std::string> bean_labels = plantarchitecture.listShootTypeLabels(bean_plantID);
2724 DOCTEST_CHECK(bean_labels.size() == 2);
2725 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"unifoliate") != bean_labels.end());
2726 DOCTEST_CHECK(std::find(bean_labels.begin(), bean_labels.end(),
"trifoliate") != bean_labels.end());
2728 std::vector<std::string> tomato_labels = plantarchitecture.listShootTypeLabels(tomato_plantID);
2729 DOCTEST_CHECK(tomato_labels.size() == 1);
2730 DOCTEST_CHECK(std::find(tomato_labels.begin(), tomato_labels.end(),
"mainstem") != tomato_labels.end());
2733DOCTEST_TEST_CASE(
"PlantArchitecture getPlantInternodeObjectIDs with shoot type filter") {
2738 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2739 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0.0);
2742 std::vector<uint> all_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID);
2743 DOCTEST_CHECK(all_internodes.size() > 0);
2746 std::vector<uint> unifoliate_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID,
"unifoliate");
2747 DOCTEST_CHECK(unifoliate_internodes.size() > 0);
2750 std::vector<uint> trifoliate_internodes = plantarchitecture.getPlantInternodeObjectIDs(plantID,
"trifoliate");
2751 DOCTEST_CHECK(trifoliate_internodes.size() > 0);
2754 for (
uint objID : unifoliate_internodes) {
2755 DOCTEST_CHECK(std::find(all_internodes.begin(), all_internodes.end(), objID) != all_internodes.end());
2757 for (
uint objID : trifoliate_internodes) {
2758 DOCTEST_CHECK(std::find(all_internodes.begin(), all_internodes.end(), objID) != all_internodes.end());
2762 for (
uint objID : unifoliate_internodes) {
2763 DOCTEST_CHECK(std::find(trifoliate_internodes.begin(), trifoliate_internodes.end(), objID) == trifoliate_internodes.end());
2767 DOCTEST_CHECK(unifoliate_internodes.size() + trifoliate_internodes.size() == all_internodes.size());
2770DOCTEST_TEST_CASE(
"PlantArchitecture getPlantInternodeObjectIDs with shoot type filter - error cases") {
2771 std::string error_message;
2777 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2778 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 0.0);
2781 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.getPlantInternodeObjectIDs(plantID,
"nonexistent_shoot_type")));
2784 DOCTEST_CHECK_THROWS(
static_cast<void>(plantarchitecture.getPlantInternodeObjectIDs(9999,
"unifoliate")));
2788DOCTEST_TEST_CASE(
"PlantArchitecture setProgressCallback") {
2789 std::vector<float> progress_values;
2790 std::vector<std::string> messages;
2797 plantarchitecture.disableMessages();
2799 plantarchitecture.setProgressCallback([&](
float progress,
const std::string &msg) {
2800 progress_values.push_back(progress);
2801 messages.push_back(msg);
2804 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2805 plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5);
2808 plantarchitecture.advanceTime(1.f);
2812 DOCTEST_CHECK(progress_values.size() > 0);
2815 for (
float p : progress_values) {
2816 DOCTEST_CHECK(p >= 0.f);
2817 DOCTEST_CHECK(p <= 1.f);
2821 if (!progress_values.empty()) {
2822 DOCTEST_CHECK(progress_values.back() == doctest::Approx(1.0f));
2826 for (
const auto &msg : messages) {
2827 DOCTEST_CHECK(!msg.empty());
2831DOCTEST_TEST_CASE(
"getAllPlantUUIDs with include_hidden parameter") {
2834 plantarchitecture.disableMessages();
2835 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2836 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000);
2838 std::vector<uint> uuids_default = plantarchitecture.getAllPlantUUIDs(plantID);
2839 std::vector<uint> uuids_no_hidden = plantarchitecture.getAllPlantUUIDs(plantID,
false);
2840 std::vector<uint> uuids_with_hidden = plantarchitecture.getAllPlantUUIDs(plantID,
true);
2843 DOCTEST_CHECK(uuids_default.size() == uuids_no_hidden.size());
2846 DOCTEST_CHECK(uuids_with_hidden.size() > uuids_no_hidden.size());
2849DOCTEST_TEST_CASE(
"deletePlantInstance cleans up prototypes when all plants deleted") {
2852 plantarchitecture.disableMessages();
2853 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2855 uint plantID1 = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000);
2856 uint plantID2 = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(1, 0, 0), 5000);
2859 std::vector<uint> all_uuids = plantarchitecture.getAllPlantUUIDs(plantID1,
true);
2860 std::vector<uint> visible_uuids = plantarchitecture.getAllPlantUUIDs(plantID1,
false);
2861 DOCTEST_CHECK(all_uuids.size() > visible_uuids.size());
2864 std::set<uint> visible_set(visible_uuids.begin(), visible_uuids.end());
2865 std::vector<uint> prototype_uuids;
2866 for (
uint uuid : all_uuids) {
2867 if (visible_set.find(uuid) == visible_set.end()) {
2868 prototype_uuids.push_back(uuid);
2871 DOCTEST_CHECK(prototype_uuids.size() > 0);
2874 plantarchitecture.deletePlantInstance(plantID1);
2875 for (
uint uuid : prototype_uuids) {
2876 DOCTEST_CHECK(
context.doesPrimitiveExist(uuid));
2880 plantarchitecture.deletePlantInstance(plantID2);
2881 for (
uint uuid : prototype_uuids) {
2882 DOCTEST_CHECK(!
context.doesPrimitiveExist(uuid));
2886DOCTEST_TEST_CASE(
"deletePlantInstance preserves prototypes when plants remain") {
2889 plantarchitecture.disableMessages();
2890 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2892 uint plantID1 = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 5000);
2893 uint plantID2 = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(1, 0, 0), 5000);
2896 std::vector<uint> uuids_with_hidden = plantarchitecture.getAllPlantUUIDs(plantID2,
true);
2897 std::vector<uint> uuids_without_hidden = plantarchitecture.getAllPlantUUIDs(plantID2,
false);
2898 DOCTEST_CHECK(uuids_with_hidden.size() > uuids_without_hidden.size());
2901 plantarchitecture.deletePlantInstance(plantID1);
2903 std::vector<uint> uuids_after = plantarchitecture.getAllPlantUUIDs(plantID2,
true);
2904 DOCTEST_CHECK(uuids_after.size() > plantarchitecture.getAllPlantUUIDs(plantID2,
false).size());
2907DOCTEST_TEST_CASE(
"USD export basic structure") {
2910 plantarchitecture.disableMessages();
2911 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2913 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2915 std::string filename =
"test_usd_basic.usda";
2916 plantarchitecture.writePlantStructureUSD(plantID, filename);
2919 std::ifstream file(filename);
2920 DOCTEST_CHECK(file.is_open());
2922 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
2924 DOCTEST_CHECK(!content.empty());
2927 DOCTEST_CHECK(content.find(
"PhysicsArticulationRootAPI") != std::string::npos);
2928 DOCTEST_CHECK(content.find(
"PhysxArticulationAPI") != std::string::npos);
2929 DOCTEST_CHECK(content.find(
"PhysicsScene") != std::string::npos);
2930 DOCTEST_CHECK(content.find(
"PhysicsMaterialAPI") != std::string::npos);
2931 DOCTEST_CHECK(content.find(
"PhysicsFixedJoint") != std::string::npos);
2932 DOCTEST_CHECK(content.find(
"PhysicsRigidBodyAPI") != std::string::npos);
2933 DOCTEST_CHECK(content.find(
"PhysicsSphericalJoint") != std::string::npos);
2934 DOCTEST_CHECK(content.find(
"PhysicsDriveAPI:angular") != std::string::npos);
2937 size_t link_count = 0;
2939 while ((pos = content.find(
"PhysicsRigidBodyAPI", pos)) != std::string::npos) {
2943 DOCTEST_CHECK(link_count > 0);
2946 size_t fixed_count = 0;
2948 while ((pos = content.find(
"PhysicsFixedJoint", pos)) != std::string::npos) {
2952 DOCTEST_CHECK(fixed_count == 1);
2954 std::remove(filename.c_str());
2957DOCTEST_TEST_CASE(
"USD export physics properties") {
2960 plantarchitecture.disableMessages();
2961 plantarchitecture.loadPlantModelFromLibrary(
"bean");
2963 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2966 params.elastic_modulus = 1e9f;
2967 params.wood_density = 500.f;
2969 std::string filename =
"test_usd_physics.usda";
2970 plantarchitecture.writePlantStructureUSD(plantID, filename, params);
2973 std::ifstream file(filename);
2974 DOCTEST_CHECK(file.is_open());
2976 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
2980 DOCTEST_CHECK(content.find(
"physics:mass") != std::string::npos);
2981 DOCTEST_CHECK(content.find(
"drive:angular:physics:stiffness") != std::string::npos);
2982 DOCTEST_CHECK(content.find(
"drive:angular:physics:damping") != std::string::npos);
2985 DOCTEST_CHECK(content.find(
"physics:gravityMagnitude = 9.81") != std::string::npos);
2987 std::remove(filename.c_str());
2990DOCTEST_TEST_CASE(
"USD export branching topology") {
2993 plantarchitecture.disableMessages();
2996 plantarchitecture.loadPlantModelFromLibrary(
"almond");
2997 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
2999 std::string filename =
"test_usd_branching.usda";
3000 plantarchitecture.writePlantStructureUSD(plantID, filename);
3002 std::ifstream file(filename);
3003 DOCTEST_CHECK(file.is_open());
3005 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
3009 size_t link_count = 0;
3011 while ((pos = content.find(
"PhysicsRigidBodyAPI", pos)) != std::string::npos) {
3015 DOCTEST_CHECK(link_count > 3);
3018 DOCTEST_CHECK(content.find(
"physics:body0") != std::string::npos);
3019 DOCTEST_CHECK(content.find(
"physics:body1") != std::string::npos);
3021 std::remove(filename.c_str());
3024DOCTEST_TEST_CASE(
"USD export error handling") {
3027 plantarchitecture.disableMessages();
3030 DOCTEST_CHECK_THROWS(plantarchitecture.writePlantStructureUSD(9999,
"test.usda"));
3033 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3034 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3037 DOCTEST_CHECK_THROWS(plantarchitecture.writePlantStructureUSD(plantID,
"test.txt"));
3040DOCTEST_TEST_CASE(
"USD export organs") {
3043 plantarchitecture.disableMessages();
3044 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3046 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3049 std::string filename =
"test_usd_organs.usda";
3050 plantarchitecture.writePlantStructureUSD(plantID, filename, params);
3052 std::ifstream file(filename);
3053 DOCTEST_CHECK(file.is_open());
3055 std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
3059 DOCTEST_CHECK(content.find(
"Pet") != std::string::npos);
3062 DOCTEST_CHECK(content.find(
"Leaf") != std::string::npos);
3063 DOCTEST_CHECK(content.find(
"def Mesh \"Visual\"") != std::string::npos);
3064 DOCTEST_CHECK(content.find(
"def Mesh \"Collision\"") != std::string::npos);
3067 DOCTEST_CHECK(content.find(
"\"MaterialBindingAPI\"") != std::string::npos);
3070 DOCTEST_CHECK(content.find(
"bool doubleSided = 1") != std::string::npos);
3071 DOCTEST_CHECK(content.find(
"subdivisionScheme = \"none\"") != std::string::npos);
3074 DOCTEST_CHECK(content.find(
"primvars:normals") != std::string::npos);
3077 DOCTEST_CHECK(content.find(
"asset inputs:file = @/") == std::string::npos);
3079 std::remove(filename.c_str());
3082DOCTEST_TEST_CASE(
"USD export minimum segment filtering") {
3085 plantarchitecture.disableMessages();
3088 plantarchitecture.loadPlantModelFromLibrary(
"almond");
3090 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3094 std::string filename_default =
"test_usd_filter_default.usda";
3095 plantarchitecture.writePlantStructureUSD(plantID, filename_default, params_default);
3100 std::string filename_strict =
"test_usd_filter_strict.usda";
3101 plantarchitecture.writePlantStructureUSD(plantID, filename_strict, params_strict);
3104 auto countOccurrences = [](
const std::string &content,
const std::string &token) {
3107 while ((pos = content.find(token, pos)) != std::string::npos) {
3114 std::ifstream f1(filename_default);
3115 std::string content1((std::istreambuf_iterator<char>(f1)), std::istreambuf_iterator<char>());
3118 std::ifstream f2(filename_strict);
3119 std::string content2((std::istreambuf_iterator<char>(f2)), std::istreambuf_iterator<char>());
3122 size_t links_default = countOccurrences(content1,
"PhysicsRigidBodyAPI");
3123 size_t links_strict = countOccurrences(content2,
"PhysicsRigidBodyAPI");
3126 DOCTEST_CHECK(links_strict <= links_default);
3128 std::remove(filename_default.c_str());
3129 std::remove(filename_strict.c_str());
3132DOCTEST_TEST_CASE(
"Growth frame registration") {
3135 plantarchitecture.disableMessages();
3136 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3138 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3140 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 0);
3143 plantarchitecture.registerGrowthFrame(plantID);
3144 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 1);
3147 plantarchitecture.advanceTime(10);
3148 plantarchitecture.registerGrowthFrame(plantID);
3149 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 2);
3151 plantarchitecture.advanceTime(10);
3152 plantarchitecture.registerGrowthFrame(plantID);
3153 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 3);
3156 plantarchitecture.clearGrowthFrames(plantID);
3157 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(plantID) == 0);
3160 DOCTEST_CHECK(plantarchitecture.getGrowthFrameCount(9999) == 0);
3163DOCTEST_TEST_CASE(
"Growth USD export basic") {
3166 plantarchitecture.disableMessages();
3167 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3169 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3171 for (
int i = 0; i < 3; i++) {
3172 plantarchitecture.advanceTime(10);
3173 plantarchitecture.registerGrowthFrame(plantID);
3176 std::string filename =
"test_growth_usd.usda";
3178 plantarchitecture.writePlantGrowthUSD(plantID, filename, 1.0f);
3181 std::ifstream f(filename);
3182 DOCTEST_CHECK(f.is_open());
3183 std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
3188 DOCTEST_CHECK(content.find(
"startTimeCode = 0") != std::string::npos);
3189 DOCTEST_CHECK(content.find(
"endTimeCode = 48") != std::string::npos);
3190 DOCTEST_CHECK(content.find(
"timeCodesPerSecond = 24") != std::string::npos);
3191 DOCTEST_CHECK(content.find(
"framesPerSecond = 24") != std::string::npos);
3192 DOCTEST_CHECK(content.find(
"upAxis = \"Z\"") != std::string::npos);
3195 DOCTEST_CHECK(content.find(
"xformOp:translate.timeSamples") != std::string::npos);
3196 DOCTEST_CHECK(content.find(
"xformOp:orient.timeSamples") != std::string::npos);
3197 DOCTEST_CHECK(content.find(
"visibility.timeSamples") != std::string::npos);
3200 DOCTEST_CHECK(content.find(
"PhysicsArticulationRootAPI") == std::string::npos);
3201 DOCTEST_CHECK(content.find(
"PhysicsRigidBodyAPI") == std::string::npos);
3202 DOCTEST_CHECK(content.find(
"PhysicsJoint") == std::string::npos);
3205 DOCTEST_CHECK(content.find(
"def Mesh \"Visual\"") != std::string::npos);
3207 std::remove(filename.c_str());
3210DOCTEST_TEST_CASE(
"Growth USD export visibility toggling") {
3213 plantarchitecture.disableMessages();
3214 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3217 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 1);
3218 plantarchitecture.registerGrowthFrame(plantID);
3221 plantarchitecture.advanceTime(30);
3222 plantarchitecture.registerGrowthFrame(plantID);
3224 std::string filename =
"test_growth_visibility.usda";
3225 plantarchitecture.writePlantGrowthUSD(plantID, filename);
3227 std::ifstream f(filename);
3228 DOCTEST_CHECK(f.is_open());
3229 std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
3234 DOCTEST_CHECK(content.find(
"\"invisible\"") != std::string::npos);
3235 DOCTEST_CHECK(content.find(
"\"inherited\"") != std::string::npos);
3237 std::remove(filename.c_str());
3240DOCTEST_TEST_CASE(
"Growth USD export error handling") {
3243 plantarchitecture.disableMessages();
3248 plantarchitecture.registerGrowthFrame(9999);
3252 DOCTEST_CHECK(threw);
3255 plantarchitecture.loadPlantModelFromLibrary(
"bean");
3256 uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(
make_vec3(0, 0, 0), 500);
3259 plantarchitecture.writePlantGrowthUSD(plantID,
"test_no_frames.usda");
3263 DOCTEST_CHECK(threw);
3267 return helios::runDoctestWithValidation(argc, argv);