19#include <unordered_set>
22using namespace helios;
25static const float MIN_TUBE_RADIUS_FOR_GEOMETRY = 1e-5f;
26static const float MIN_TUBE_LENGTH_FOR_GEOMETRY = 1e-4f;
28static float clampOffset(
int count_per_axis,
float offset) {
29 if (count_per_axis > 2) {
30 float denom = 0.5f * float(count_per_axis) - 1.f;
31 if (offset * denom > 1.f) {
38float PlantArchitecture::interpolateTube(
const std::vector<float> &P,
const float frac) {
39 assert(frac >= 0 && frac <= 1);
42 float dl = 1.f / float(P.size() - 1);
45 for (
int i = 0; i < P.size() - 1; i++) {
52 if (frac >= f && (frac <= fplus || std::abs(frac - fplus) < 0.0001)) {
53 float V = P.at(i) + (frac - f) / (fplus - f) * (P.at(i + 1) - P.at(i));
64vec3 PlantArchitecture::interpolateTube(
const std::vector<vec3> &P,
const float frac) {
65 assert(frac >= 0 && frac <= 1);
69 for (
int i = 0; i < P.size() - 1; i++) {
70 dl += (P.at(i + 1) - P.at(i)).magnitude();
74 for (
int i = 0; i < P.size() - 1; i++) {
75 float dseg = (P.at(i + 1) - P.at(i)).magnitude();
77 float fplus = f + dseg / dl;
83 if (frac >= f && (frac <= fplus || fabs(frac - fplus) < 0.0001)) {
84 vec3 V = P.at(i) + (frac - f) / (fplus - f) * (P.at(i + 1) - P.at(i));
100 initializePlantModelRegistrations();
102 output_object_data[
"age"] =
false;
103 output_object_data[
"rank"] =
false;
104 output_object_data[
"plantID"] =
false;
105 output_object_data[
"plant_name"] =
false;
106 output_object_data[
"plant_height"] =
false;
107 output_object_data[
"plant_type"] =
false;
108 output_object_data[
"phenology_stage"] =
false;
109 output_object_data[
"leafID"] =
false;
110 output_object_data[
"peduncleID"] =
false;
111 output_object_data[
"closedflowerID"] =
false;
112 output_object_data[
"openflowerID"] =
false;
113 output_object_data[
"fruitID"] =
false;
114 output_object_data[
"carbohydrate_concentration"] =
false;
119 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
120 delete collision_detection_ptr;
121 collision_detection_ptr =
nullptr;
122 owns_collision_detection =
false;
128 if (texture_file.empty()) {
132 std::filesystem::path filepath(texture_file);
135 if (filepath.is_absolute() && std::filesystem::exists(filepath)) {
141 if (!resolved_path.empty()) {
142 return resolved_path.string();
147 if (!resolved_path.empty()) {
148 return resolved_path.string();
152 if (texture_file.find(
"assets/") != 0) {
153 std::string filename = std::filesystem::path(texture_file).filename().string();
154 std::string extension = std::filesystem::path(texture_file).extension().string();
157 std::string subdirectory = (extension ==
".obj" || extension ==
".mtl") ?
"assets/obj/" :
"assets/textures/";
158 std::string assets_path = subdirectory + filename;
161 if (!resolved_path.empty()) {
162 return resolved_path.string();
167 helios_runtime_error(
"ERROR (PlantArchitecture): Could not resolve asset file: " + texture_file +
". Tried: direct path, plugin asset path, and assets/ subdirectory prefix.");
172 leaf_aspect_ratio.initialize(1.f, generator);
186 if (generator !=
nullptr) {
196 internode.pitch.initialize(20, generator);
197 internode.phyllotactic_angle.initialize(137.5, generator);
198 internode.radius_initial.initialize(0.001, generator);
204 petiole.petioles_per_internode = 1;
205 petiole.pitch.initialize(90, generator);
206 petiole.radius.initialize(0.001, generator);
207 petiole.length.initialize(0.05, generator);
208 petiole.curvature.initialize(0, generator);
209 petiole.taper.initialize(0, generator);
210 petiole.color = RGB::forestgreen;
212 petiole.radial_subdivisions = 7;
215 leaf.leaves_per_petiole.initialize(1, generator);
216 leaf.pitch.initialize(0, generator);
217 leaf.yaw.initialize(0, generator);
218 leaf.roll.initialize(0, generator);
219 leaf.leaflet_offset.initialize(0, generator);
220 leaf.leaflet_scale = 1;
221 leaf.prototype_scale.initialize(0.05, generator);
225 peduncle.length.initialize(0.05, generator);
226 peduncle.radius.initialize(0.001, generator);
227 peduncle.pitch.initialize(0, generator);
228 peduncle.roll.initialize(0, generator);
229 peduncle.curvature.initialize(0, generator);
230 petiole.color = RGB::forestgreen;
239 inflorescence.flower_prototype_scale.initialize(0.0075, generator);
240 inflorescence.fruit_prototype_scale.initialize(0.0075, generator);
241 inflorescence.fruit_gravity_factor_fraction.initialize(0, generator);
290 if (a_child_shoot_type_labels.size() != a_child_shoot_type_probabilities.size()) {
291 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Child shoot type labels and probabilities must be the same size.");
292 }
else if (a_child_shoot_type_labels.empty()) {
293 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Input argument vectors were empty.");
294 }
else if (
sum(a_child_shoot_type_probabilities) != 1.f) {
295 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Child shoot type probabilities must sum to 1.");
298 this->child_shoot_type_labels = a_child_shoot_type_labels;
299 this->child_shoot_type_probabilities = a_child_shoot_type_probabilities;
303 const std::map<std::string, float> &build_parameters) {
304 if (plant_count_xy.
x <= 0 || plant_count_xy.
y <= 0) {
305 helios_runtime_error(
"ERROR (PlantArchitecture::buildPlantCanopyFromLibrary): Plant count must be greater than zero.");
308 vec2 canopy_extent(plant_spacing_xy.
x *
float(plant_count_xy.
x - 1), plant_spacing_xy.
y *
float(plant_count_xy.
y - 1));
310 std::vector<uint> plantIDs;
311 plantIDs.reserve(plant_count_xy.
x * plant_count_xy.
y);
312 for (
int j = 0; j < plant_count_xy.
y; j++) {
313 for (
int i = 0; i < plant_count_xy.
x; i++) {
314 if (context_ptr->
randu() < germination_rate) {
315 plantIDs.push_back(
buildPlantInstanceFromLibrary(canopy_center_position +
make_vec3(-0.5f * canopy_extent.
x +
float(i) * plant_spacing_xy.
x, -0.5f * canopy_extent.
y +
float(j) * plant_spacing_xy.
y, 0), 0, build_parameters));
328 std::vector<uint> plantIDs;
329 plantIDs.reserve(plant_count);
330 for (
int i = 0; i < plant_count; i++) {
331 vec3 plant_origin = canopy_center_position +
make_vec3((-0.5f + context_ptr->
randu()) * canopy_extent_xy.
x, (-0.5f + context_ptr->
randu()) * canopy_extent_xy.
y, 0);
340 if (this->shoot_types.find(shoot_type_label) != this->shoot_types.end()) {
342 this->shoot_types.at(shoot_type_label) = shoot_params;
344 this->shoot_types.emplace(shoot_type_label, shoot_params);
349 std::vector<vec3> nodes = parent_shoot_ptr->shoot_internode_vertices.at(
shoot_index.
x);
352 int s_minus = parent_shoot_ptr->shoot_internode_vertices.at(p_minus).size() - 1;
353 nodes.insert(nodes.begin(), parent_shoot_ptr->shoot_internode_vertices.at(p_minus).at(s_minus));
359 std::vector<float> node_radii = parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x);
362 int s_minus = parent_shoot_ptr->shoot_internode_radii.at(p_minus).size() - 1;
363 node_radii.insert(node_radii.begin(), parent_shoot_ptr->shoot_internode_radii.at(p_minus).at(s_minus));
380 if (petiole_index >= this->peduncle_vertices.size()) {
383 if (bud_index >= this->peduncle_vertices.at(petiole_index).size()) {
384 helios_runtime_error(
"ERROR (Phytomer::getPeduncleAxisVector): Floral bud index out of range.");
386 return getAxisVector(stem_fraction, this->peduncle_vertices.at(petiole_index).at(bud_index));
390 assert(stem_fraction >= 0 && stem_fraction <= 1);
393 float frac_plus, frac_minus;
394 if (stem_fraction + df <= 1) {
395 frac_minus = stem_fraction;
396 frac_plus = stem_fraction + df;
398 frac_minus = stem_fraction - df;
399 frac_plus = stem_fraction;
402 const vec3 node_minus = PlantArchitecture::interpolateTube(axis_vertices, frac_minus);
403 const vec3 node_plus = PlantArchitecture::interpolateTube(axis_vertices, frac_plus);
405 vec3 norm = node_plus - node_minus;
412 return parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x).front();
418 for (
int i = 0; i < node_vertices.size() - 1; i++) {
419 length += (node_vertices.at(i + 1) - node_vertices.at(i)).magnitude();
430 return PlantArchitecture::interpolateTube(parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x), stem_fraction);
436 for (
auto &petiole: leaf_objIDs) {
437 for (
auto &leaf_objID: petiole) {
440 float scale_factor = current_leaf_scale_factor.at(p);
441 float scaled_area = obj_area /
powi(scale_factor, 2);
442 leaf_area += scaled_area;
452 if (petiole_index >= leaf_bases.size()) {
454 }
else if (leaf_index >= leaf_bases.at(petiole_index).size()) {
458 return leaf_bases.at(petiole_index).at(leaf_index);
462 for (
auto &petiole: axillary_vegetative_buds) {
463 for (
auto &bud: petiole) {
470 if (petiole_index >= axillary_vegetative_buds.size()) {
473 if (bud_index >= axillary_vegetative_buds.at(petiole_index).size()) {
484 for (
auto &petiole: floral_buds) {
485 for (
auto &fbud: petiole) {
486 if (!fbud.isterminal) {
494 if (petiole_index >= floral_buds.size()) {
497 if (bud_index >= floral_buds.at(petiole_index).size()) {
505 if (fbud.state == state) {
507 }
else if (state == BUD_DORMANT || state == BUD_ACTIVE) {
513 if (plantarchitecture_ptr->carbon_model_enabled) {
514 if (state == BUD_FLOWER_CLOSED || (fbud.state == BUD_ACTIVE && state == BUD_FLOWER_OPEN)) {
516 float flower_cost = calculateFlowerConstructionCosts(fbud);
517 plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC -= flower_cost;
518 }
else if (state == BUD_FRUITING) {
520 float fruit_cost = calculateFruitConstructionCosts(fbud);
521 fbud.previous_fruit_scale_factor = fbud.current_fruit_scale_factor;
522 if (plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC > fruit_cost) {
523 plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC -= fruit_cost;
532 fbud.inflorescence_objIDs.resize(0);
533 fbud.inflorescence_bases.resize(0);
534 fbud.inflorescence_rotation.resize(0);
535 fbud.inflorescence_base_scales.resize(0);
537 if (plantarchitecture_ptr->build_context_geometry_peduncle) {
539 fbud.peduncle_objIDs.resize(0);
544 if (state != BUD_DEAD) {
547 updateInflorescence(fbud);
548 fbud.time_counter = 0;
549 if (fbud.state == BUD_FRUITING) {
555helios::vec3 Phytomer::calculateCollisionAvoidanceDirection(
const helios::vec3 &internode_base_origin,
const helios::vec3 &internode_axis,
bool &collision_detection_active)
const {
556 vec3 collision_optimal_direction;
557 collision_detection_active =
false;
559 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
562 if (!plantarchitecture_ptr->bvh_cached_for_current_growth) {
563 if (plantarchitecture_ptr->printmessages) {
564 std::cout <<
"WARNING: BVH not cached - this indicates rebuildBVHForTimestep() was not called" << std::endl;
566 return collision_optimal_direction;
570 std::vector<uint> filtered_geometry;
574 float look_ahead_distance = plantarchitecture_ptr->collision_cone_height;
578 float max_relevant_distance = look_ahead_distance * 1.1f;
582 filtered_geometry = plantarchitecture_ptr->collision_detection_ptr->
filterGeometryByDistance(internode_base_origin, max_relevant_distance, plantarchitecture_ptr->cached_target_geometry);
586 plantarchitecture_ptr->cached_filtered_geometry = filtered_geometry;
588 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
590 vec3 apex = internode_base_origin;
591 vec3 central_axis = internode_axis;
593 float height = plantarchitecture_ptr->collision_cone_height;
594 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
595 int samples = plantarchitecture_ptr->collision_sample_count;
598 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
601 if (optimal_result.confidence > 0.0f) {
602 collision_optimal_direction = optimal_result.direction;
604 collision_detection_active =
true;
608 return collision_optimal_direction;
611helios::vec3 Phytomer::calculatePetioleCollisionAvoidanceDirection(
const helios::vec3 &petiole_base_origin,
const helios::vec3 &proposed_petiole_axis,
bool &collision_detection_active)
const {
612 vec3 collision_optimal_direction;
613 collision_detection_active =
false;
615 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
617 std::vector<uint> target_geometry;
618 if (!plantarchitecture_ptr->collision_target_UUIDs.empty()) {
619 target_geometry = plantarchitecture_ptr->collision_target_UUIDs;
620 }
else if (!plantarchitecture_ptr->collision_target_object_IDs.empty()) {
621 for (
uint objID: plantarchitecture_ptr->collision_target_object_IDs) {
623 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
631 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
633 vec3 apex = petiole_base_origin;
634 vec3 central_axis = proposed_petiole_axis;
636 float height = plantarchitecture_ptr->collision_cone_height;
637 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
638 int samples = plantarchitecture_ptr->collision_sample_count;
641 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
644 if (optimal_result.confidence > 0.0f) {
645 collision_optimal_direction = optimal_result.direction;
647 collision_detection_active =
true;
651 return collision_optimal_direction;
654helios::vec3 Phytomer::calculateFruitCollisionAvoidanceDirection(
const helios::vec3 &fruit_base_origin,
const helios::vec3 &proposed_fruit_axis,
bool &collision_detection_active)
const {
655 vec3 collision_optimal_direction;
656 collision_detection_active =
false;
659 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
661 std::vector<uint> target_geometry;
662 if (!plantarchitecture_ptr->collision_target_UUIDs.empty()) {
663 target_geometry = plantarchitecture_ptr->collision_target_UUIDs;
664 }
else if (!plantarchitecture_ptr->collision_target_object_IDs.empty()) {
665 for (
uint objID: plantarchitecture_ptr->collision_target_object_IDs) {
667 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
675 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
677 vec3 apex = fruit_base_origin;
678 vec3 central_axis = proposed_fruit_axis;
680 float height = plantarchitecture_ptr->collision_cone_height;
681 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
682 int samples = plantarchitecture_ptr->collision_sample_count;
685 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
688 if (optimal_result.confidence > 0.0f) {
689 collision_optimal_direction = optimal_result.direction;
691 collision_detection_active =
true;
695 static int no_collision_count = 0;
696 if (optimal_result.confidence <= 0.0f) {
697 no_collision_count++;
700 static int no_bvh_count = 0;
704 return collision_optimal_direction;
707bool Phytomer::applySolidObstacleAvoidance(
const helios::vec3 ¤t_position,
helios::vec3 &internode_axis)
const {
708 if (!plantarchitecture_ptr->solid_obstacle_avoidance_enabled || plantarchitecture_ptr->solid_obstacle_UUIDs.empty()) {
718 vec3 growth_direction = internode_axis;
722 float nearest_obstacle_distance;
723 vec3 nearest_obstacle_direction;
726 float hard_detection_cone_angle =
deg2rad(20.0f);
727 float detection_distance = plantarchitecture_ptr->solid_obstacle_avoidance_distance;
729 if (plantarchitecture_ptr->collision_detection_ptr !=
nullptr && plantarchitecture_ptr->collision_detection_ptr->
findNearestSolidObstacleInCone(current_position, growth_direction, hard_detection_cone_angle, detection_distance,
730 plantarchitecture_ptr->solid_obstacle_UUIDs, nearest_obstacle_distance, nearest_obstacle_direction)) {
733 float buffer_distance = detection_distance * 0.05f;
736 float normalized_distance = nearest_obstacle_distance / detection_distance;
737 float buffer_threshold = buffer_distance / detection_distance;
739 vec3 avoidance_direction;
740 float rotation_fraction;
742 if (nearest_obstacle_distance <= buffer_distance) {
745 avoidance_direction = current_position - (current_position + nearest_obstacle_direction * nearest_obstacle_distance);
746 if (avoidance_direction.
magnitude() < 0.001f) {
748 avoidance_direction =
cross(growth_direction, nearest_obstacle_direction);
749 if (avoidance_direction.
magnitude() < 0.001f) {
750 avoidance_direction =
make_vec3(0, 0, 1);
756 rotation_fraction = 1.0f;
759 float buffer_blend_factor = 0.8f;
760 internode_axis = (1.0f - buffer_blend_factor) * growth_direction + buffer_blend_factor * avoidance_direction;
767 float dot_with_obstacle = normalize(growth_direction) * normalize(nearest_obstacle_direction);
768 float angle_deficit =
asin_safe(fabs(dot_with_obstacle));
771 vec3 rotation_axis =
cross(growth_direction, -nearest_obstacle_direction);
773 if (rotation_axis.
magnitude() > 0.001f) {
779 if (rotation_axis.
magnitude() > 0.001f) {
783 float surface_threshold_fraction = 0.2f;
785 if (normalized_distance <= surface_threshold_fraction) {
787 rotation_fraction = 1.0f;
790 float remaining_distance = normalized_distance - surface_threshold_fraction;
791 float max_remaining_distance = 1.0f - surface_threshold_fraction;
794 float distance_factor = remaining_distance / max_remaining_distance;
795 float min_rotation_fraction = 0.05f;
798 rotation_fraction = min_rotation_fraction + (1.0f - min_rotation_fraction) * exp(-3.0f * distance_factor);
802 float rotation_this_step = angle_deficit * rotation_fraction;
805 internode_axis =
rotatePointAboutLine(internode_axis, nullorigin, rotation_axis, rotation_this_step);
817 vec3 attraction_direction;
818 attraction_active =
false;
821 if (plantarchitecture_ptr->plant_instances.find(plantID) != plantarchitecture_ptr->plant_instances.end()) {
822 const auto &plant = plantarchitecture_ptr->plant_instances.at(plantID);
823 if (plant.attraction_points_enabled && !plant.attraction_points.empty()) {
825 vec3 look_direction = internode_axis;
827 float half_angle_degrees =
rad2deg(plant.attraction_cone_half_angle_rad);
828 float look_ahead_distance = plant.attraction_cone_height;
830 vec3 direction_to_closest;
831 if (plantarchitecture_ptr->
detectAttractionPointsInCone(plant.attraction_points, internode_base_origin, look_direction, look_ahead_distance, half_angle_degrees, direction_to_closest)) {
832 attraction_direction = direction_to_closest;
834 attraction_active =
true;
836 return attraction_direction;
841 if (!plantarchitecture_ptr->attraction_points_enabled || plantarchitecture_ptr->attraction_points.empty()) {
842 return attraction_direction;
846 vec3 look_direction = internode_axis;
848 float half_angle_degrees =
rad2deg(plantarchitecture_ptr->attraction_cone_half_angle_rad);
849 float look_ahead_distance = plantarchitecture_ptr->attraction_cone_height;
851 vec3 direction_to_closest;
852 if (plantarchitecture_ptr->
detectAttractionPointsInCone(plantarchitecture_ptr->attraction_points, internode_base_origin, look_direction, look_ahead_distance, half_angle_degrees, direction_to_closest)) {
853 attraction_direction = direction_to_closest;
855 attraction_active =
true;
858 return attraction_direction;
864 if (attraction_points.empty()) {
868 if (look_ahead_distance <= 0.0f) {
874 if (half_angle_degrees <= 0.0f || half_angle_degrees >= 180.0f) {
881 float half_angle_rad = half_angle_degrees *
M_PI / 180.0f;
884 vec3 axis = look_direction;
888 bool found_any =
false;
889 float min_angular_distance = std::numeric_limits<float>::max();
893 for (
const vec3 &point: attraction_points) {
895 vec3 to_point = point - vertex;
896 float distance_to_point = to_point.
magnitude();
899 if (distance_to_point < 1e-6f || distance_to_point > look_ahead_distance) {
904 vec3 direction_to_point = to_point;
908 float cos_angle = axis * direction_to_point;
911 cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
913 float angle = std::acos(cos_angle);
916 if (angle <= half_angle_rad) {
920 if (angle < min_angular_distance) {
921 min_angular_distance = angle;
922 closest_point = point;
929 direction_to_closest = closest_point - vertex;
941 if (attraction_points_input.empty()) {
945 if (look_ahead_distance <= 0.0f) {
951 if (half_angle_degrees <= 0.0f || half_angle_degrees >= 180.0f) {
958 float half_angle_rad = half_angle_degrees *
M_PI / 180.0f;
961 vec3 axis = look_direction;
965 bool found_any =
false;
966 float min_angular_distance = std::numeric_limits<float>::max();
970 for (
const vec3 &point: attraction_points_input) {
972 vec3 to_point = point - vertex;
973 float distance_to_point = to_point.
magnitude();
976 if (distance_to_point <= 1e-6 || distance_to_point > look_ahead_distance) {
981 vec3 direction_to_point = to_point;
985 float cos_angle = axis * direction_to_point;
988 cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
990 float angle = std::acos(cos_angle);
993 if (angle <= half_angle_rad) {
997 if (angle < min_angular_distance) {
998 min_angular_distance = angle;
999 closest_point = point;
1006 direction_to_closest = closest_point - vertex;
1015 auto shoot_tree_ptr = &plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree;
1018 vec3 parent_internode_axis;
1019 vec3 parent_petiole_axis;
1020 vec3 internode_base_position;
1021 if (phytomers.empty()) {
1023 if (parent_shoot_ID == -1) {
1025 parent_internode_axis =
make_vec3(0, 0, 1);
1026 parent_petiole_axis =
make_vec3(0, -1, 0);
1029 assert(parent_shoot_ID < shoot_tree_ptr->size() && parent_node_index < shoot_tree_ptr->at(parent_shoot_ID)->phytomers.size());
1030 parent_internode_axis = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
1032 if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
1033 parent_petiole_axis =
cross(parent_internode_axis,
make_vec3(0, 0, 1));
1034 if (parent_petiole_axis.
magnitude() < 0.01f) {
1036 parent_petiole_axis =
make_vec3(0, 1, 0);
1040 float phyllotactic_angle = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->internode_phyllotactic_angle;
1041 float cumulative_rotation = float(parent_node_index) * phyllotactic_angle;
1044 parent_petiole_axis = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->getPetioleAxisVector(0.f, parent_petiole_index);
1047 internode_base_position = base_position;
1050 parent_internode_axis = phytomers.back()->getInternodeAxisVector(1.f);
1052 if (phytomers.back()->petiole_vertices.empty()) {
1053 parent_petiole_axis =
cross(parent_internode_axis,
make_vec3(0, 0, 1));
1054 if (parent_petiole_axis.
magnitude() < 0.01f) {
1056 parent_petiole_axis =
make_vec3(0, 1, 0);
1060 uint prev_phytomer_index = phytomers.size() - 1;
1061 float phyllotactic_angle = phytomers.back()->internode_phyllotactic_angle;
1062 float cumulative_rotation = float(prev_phytomer_index) * phyllotactic_angle;
1065 parent_petiole_axis = phytomers.back()->getPetioleAxisVector(0.f, 0);
1067 internode_base_position = shoot_internode_vertices.back().back();
1070 std::shared_ptr<Phytomer> phytomer = std::make_shared<Phytomer>(phytomer_parameters,
this,
static_cast<uint>(phytomers.size()), parent_internode_axis, parent_petiole_axis, internode_base_position, this->base_rotation, internode_radius,
1071 internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, rank, plantarchitecture_ptr, context_ptr);
1072 shoot_tree_ptr->at(ID)->phytomers.push_back(phytomer);
1073 phytomer = shoot_tree_ptr->at(ID)->phytomers.back();
1076 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1079 for (
auto &vbud: petiole) {
1080 phytomer->setVegetativeBudState(BUD_DORMANT, vbud);
1081 vbud.shoot_type_label = child_shoot_type_label;
1085 if (plantarchitecture_ptr->carbon_model_enabled) {
1086 if (sampleVegetativeBudBreak_carb(phytomer->shoot_index.x)) {
1088 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1090 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1095 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1097 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1105 uint petiole_index = 0;
1106 for (
auto &petiole: phytomer->floral_buds) {
1108 for (
auto &fbud: petiole) {
1110 phytomer->setFloralBudState(BUD_DORMANT, fbud);
1114 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1117 fbud.parent_index = petiole_index;
1118 fbud.bud_index = bud_index;
1130 if (plantarchitecture_ptr->build_context_geometry_internode && context_ptr->
doesObjectExist(internode_tube_objID)) {
1132 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1133 context_ptr->
setObjectData(internode_tube_objID,
"age", phytomer->age);
1135 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1136 context_ptr->
setObjectData(internode_tube_objID,
"rank", rank);
1138 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1139 context_ptr->
setObjectData(internode_tube_objID,
"plantID", (
int) plantID);
1141 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1142 context_ptr->
setObjectData(internode_tube_objID,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1145 if (plantarchitecture_ptr->build_context_geometry_petiole) {
1146 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1147 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"age", phytomer->age);
1149 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1150 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"rank", phytomer->rank);
1152 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1153 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"plantID", (
int) plantID);
1155 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1156 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1159 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1160 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"age", phytomer->age);
1162 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1163 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"rank", phytomer->rank);
1165 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1166 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"plantID", (
int) plantID);
1168 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1169 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1172 if (plantarchitecture_ptr->output_object_data.at(
"leafID")) {
1173 for (
auto &petiole: phytomer->leaf_objIDs) {
1174 for (
uint objID: petiole) {
1181 phytomer_parameters.
phytomer_creation_function(phytomer, current_node_number, this->parent_node_index, shoot_parameters.
max_nodes.val(), plantarchitecture_ptr->plant_instances.at(plantID).current_age);
1185 if (plantarchitecture_ptr->carbon_model_enabled) {
1186 this->carbohydrate_pool_molC -= phytomer->calculatePhytomerConstructionCosts();
1189 return (
int) phytomers.size() - 1;
1192void Shoot::breakDormancy() {
1195 int phytomer_ind = 0;
1196 for (
auto &phytomer: phytomers) {
1197 for (
auto &petiole: phytomer->floral_buds) {
1198 for (
auto &fbud: petiole) {
1199 if (fbud.state != BUD_DEAD) {
1200 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1202 if (meristem_is_alive && fbud.isterminal) {
1203 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1205 fbud.time_counter = 0;
1208 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1209 for (
auto &vbud: petiole) {
1210 if (vbud.state != BUD_DEAD) {
1211 if (plantarchitecture_ptr->carbon_model_enabled) {
1212 if (sampleVegetativeBudBreak_carb(phytomer_ind)) {
1214 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1216 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1221 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1223 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1230 phytomer->isdormant =
false;
1235void Shoot::makeDormant() {
1238 nodes_this_season = 0;
1240 for (
auto &phytomer: phytomers) {
1241 for (
auto &petiole: phytomer->floral_buds) {
1243 for (
auto &fbud: petiole) {
1244 if (fbud.state != BUD_DORMANT) {
1245 phytomer->setFloralBudState(BUD_DEAD, fbud);
1249 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1250 for (
auto &vbud: petiole) {
1251 if (vbud.state != BUD_DORMANT) {
1252 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1256 if (!plantarchitecture_ptr->plant_instances.at(plantID).is_evergreen) {
1257 phytomer->removeLeaf();
1259 phytomer->isdormant =
true;
1268 this->meristem_is_alive =
false;
1269 this->phyllochron_counter = 0;
1273 for (
auto &phytomer: phytomers) {
1274 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1275 for (
auto &vbud: petiole) {
1276 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1284 for (
int bud = 0; bud < Nbuds; bud++) {
1286 bud_new.isterminal =
true;
1287 bud_new.parent_index = 0;
1288 bud_new.bud_index = bud;
1289 bud_new.base_position = shoot_internode_vertices.back().back();
1290 float pitch_adjustment = 0;
1292 pitch_adjustment =
deg2rad(30);
1294 float yaw_adjustment =
static_cast<float>(bud_new.bud_index) * 2.f *
PI_F /
float(Nbuds);
1296 bud_new.base_rotation = make_AxisRotation(pitch_adjustment, yaw_adjustment, 0);
1297 bud_new.bending_axis =
make_vec3(1, 0, 0);
1299 phytomers.back()->floral_buds.push_back({bud_new});
1306 float shoot_volume = 0;
1307 for (
const auto &phytomer: phytomers) {
1312 return shoot_volume;
1316 float shoot_length = 0;
1317 for (
const auto &phytomer: phytomers) {
1318 shoot_length += phytomer->getInternodeLength();
1320 return shoot_length;
1325 if (parent_shoot_ID >= 0) {
1328 auto parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
1330 const vec3 current_origin = shoot_internode_vertices.front().front();
1331 const vec3 updated_origin = parent_shoot->shoot_internode_vertices.at(this->parent_node_index).back();
1332 vec3 shift = updated_origin - current_origin;
1338 for (
auto &phytomer: shoot_internode_vertices) {
1339 for (
vec3 &node: phytomer) {
1346 if (update_context_geometry && plantarchitecture_ptr->build_context_geometry_internode && context_ptr->
doesObjectExist(internode_tube_objID)) {
1352 for (
int p = 0; p < phytomers.size(); p++) {
1353 vec3 petiole_base = shoot_internode_vertices.at(p).back();
1354 if (parent_shoot_ID >= 0) {
1356 auto parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
1359 phytomers.at(p)->setPetioleBase(petiole_base);
1363 for (
const auto &node: childIDs) {
1364 for (
int child_shoot_ID: node.second) {
1365 plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->updateShootNodes(update_context_geometry);
1371 uint phytomer_count = this->phytomers.size();
1373 uint phytomer_index = 0;
1374 if (shoot_fraction > 0) {
1375 phytomer_index = std::ceil(shoot_fraction *
float(phytomer_count)) - 1;
1378 assert(phytomer_index < phytomer_count);
1380 return this->phytomers.at(phytomer_index)->getInternodeAxisVector(0.5);
1384 for (
int i = node_index; i >= 0; i--) {
1385 shoot->phytomers.at(i)->downstream_leaf_area += leaf_area;
1386 shoot->phytomers.at(i)->downstream_leaf_area = std::max(0.f, shoot->phytomers.at(i)->downstream_leaf_area);
1389 if (shoot->parent_shoot_ID >= 0) {
1390 Shoot *parent_shoot = plantarchitecture_ptr->plant_instances.at(shoot->plantID).shoot_tree.at(shoot->parent_shoot_ID).get();
1397 if (start_node_index >= phytomers.size()) {
1403 for (
uint p = start_node_index; p < phytomers.size(); p++) {
1405 auto phytomer = phytomers.at(p);
1406 for (
auto &petiole: phytomer->leaf_objIDs) {
1407 for (
uint objID: petiole) {
1415 if (childIDs.find(p) != childIDs.end()) {
1416 for (
int child_shoot_ID: childIDs.at(p)) {
1417 area += plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->sumShootLeafArea(0);
1427 if (start_node_index >= phytomers.size()) {
1433 for (
uint p = start_node_index; p < phytomers.size(); p++) {
1435 if (childIDs.find(p) != childIDs.end()) {
1436 for (
int child_shoot_ID: childIDs.at(p)) {
1437 volume += plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
1446 const AxisRotation &shoot_base_rotation,
float internode_radius,
float internode_length_max,
float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
uint rank,
PlantArchitecture *plantarchitecture_ptr,
1447 helios::Context *context_ptr) : rank(rank), context_ptr(context_ptr), plantarchitecture_ptr(plantarchitecture_ptr) {
1448 this->phytomer_parameters = params;
1451 ShootParameters parent_shoot_parameters = parent_shoot->shoot_parameters;
1453 this->internode_radius_initial = internode_radius;
1454 this->internode_length_max = internode_length_max;
1457 this->rank = parent_shoot->rank;
1458 this->plantID = parent_shoot->plantID;
1459 this->parent_shoot_ID = parent_shoot->ID;
1460 this->parent_shoot_ptr = parent_shoot;
1462 bool build_context_geometry_internode = plantarchitecture_ptr->build_context_geometry_internode;
1463 bool build_context_geometry_petiole = plantarchitecture_ptr->build_context_geometry_petiole;
1464 bool build_context_geometry_peduncle = plantarchitecture_ptr->build_context_geometry_peduncle;
1472 uint Ndiv_internode_length = std::max(
uint(1), phytomer_parameters.
internode.length_segments);
1473 uint Ndiv_internode_radius = std::max(
uint(3), phytomer_parameters.
internode.radial_subdivisions);
1474 uint Ndiv_petiole_length = std::max(
uint(1), phytomer_parameters.
petiole.length_segments);
1475 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
1478 if (phytomer_parameters.
internode.length_segments == 0 || phytomer_parameters.
internode.radial_subdivisions < 3) {
1479 build_context_geometry_internode =
false;
1481 if (phytomer_parameters.
petiole.length_segments == 0 || phytomer_parameters.
petiole.radial_subdivisions < 3) {
1482 build_context_geometry_petiole =
false;
1485 if (phytomer_parameters.
petiole.petioles_per_internode == 0) {
1487 build_context_geometry_petiole =
false;
1488 phytomer_parameters.
leaf.leaves_per_petiole = 0;
1491 if (phytomer_parameters.
petiole.petioles_per_internode < 0) {
1492 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Number of petioles per internode cannot be negative.");
1495 current_internode_scale_factor = internode_length_scale_factor_fraction;
1496 current_leaf_scale_factor.resize(phytomer_parameters.
petiole.petioles_per_internode);
1497 std::fill(current_leaf_scale_factor.begin(), current_leaf_scale_factor.end(), leaf_scale_factor_fraction);
1499 if (internode_radius == 0.f) {
1500 internode_radius = MIN_TUBE_RADIUS_FOR_GEOMETRY;
1504 float internode_length = internode_length_scale_factor_fraction * internode_length_max;
1505 float dr_internode = internode_length / float(phytomer_parameters.
internode.length_segments);
1506 float dr_internode_max = internode_length_max / float(phytomer_parameters.
internode.length_segments);
1507 std::vector<vec3> phytomer_internode_vertices;
1508 std::vector<float> phytomer_internode_radii;
1509 phytomer_internode_vertices.resize(Ndiv_internode_length + 1);
1510 phytomer_internode_vertices.at(0) = internode_base_origin;
1511 phytomer_internode_radii.resize(Ndiv_internode_length + 1);
1512 phytomer_internode_radii.at(0) = internode_radius;
1514 phytomer_parameters.
internode.pitch.resample();
1515 internode_phyllotactic_angle =
deg2rad(phytomer_parameters.
internode.phyllotactic_angle.val());
1516 phytomer_parameters.
internode.phyllotactic_angle.resample();
1519 petiole_length.resize(phytomer_parameters.
petiole.petioles_per_internode);
1521 petiole_radii.resize(phytomer_parameters.
petiole.petioles_per_internode);
1524 peduncle_vertices.resize(phytomer_parameters.
petiole.petioles_per_internode);
1525 peduncle_radii.resize(phytomer_parameters.
petiole.petioles_per_internode);
1526 peduncle_length.resize(phytomer_parameters.
petiole.petioles_per_internode);
1527 peduncle_radius.resize(phytomer_parameters.
petiole.petioles_per_internode);
1528 peduncle_pitch.resize(phytomer_parameters.
petiole.petioles_per_internode);
1529 peduncle_curvature.resize(phytomer_parameters.
petiole.petioles_per_internode);
1530 petiole_pitch.resize(phytomer_parameters.
petiole.petioles_per_internode);
1531 petiole_curvature.resize(phytomer_parameters.
petiole.petioles_per_internode);
1532 petiole_taper.resize(phytomer_parameters.
petiole.petioles_per_internode);
1533 petiole_axis_initial.resize(phytomer_parameters.
petiole.petioles_per_internode);
1534 petiole_rotation_axis.resize(phytomer_parameters.
petiole.petioles_per_internode);
1535 std::vector<float> dr_petiole(phytomer_parameters.
petiole.petioles_per_internode);
1536 std::vector<float> dr_petiole_max(phytomer_parameters.
petiole.petioles_per_internode);
1537 for (
int p = 0; p < phytomer_parameters.
petiole.petioles_per_internode; p++) {
1539 petiole_radii.at(p).resize(Ndiv_petiole_length + 1);
1541 petiole_length.at(p) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.length.val();
1542 if (petiole_length.at(p) <= 0.f) {
1543 petiole_length.at(p) = MIN_TUBE_LENGTH_FOR_GEOMETRY;
1545 dr_petiole.at(p) = petiole_length.at(p) / float(phytomer_parameters.
petiole.length_segments);
1546 dr_petiole_max.at(p) = phytomer_parameters.
petiole.length.val() / float(phytomer_parameters.
petiole.length_segments);
1548 petiole_radii.at(p).at(0) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.radius.val();
1549 if (petiole_radii.at(p).at(0) <= 0.f) {
1550 petiole_radii.at(p).at(0) = MIN_TUBE_RADIUS_FOR_GEOMETRY;
1553 phytomer_parameters.
petiole.length.resample();
1555 petiole_objIDs.resize(phytomer_parameters.
petiole.petioles_per_internode);
1558 leaf_bases.resize(phytomer_parameters.
petiole.petioles_per_internode);
1559 leaf_objIDs.resize(phytomer_parameters.
petiole.petioles_per_internode);
1560 leaf_size_max.resize(phytomer_parameters.
petiole.petioles_per_internode);
1561 leaf_rotation.resize(phytomer_parameters.
petiole.petioles_per_internode);
1562 int leaves_per_petiole = phytomer_parameters.
leaf.leaves_per_petiole.val();
1563 float leaflet_offset_val = clampOffset(leaves_per_petiole, phytomer_parameters.
leaf.leaflet_offset.val());
1564 phytomer_parameters.
leaf.leaves_per_petiole.resample();
1565 for (
uint petiole = 0; petiole < phytomer_parameters.
petiole.petioles_per_internode; petiole++) {
1566 leaf_size_max.at(petiole).resize(leaves_per_petiole);
1567 leaf_rotation.at(petiole).resize(leaves_per_petiole);
1570 internode_colors.resize(Ndiv_internode_length + 1);
1571 internode_colors.at(0) = phytomer_parameters.
internode.color;
1572 petiole_colors.resize(Ndiv_petiole_length + 1);
1573 petiole_colors.at(0) = phytomer_parameters.
petiole.color;
1575 vec3 internode_axis = parent_internode_axis;
1577 vec3 petiole_rotation_axis =
cross(parent_internode_axis, parent_petiole_axis);
1578 if (petiole_rotation_axis ==
make_vec3(0, 0, 0)) {
1579 petiole_rotation_axis =
make_vec3(1, 0, 0);
1583 if (phytomer_index == 0) {
1586 if (phytomer_index == 0) {
1589 if (internode_pitch != 0.f) {
1590 if (phytomer_index == 0) {
1593 if (phytomer_index == 0) {
1597 float roll_nudge = 0.f;
1603 if (phytomer_index == 0) {
1606 if (shoot_base_rotation.roll != 0.f || roll_nudge != 0.f) {
1607 if (phytomer_index == 0) {
1612 if (phytomer_index == 0) {
1616 vec3 base_pitch_axis = -1 *
cross(parent_internode_axis, parent_petiole_axis);
1619 if (shoot_base_rotation.pitch != 0.f) {
1620 if (phytomer_index == 0) {
1624 if (phytomer_index == 0) {
1629 if (shoot_base_rotation.yaw != 0) {
1630 if (phytomer_index == 0) {
1634 if (phytomer_index == 0) {
1646 if (internode_pitch != 0) {
1654 if (internode_axis ==
make_vec3(0, 0, 1)) {
1655 shoot_bending_axis =
make_vec3(0, 1, 0);
1659 vec3 collision_optimal_direction;
1660 bool collision_detection_active =
false;
1661 vec3 attraction_direction;
1662 bool attraction_active =
false;
1663 bool obstacle_found =
false;
1666 collision_optimal_direction = calculateCollisionAvoidanceDirection(internode_base_origin, internode_axis, collision_detection_active);
1669 attraction_direction = calculateAttractionPointDirection(internode_base_origin, internode_axis, attraction_active);
1678 for (
int inode_segment = 1; inode_segment <= Ndiv_internode_length; inode_segment++) {
1680 if ((fabs(parent_shoot->gravitropic_curvature) > 0 || parent_shoot_parameters.
tortuosity.val() > 0) &&
shoot_index.
x > 0) {
1683 float current_curvature_fact = 0.5f - internode_axis.
z / 2.f;
1684 if (internode_axis.
z < 0) {
1685 current_curvature_fact *= 2.f;
1688 float dt = dr_internode_max / float(Ndiv_internode_length);
1690 parent_shoot->curvature_perturbation += -0.5f * parent_shoot->curvature_perturbation * dt + parent_shoot_parameters.
tortuosity.val() * context_ptr->
randn() * sqrt(dt);
1692 float curvature_angle =
deg2rad((parent_shoot->gravitropic_curvature * current_curvature_fact * dr_internode_max + parent_shoot->curvature_perturbation));
1695 parent_shoot->yaw_perturbation += -0.5f * parent_shoot->yaw_perturbation * dt + parent_shoot_parameters.
tortuosity.val() * context_ptr->
randn() * sqrt(dt);
1697 float yaw_angle =
deg2rad((parent_shoot->yaw_perturbation));
1702 vec3 current_position = phytomer_internode_vertices.at(inode_segment - 1);
1703 obstacle_found = applySolidObstacleAvoidance(current_position, internode_axis);
1708 vec3 final_direction = internode_axis;
1710 if (attraction_active) {
1712 float attraction_weight = plantarchitecture_ptr->attraction_weight;
1714 if (obstacle_found) {
1717 attraction_weight *= plantarchitecture_ptr->attraction_obstacle_reduction_factor;
1721 final_direction = (1.0f - attraction_weight) * final_direction + attraction_weight * attraction_direction;
1725 plantarchitecture_ptr->collision_avoidance_applied =
true;
1727 }
else if (collision_detection_active && !obstacle_found) {
1729 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
1732 final_direction = inertia_weight * final_direction + (1.0f - inertia_weight) * collision_optimal_direction;
1736 plantarchitecture_ptr->collision_avoidance_applied =
true;
1739 if (obstacle_found) {
1741 plantarchitecture_ptr->collision_avoidance_applied =
true;
1745 internode_axis = final_direction;
1761 phytomer_internode_vertices.at(inode_segment) = phytomer_internode_vertices.at(inode_segment - 1) + dr_internode * internode_axis;
1763 phytomer_internode_radii.at(inode_segment) = internode_radius;
1764 internode_colors.at(inode_segment) = phytomer_parameters.
internode.color;
1769 parent_shoot_ptr->shoot_internode_vertices.push_back(phytomer_internode_vertices);
1770 parent_shoot_ptr->shoot_internode_radii.push_back(phytomer_internode_radii);
1773 parent_shoot_ptr->shoot_internode_vertices.emplace_back(phytomer_internode_vertices.begin() + 1, phytomer_internode_vertices.end());
1774 parent_shoot_ptr->shoot_internode_radii.emplace_back(phytomer_internode_radii.begin() + 1, phytomer_internode_radii.end());
1778 if (build_context_geometry_internode) {
1780 float texture_repeat_length = 0.25f;
1782 for (
auto &phytomer: parent_shoot_ptr->phytomers) {
1783 length += phytomer->internode_length_max;
1785 std::vector<float> uv_y(phytomer_internode_vertices.size());
1786 float dy = internode_length_max / float(uv_y.size() - 1);
1787 for (
int j = 0; j < uv_y.size(); j++) {
1788 uv_y.at(j) = (length + j * dy) / texture_repeat_length - std::floor((length + j * dy) / texture_repeat_length);
1794 if (!context_ptr->
doesObjectExist(parent_shoot->internode_tube_objID)) {
1796 if (!resolved_internode_texture.empty()) {
1797 parent_shoot->internode_tube_objID = context_ptr->
addTubeObject(Ndiv_internode_radius, phytomer_internode_vertices, phytomer_internode_radii, resolved_internode_texture.c_str(), uv_y);
1799 parent_shoot->internode_tube_objID = context_ptr->
addTubeObject(Ndiv_internode_radius, phytomer_internode_vertices, phytomer_internode_radii, internode_colors);
1804 for (
int inode_segment = 1; inode_segment <= Ndiv_internode_length; inode_segment++) {
1805 if (!resolved_internode_texture.empty()) {
1806 context_ptr->
appendTubeSegment(parent_shoot->internode_tube_objID, phytomer_internode_vertices.at(inode_segment), phytomer_internode_radii.at(inode_segment), resolved_internode_texture.c_str(),
1807 {uv_y.at(inode_segment - 1), uv_y.at(inode_segment)});
1809 context_ptr->
appendTubeSegment(parent_shoot->internode_tube_objID, phytomer_internode_vertices.at(inode_segment), phytomer_internode_radii.at(inode_segment), internode_colors.at(inode_segment));
1818 for (
int petiole = 0; petiole < phytomer_parameters.
petiole.petioles_per_internode; petiole++) {
1821 vec3 petiole_axis = internode_axis;
1827 petiole_pitch.at(petiole) = 0.0f;
1830 petiole_pitch.at(petiole) =
deg2rad(phytomer_parameters.
petiole.pitch.val());
1831 phytomer_parameters.
petiole.pitch.resample();
1832 if (fabs(petiole_pitch.at(petiole)) <
deg2rad(5.f)) {
1833 petiole_pitch.at(petiole) =
deg2rad(5.f);
1839 if (phytomer_index != 0 && internode_phyllotactic_angle != 0) {
1846 petiole_curvature.at(petiole) = phytomer_parameters.
petiole.curvature.val();
1847 phytomer_parameters.
petiole.curvature.resample();
1849 vec3 petiole_rotation_axis_actual = petiole_rotation_axis;
1850 vec3 petiole_axis_actual = petiole_axis;
1853 float budrot = float(petiole) * 2.f *
PI_F / float(phytomer_parameters.
petiole.petioles_per_internode);
1859 this->petiole_axis_initial.at(petiole) = petiole_axis_actual;
1860 this->petiole_rotation_axis.at(petiole) = petiole_rotation_axis_actual;
1863 vec3 collision_optimal_petiole_direction;
1864 bool petiole_collision_active =
false;
1866 if (plantarchitecture_ptr->petiole_collision_detection_enabled) {
1867 collision_optimal_petiole_direction = calculatePetioleCollisionAvoidanceDirection(phytomer_internode_vertices.back(),
1868 petiole_axis_actual, petiole_collision_active);
1871 if (petiole_collision_active) {
1872 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
1873 vec3 natural_petiole_direction = petiole_axis_actual;
1878 petiole_axis_actual = inertia_weight * natural_petiole_direction + (1.0f - inertia_weight) * collision_optimal_petiole_direction;
1883 vec3 bending_direction = collision_optimal_petiole_direction - (collision_optimal_petiole_direction * natural_petiole_direction) * natural_petiole_direction;
1885 if (bending_direction.
magnitude() > 1e-6f) {
1890 vec3 curvature_axis =
cross(natural_petiole_direction, bending_direction);
1892 if (curvature_axis.
magnitude() > 1e-6f) {
1896 float angular_deviation = acosf(std::max(-1.0f, std::min(1.0f, collision_optimal_petiole_direction * natural_petiole_direction)));
1899 float desired_curvature_deg =
rad2deg(angular_deviation) * (1.0f - inertia_weight);
1902 float curvature_sign = (curvature_axis * petiole_rotation_axis_actual > 0) ? 1.0f : -1.0f;
1905 petiole_curvature.at(petiole) += curvature_sign * desired_curvature_deg * 0.5f;
1911 petiole_taper.at(petiole) = phytomer_parameters.
petiole.taper.val();
1912 phytomer_parameters.
petiole.taper.resample();
1916 for (
int j = 1; j <= Ndiv_petiole_length; j++) {
1917 if (fabs(petiole_curvature.at(petiole)) > 0) {
1923 petiole_radii.at(petiole).at(j) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.radius.val() * (1.f - petiole_taper.at(petiole) / float(Ndiv_petiole_length) * float(j));
1924 petiole_colors.at(j) = phytomer_parameters.
petiole.color;
1927 assert(!std::isnan(petiole_radii.at(petiole).at(j)) && std::isfinite(petiole_radii.at(petiole).at(j)));
1930 if (build_context_geometry_petiole) {
1932 if (!petiole_objIDs.at(petiole).empty()) {
1939 std::vector<VegetativeBud> vegetative_buds_new;
1940 vegetative_buds_new.resize(phytomer_parameters.
internode.max_vegetative_buds_per_petiole.val());
1941 phytomer_parameters.
internode.max_vegetative_buds_per_petiole.resample();
1943 axillary_vegetative_buds.push_back(vegetative_buds_new);
1945 std::vector<FloralBud> floral_buds_new;
1946 floral_buds_new.resize(phytomer_parameters.
internode.max_floral_buds_per_petiole.val());
1947 phytomer_parameters.
internode.max_floral_buds_per_petiole.resample();
1950 for (
auto &fbud: floral_buds_new) {
1951 fbud.bud_index = index;
1952 fbud.parent_index = petiole;
1953 float pitch_adjustment = fbud.bud_index * 0.1f *
PI_F / float(axillary_vegetative_buds.size());
1954 float yaw_adjustment = -0.25f *
PI_F + fbud.bud_index * 0.5f *
PI_F / float(axillary_vegetative_buds.size());
1955 fbud.base_rotation = make_AxisRotation(pitch_adjustment, yaw_adjustment, 0);
1956 fbud.base_position = phytomer_internode_vertices.back();
1957 fbud.bending_axis = shoot_bending_axis;
1961 floral_buds.push_back(floral_buds_new);
1965 if (phytomer_parameters.
leaf.prototype.prototype_function ==
nullptr) {
1966 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Leaf prototype function was not defined for shoot type " + parent_shoot->shoot_type_label +
".");
1972 assert(phytomer_parameters.
leaf.prototype.unique_prototype_identifier != 0);
1973 if (phytomer_parameters.
leaf.prototype.unique_prototypes > 0 &&
1974 plantarchitecture_ptr->unique_leaf_prototype_objIDs.find(phytomer_parameters.
leaf.prototype.unique_prototype_identifier) == plantarchitecture_ptr->unique_leaf_prototype_objIDs.end()) {
1975 plantarchitecture_ptr->unique_leaf_prototype_objIDs[phytomer_parameters.
leaf.prototype.unique_prototype_identifier].resize(phytomer_parameters.
leaf.prototype.unique_prototypes);
1976 for (
int prototype = 0; prototype < phytomer_parameters.
leaf.prototype.unique_prototypes; prototype++) {
1977 for (
int leaf = 0; leaf < leaves_per_petiole; leaf++) {
1978 float ind_from_tip = float(leaf) - float(leaves_per_petiole - 1) / 2.f;
1979 uint objID_leaf = phytomer_parameters.
leaf.prototype.prototype_function(context_ptr, &phytomer_parameters.
leaf.prototype, ind_from_tip);
1980 if (phytomer_parameters.
leaf.prototype.prototype_function == GenericLeafPrototype) {
1983 plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).push_back(objID_leaf);
1991 for (
int leaf = 0; leaf < leaves_per_petiole; leaf++) {
1992 float ind_from_tip = float(leaf) - float(leaves_per_petiole - 1) / 2.f;
1995 if (phytomer_parameters.
leaf.prototype.unique_prototypes > 0) {
1997 int prototype = context_ptr->
randu(0, phytomer_parameters.
leaf.prototype.unique_prototypes - 1);
1998 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.find(phytomer_parameters.
leaf.prototype.unique_prototype_identifier) != plantarchitecture_ptr->unique_leaf_prototype_objIDs.end());
1999 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).size() > prototype);
2000 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).size() > leaf);
2001 objID_leaf = context_ptr->
copyObject(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).at(leaf));
2004 objID_leaf = phytomer_parameters.
leaf.prototype.prototype_function(context_ptr, &phytomer_parameters.
leaf.prototype, ind_from_tip);
2009 if (leaves_per_petiole > 0 && phytomer_parameters.
leaf.leaflet_scale.val() != 1.f && ind_from_tip != 0) {
2010 leaf_size_max.at(petiole).at(leaf) = powf(phytomer_parameters.
leaf.leaflet_scale.val(), fabs(ind_from_tip)) * phytomer_parameters.
leaf.prototype_scale.val();
2012 leaf_size_max.at(petiole).at(leaf) = phytomer_parameters.
leaf.prototype_scale.val();
2014 vec3 leaf_scale = leaf_scale_factor_fraction * leaf_size_max.at(petiole).at(leaf) *
make_vec3(1, 1, 1);
2018 float compound_rotation = 0;
2019 if (leaves_per_petiole > 1) {
2020 if (leaflet_offset_val == 0) {
2021 float dphi =
PI_F / (floor(0.5 *
float(leaves_per_petiole - 1)) + 1);
2022 compound_rotation = -float(
PI_F) + dphi * (leaf + 0.5f);
2024 if (leaf ==
float(leaves_per_petiole - 1) / 2.f) {
2026 compound_rotation = 0;
2027 }
else if (leaf <
float(leaves_per_petiole - 1) / 2.f) {
2028 compound_rotation = -0.5 *
PI_F;
2030 compound_rotation = 0.5 *
PI_F;
2039 if (leaves_per_petiole == 1) {
2042 }
else if (ind_from_tip != 0) {
2043 roll_rot = (
asin_safe(petiole_tip_axis.
z) +
deg2rad(phytomer_parameters.
leaf.roll.val())) * compound_rotation / std::fabs(compound_rotation);
2045 leaf_rotation.at(petiole).at(leaf).roll =
deg2rad(phytomer_parameters.
leaf.roll.val());
2046 phytomer_parameters.
leaf.roll.resample();
2050 leaf_rotation.at(petiole).at(leaf).pitch =
deg2rad(phytomer_parameters.
leaf.pitch.val());
2051 float pitch_rot = leaf_rotation.at(petiole).at(leaf).pitch;
2052 phytomer_parameters.
leaf.pitch.resample();
2053 if (ind_from_tip == 0) {
2056 context_ptr->
rotateObject(objID_leaf, -pitch_rot,
"y");
2059 if (ind_from_tip != 0) {
2060 float sign = -compound_rotation / fabs(compound_rotation);
2061 leaf_rotation.at(petiole).at(leaf).yaw = sign *
deg2rad(phytomer_parameters.
leaf.yaw.val());
2062 float yaw_rot = leaf_rotation.at(petiole).at(leaf).yaw;
2063 phytomer_parameters.
leaf.yaw.resample();
2066 leaf_rotation.at(petiole).at(leaf).yaw = 0;
2070 context_ptr->
rotateObject(objID_leaf, -std::atan2(petiole_tip_axis.
y, petiole_tip_axis.
x) + compound_rotation,
"z");
2076 if (leaves_per_petiole > 1 && leaflet_offset_val > 0) {
2077 if (ind_from_tip != 0) {
2078 float offset = (fabs(ind_from_tip) - 0.5f) * leaflet_offset_val * phytomer_parameters.
petiole.length.val();
2079 leaf_base = PlantArchitecture::interpolateTube(
petiole_vertices.at(petiole), 1.f - offset / phytomer_parameters.
petiole.length.val());
2085 leaf_objIDs.at(petiole).push_back(objID_leaf);
2086 leaf_bases.at(petiole).push_back(leaf_base);
2088 phytomer_parameters.
leaf.prototype_scale.resample();
2090 inflorescence_bending_axis =
cross(parent_internode_axis, petiole_axis_actual);
2091 if (inflorescence_bending_axis ==
make_vec3(0, 0, 0)) {
2092 inflorescence_bending_axis =
make_vec3(1, 0, 0);
2097 if (phytomer_parameters.
petiole.petioles_per_internode == 0) {
2098 std::vector<VegetativeBud> vegetative_buds_new;
2099 vegetative_buds_new.resize(phytomer_parameters.
internode.max_vegetative_buds_per_petiole.val());
2100 phytomer_parameters.
internode.max_vegetative_buds_per_petiole.resample();
2101 axillary_vegetative_buds.push_back(vegetative_buds_new);
2103 std::vector<FloralBud> floral_buds_new;
2104 floral_buds_new.resize(phytomer_parameters.
internode.max_floral_buds_per_petiole.val());
2105 phytomer_parameters.
internode.max_floral_buds_per_petiole.resample();
2106 floral_buds.push_back(floral_buds_new);
2112 const auto &segment = parent_shoot_ptr->shoot_internode_radii.at(node_number);
2115 float avg_radius = 0.0f;
2116 for (
float radius: segment) {
2117 avg_radius += radius;
2119 avg_radius /= scast<float>(segment.size());
2125 float volume =
PI_F * avg_radius * avg_radius * length;
2130void Phytomer::createInflorescenceGeometry(
FloralBud &fbud,
const helios::vec3 &fruit_base,
const helios::vec3 &peduncle_axis,
float pitch,
float roll,
float azimuth,
float yaw_compound,
float scale_factor,
bool is_open_flower) {
2134 if (fbud.state == BUD_FRUITING) {
2135 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2137 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2138 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_fruit_prototype_objIDs.at(phytomer_parameters.
inflorescence.fruit_prototype_function).at(prototype));
2141 objID_fruit = phytomer_parameters.
inflorescence.fruit_prototype_function(context_ptr, 1);
2145 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2147 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2148 if (is_open_flower) {
2149 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_open_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype));
2151 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_closed_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype));
2155 objID_fruit = phytomer_parameters.
inflorescence.flower_prototype_function(context_ptr, 1, is_open_flower);
2161 context_ptr->
scaleObject(objID_fruit, fruit_scale);
2164 if (std::abs(roll) > 1e-6) {
2167 if (std::abs(pitch) > 1e-6) {
2170 if (std::abs(azimuth) > 1e-6) {
2178 if (std::abs(yaw_compound) > 1e-6) {
2179 context_ptr->
rotateObject(objID_fruit, yaw_compound, fruit_base, peduncle_axis);
2183 fbud.inflorescence_objIDs.push_back(objID_fruit);
2184 fbud.inflorescence_bases.push_back(fruit_base);
2187 flower_rotation.pitch = pitch;
2188 flower_rotation.yaw = yaw_compound;
2189 flower_rotation.roll = roll;
2190 flower_rotation.azimuth = azimuth;
2191 flower_rotation.peduncle_axis = peduncle_axis;
2192 fbud.inflorescence_rotation.push_back(flower_rotation);
2194 fbud.inflorescence_base_scales.push_back(scale_factor);
2196 assert(fbud.inflorescence_objIDs.size() == fbud.inflorescence_bases.size());
2197 assert(fbud.inflorescence_bases.size() == fbud.inflorescence_rotation.size());
2198 assert(fbud.inflorescence_rotation.size() == fbud.inflorescence_base_scales.size());
2201void PlantArchitecture::ensureInflorescencePrototypesInitialized(
const PhytomerParameters ¶ms) {
2204 if (params.
inflorescence.flower_prototype_function !=
nullptr && unique_closed_flower_prototype_objIDs.find(params.
inflorescence.flower_prototype_function) == unique_closed_flower_prototype_objIDs.end()) {
2205 unique_closed_flower_prototype_objIDs[params.
inflorescence.flower_prototype_function].resize(params.
inflorescence.unique_prototypes);
2206 for (
int prototype = 0; prototype < params.
inflorescence.unique_prototypes; prototype++) {
2207 uint objID_flower = params.
inflorescence.flower_prototype_function(context_ptr, 1,
false);
2208 unique_closed_flower_prototype_objIDs.at(params.
inflorescence.flower_prototype_function).at(prototype) = objID_flower;
2213 if (params.
inflorescence.flower_prototype_function !=
nullptr && unique_open_flower_prototype_objIDs.find(params.
inflorescence.flower_prototype_function) == unique_open_flower_prototype_objIDs.end()) {
2214 unique_open_flower_prototype_objIDs[params.
inflorescence.flower_prototype_function].resize(params.
inflorescence.unique_prototypes);
2215 for (
int prototype = 0; prototype < params.
inflorescence.unique_prototypes; prototype++) {
2216 uint objID_flower = params.
inflorescence.flower_prototype_function(context_ptr, 1,
true);
2217 unique_open_flower_prototype_objIDs.at(params.
inflorescence.flower_prototype_function).at(prototype) = objID_flower;
2222 if (params.
inflorescence.fruit_prototype_function !=
nullptr && unique_fruit_prototype_objIDs.find(params.
inflorescence.fruit_prototype_function) == unique_fruit_prototype_objIDs.end()) {
2224 for (
int prototype = 0; prototype < params.
inflorescence.unique_prototypes; prototype++) {
2225 uint objID_fruit = params.
inflorescence.fruit_prototype_function(context_ptr, 1);
2226 unique_fruit_prototype_objIDs.at(params.
inflorescence.fruit_prototype_function).at(prototype) = objID_fruit;
2233void Phytomer::updateInflorescence(
FloralBud &fbud) {
2234 bool build_context_geometry_peduncle = plantarchitecture_ptr->build_context_geometry_peduncle;
2236 uint Ndiv_peduncle_length = std::max(
uint(1), phytomer_parameters.
peduncle.length_segments);
2237 uint Ndiv_peduncle_radius = std::max(
uint(3), phytomer_parameters.
peduncle.radial_subdivisions);
2238 if (phytomer_parameters.
peduncle.length_segments == 0 || phytomer_parameters.
peduncle.radial_subdivisions < 3) {
2239 build_context_geometry_peduncle =
false;
2243 float peduncle_length = phytomer_parameters.
peduncle.length.val();
2244 float dr_peduncle = peduncle_length / float(Ndiv_peduncle_length);
2246 std::vector<vec3> peduncle_vertices(phytomer_parameters.
peduncle.length_segments + 1);
2247 peduncle_vertices.at(0) = fbud.base_position;
2248 std::vector<float> peduncle_radii(phytomer_parameters.
peduncle.length_segments + 1);
2249 peduncle_radii.at(0) = phytomer_parameters.
peduncle.radius.val();
2250 std::vector<RGBcolor> peduncle_colors(phytomer_parameters.
peduncle.length_segments + 1);
2251 peduncle_colors.at(0) = phytomer_parameters.
peduncle.color;
2256 vec3 inflorescence_bending_axis_actual = inflorescence_bending_axis;
2259 if (phytomer_parameters.
peduncle.pitch.val() != 0.f || fbud.base_rotation.pitch != 0.f) {
2260 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, inflorescence_bending_axis_actual,
deg2rad(phytomer_parameters.
peduncle.pitch.val()) + fbud.base_rotation.pitch);
2265 vec3 parent_petiole_base_axis;
2268 parent_petiole_base_axis = internode_axis;
2272 float parent_petiole_azimuth = -std::atan2(parent_petiole_base_axis.
y, parent_petiole_base_axis.
x);
2273 float current_peduncle_azimuth = -std::atan2(peduncle_axis.
y, peduncle_axis.
x);
2274 float azimuthal_rotation = current_peduncle_azimuth - parent_petiole_azimuth;
2275 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, internode_axis, azimuthal_rotation);
2277 inflorescence_bending_axis_actual =
rotatePointAboutLine(inflorescence_bending_axis_actual, nullorigin, internode_axis, azimuthal_rotation);
2280 float theta_base = fabs(
cart2sphere(peduncle_axis).zenith);
2283 vec3 collision_optimal_peduncle_direction;
2284 bool peduncle_collision_active =
false;
2286 if (plantarchitecture_ptr->fruit_collision_detection_enabled) {
2287 collision_optimal_peduncle_direction = calculateFruitCollisionAvoidanceDirection(fbud.base_position, peduncle_axis, peduncle_collision_active);
2290 if (peduncle_collision_active) {
2291 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
2292 vec3 natural_peduncle_direction = peduncle_axis;
2297 peduncle_axis = inertia_weight * natural_peduncle_direction + (1.0f - inertia_weight) * collision_optimal_peduncle_direction;
2302 float peduncle_curvature = phytomer_parameters.
peduncle.curvature.val();
2303 phytomer_parameters.
peduncle.curvature.resample();
2306 uint petiole_idx = fbud.parent_index;
2307 uint bud_idx = fbud.bud_index;
2308 if (petiole_idx < this->peduncle_length.size()) {
2309 if (this->peduncle_length.at(petiole_idx).size() <= bud_idx) {
2310 this->peduncle_length.at(petiole_idx).resize(bud_idx + 1);
2311 this->peduncle_radius.at(petiole_idx).resize(bud_idx + 1);
2312 this->peduncle_pitch.at(petiole_idx).resize(bud_idx + 1);
2313 this->peduncle_curvature.at(petiole_idx).resize(bud_idx + 1);
2315 this->peduncle_length.at(petiole_idx).at(bud_idx) = peduncle_length;
2316 this->peduncle_radius.at(petiole_idx).at(bud_idx) = phytomer_parameters.
peduncle.radius.val();
2317 this->peduncle_pitch.at(petiole_idx).at(bud_idx) = phytomer_parameters.
peduncle.pitch.val();
2318 this->peduncle_curvature.at(petiole_idx).at(bud_idx) = peduncle_curvature;
2321 for (
int i = 1; i <= phytomer_parameters.
peduncle.length_segments; i++) {
2322 if (peduncle_curvature != 0.f) {
2323 float curvature_value = peduncle_curvature;
2328 float axis_magnitude = horizontal_bending_axis.
magnitude();
2331 if (axis_magnitude > 0.001f) {
2332 horizontal_bending_axis = horizontal_bending_axis / axis_magnitude;
2335 float theta_curvature =
deg2rad(curvature_value * dr_peduncle);
2336 float theta_from_target;
2338 if (curvature_value > 0) {
2341 theta_from_target = std::acos(std::min(1.0f, std::max(-1.0f, peduncle_axis.
z)));
2345 theta_from_target = std::acos(std::min(1.0f, std::max(-1.0f, -peduncle_axis.
z)));
2349 if (fabs(theta_curvature) >= theta_from_target) {
2351 if (curvature_value > 0) {
2358 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, horizontal_bending_axis, theta_curvature);
2363 if (curvature_value > 0) {
2371 peduncle_vertices.at(i) = peduncle_vertices.at(i - 1) + dr_peduncle * peduncle_axis;
2373 peduncle_radii.at(i) = phytomer_parameters.
peduncle.radius.val();
2374 peduncle_colors.at(i) = phytomer_parameters.
peduncle.color;
2377 if (build_context_geometry_peduncle) {
2378 fbud.peduncle_objIDs.push_back(context_ptr->
addTubeObject(Ndiv_peduncle_radius, peduncle_vertices, peduncle_radii, peduncle_colors));
2386 if (petiole_idx < this->peduncle_vertices.size()) {
2387 if (this->peduncle_vertices.at(petiole_idx).size() <= fbud.bud_index) {
2388 this->peduncle_vertices.at(petiole_idx).resize(fbud.bud_index + 1);
2390 this->peduncle_vertices.at(petiole_idx).at(fbud.bud_index) = peduncle_vertices;
2394 if (petiole_idx < this->peduncle_radii.size()) {
2395 if (this->peduncle_radii.at(petiole_idx).size() <= fbud.bud_index) {
2396 this->peduncle_radii.at(petiole_idx).resize(fbud.bud_index + 1);
2398 this->peduncle_radii.at(petiole_idx).at(fbud.bud_index) = peduncle_radii;
2402 phytomer_parameters.
peduncle.length.resample();
2403 phytomer_parameters.
peduncle.radius.resample();
2404 phytomer_parameters.
peduncle.pitch.resample();
2407 plantarchitecture_ptr->ensureInflorescencePrototypesInitialized(phytomer_parameters);
2409 int flowers_per_peduncle = phytomer_parameters.
inflorescence.flowers_per_peduncle.val();
2410 float flower_offset_val = clampOffset(flowers_per_peduncle, phytomer_parameters.
inflorescence.flower_offset.val());
2411 for (
int fruit = 0; fruit < flowers_per_peduncle; fruit++) {
2414 if (fbud.state == BUD_FRUITING) {
2415 scale_factor = phytomer_parameters.
inflorescence.fruit_prototype_scale.val();
2416 phytomer_parameters.
inflorescence.fruit_prototype_scale.resample();
2418 scale_factor = phytomer_parameters.
inflorescence.flower_prototype_scale.val();
2419 phytomer_parameters.
inflorescence.flower_prototype_scale.resample();
2422 float ind_from_tip = fabs(fruit -
float(flowers_per_peduncle - 1) /
float(phytomer_parameters.
petiole.petioles_per_internode));
2425 vec3 fruit_base = peduncle_vertices.back();
2427 if (flowers_per_peduncle > 1 && flower_offset_val > 0) {
2428 if (ind_from_tip != 0) {
2429 float offset = (ind_from_tip - 0.5f) * flower_offset_val * phytomer_parameters.
peduncle.length.val();
2430 if (phytomer_parameters.
peduncle.length.val() > 0) {
2431 frac = 1.f - offset / phytomer_parameters.
peduncle.length.val();
2433 fruit_base = PlantArchitecture::interpolateTube(peduncle_vertices, frac);
2438 float compound_rotation = 0;
2439 if (flowers_per_peduncle > 1) {
2440 if (flower_offset_val == 0) {
2442 float dphi =
PI_F / (floor(0.5 *
float(flowers_per_peduncle - 1)) + 1);
2443 compound_rotation = -float(
PI_F) + dphi * (fruit + 0.5f);
2445 compound_rotation =
deg2rad(phytomer_parameters.
internode.phyllotactic_angle.val()) * float(ind_from_tip) + 2.f *
PI_F / float(phytomer_parameters.
petiole.petioles_per_internode) * float(fruit);
2446 phytomer_parameters.
internode.phyllotactic_angle.resample();
2460 float pitch_inflorescence = -
asin_safe(peduncle_axis.
z) + applied_pitch_param;
2461 if (fbud.state == BUD_FRUITING) {
2463 pitch_inflorescence = pitch_inflorescence + phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.val() * (0.5f *
PI_F - pitch_inflorescence);
2465 phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.resample();
2468 float azimuth = -std::atan2(peduncle_axis.
y, peduncle_axis.
x);
2471 float yaw_compound =
deg2rad(phytomer_parameters.
peduncle.roll.val()) + compound_rotation;
2474 bool is_open_flower = (fbud.state == BUD_FLOWER_OPEN);
2477 createInflorescenceGeometry(fbud, fruit_base, peduncle_axis, pitch_inflorescence, applied_roll, azimuth, yaw_compound, scale_factor, is_open_flower);
2479 phytomer_parameters.
inflorescence.flowers_per_peduncle.resample();
2480 phytomer_parameters.
peduncle.roll.resample();
2482 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
2483 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"rank", rank);
2484 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"rank", rank);
2487 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
2488 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
2489 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
2492 if (plantarchitecture_ptr->output_object_data.at(
"peduncleID")) {
2493 for (
uint objID: fbud.peduncle_objIDs) {
2494 context_ptr->
setObjectData(objID,
"peduncleID", (
int) objID);
2497 for (
uint objID: fbud.inflorescence_objIDs) {
2498 if (fbud.state == BUD_FLOWER_CLOSED && plantarchitecture_ptr->output_object_data.at(
"closedflowerID")) {
2499 context_ptr->
setObjectData(objID,
"closedflowerID", (
int) objID);
2500 }
else if (fbud.state == BUD_FLOWER_OPEN && plantarchitecture_ptr->output_object_data.at(
"openflowerID")) {
2502 context_ptr->
setObjectData(objID,
"openflowerID", (
int) objID);
2503 }
else if (plantarchitecture_ptr->output_object_data.at(
"fruitID")) {
2516 vec3 shift = base_position - old_base;
2519 for (
auto &vertex: petiole_vertice) {
2524 if (build_context_geometry_petiole) {
2529 for (
auto &petiole: leaf_bases) {
2530 for (
auto &leaf_base: petiole) {
2535 for (
auto &petiole_peduncles: peduncle_vertices) {
2536 for (
auto &bud_peduncle_vertices: petiole_peduncles) {
2537 for (
auto &vertex: bud_peduncle_vertices) {
2543 for (
auto &floral_bud: floral_buds) {
2544 for (
auto &fbud: floral_bud) {
2547 for (
auto &base: fbud.inflorescence_bases) {
2550 if (build_context_geometry_peduncle) {
2558 if (petiole_index >= leaf_objIDs.size()) {
2560 }
else if (leaf_index >= leaf_objIDs.at(petiole_index).size()) {
2569 vec3 pitch_axis = -1 *
cross(internode_axis, petiole_axis);
2571 int leaves_per_petiole = leaf_rotation.at(petiole_index).size();
2574 float compound_rotation = 0;
2575 if (leaves_per_petiole > 1 && leaf_index ==
float(leaves_per_petiole - 1) / 2.f) {
2579 compound_rotation = 0;
2580 }
else if (leaves_per_petiole > 1 && leaf_index <
float(leaves_per_petiole - 1) / 2.f) {
2582 yaw = -rotation.yaw;
2583 roll = -rotation.roll;
2584 compound_rotation = -0.5 *
PI_F;
2587 yaw = -rotation.yaw;
2588 roll = rotation.roll;
2589 compound_rotation = 0;
2595 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), roll, leaf_bases.at(petiole_index).at(leaf_index), roll_axis);
2596 leaf_rotation.at(petiole_index).at(leaf_index).roll += roll;
2600 if (rotation.pitch != 0) {
2602 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), rotation.pitch, leaf_bases.at(petiole_index).at(leaf_index), pitch_axis);
2603 leaf_rotation.at(petiole_index).at(leaf_index).pitch += rotation.pitch;
2608 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), yaw, leaf_bases.at(petiole_index).at(leaf_index), {0, 0, 1});
2609 leaf_rotation.at(petiole_index).at(leaf_index).yaw += yaw;
2614 assert(internode_scale_factor_fraction >= 0 && internode_scale_factor_fraction <= 1);
2616 if (internode_scale_factor_fraction == current_internode_scale_factor) {
2620 float delta_scale = internode_scale_factor_fraction / current_internode_scale_factor;
2622 current_internode_scale_factor = internode_scale_factor_fraction;
2625 int s_start = (p == 0) ? 1 : 0;
2627 for (
int s = s_start; s < parent_shoot_ptr->shoot_internode_vertices.at(p).size(); s++) {
2631 int s_minus = s - 1;
2634 s_minus =
static_cast<int>(parent_shoot_ptr->shoot_internode_vertices.at(p_minus).size() - 1);
2637 vec3 central_axis = (parent_shoot_ptr->shoot_internode_vertices.at(p).at(s) - parent_shoot_ptr->shoot_internode_vertices.at(p_minus).at(s_minus));
2638 float current_length = central_axis.
magnitude();
2639 central_axis = central_axis / current_length;
2640 vec3 dL = central_axis * current_length * (delta_scale - 1);
2643 for (
int p_downstream = p; p_downstream < parent_shoot_ptr->shoot_internode_vertices.size(); p_downstream++) {
2644 int sd_start = (p_downstream == p) ? s : 0;
2645 for (
int s_downstream = sd_start; s_downstream < parent_shoot_ptr->shoot_internode_vertices.at(p_downstream).size(); s_downstream++) {
2646 parent_shoot_ptr->shoot_internode_vertices.at(p_downstream).at(s_downstream) += dL;
2655 this->internode_length_max *= scale_factor;
2657 current_internode_scale_factor = current_internode_scale_factor / scale_factor;
2659 if (current_internode_scale_factor >= 1.f) {
2661 current_internode_scale_factor = 1.f;
2666 float scale_factor = internode_length_max_new / this->internode_length_max;
2671 this->internode_radius_max = internode_radius_max_new;
2676 assert(leaf_scale_factor_fraction >= 0 && leaf_scale_factor_fraction <= 1);
2678 if (current_leaf_scale_factor.size() <= petiole_index) {
2679 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf scale factor.");
2683 if (leaf_scale_factor_fraction == current_leaf_scale_factor.at(petiole_index) || (leaf_objIDs.at(petiole_index).empty() && petiole_objIDs.at(petiole_index).empty())) {
2687 float delta_scale = leaf_scale_factor_fraction / current_leaf_scale_factor.at(petiole_index);
2689 petiole_length.at(petiole_index) *= delta_scale;
2691 current_leaf_scale_factor.at(petiole_index) = leaf_scale_factor_fraction;
2693 assert(leaf_objIDs.size() == leaf_bases.size());
2697 if (!petiole_objIDs.at(petiole_index).empty()) {
2700 for (
uint objID: petiole_objIDs.at(petiole_index)) {
2704 petiole_radii.at(petiole_index).at(node) *= delta_scale;
2715 }
else if (build_context_geometry_petiole) {
2718 for (
uint node = 0; node < petiole_radii.at(petiole_index).size(); node++) {
2719 petiole_radii.at(petiole_index).at(node) *= delta_scale;
2727 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
2728 petiole_objIDs.at(petiole_index) =
makeTubeFromCones(Ndiv_petiole_radius,
petiole_vertices.at(petiole_index), petiole_radii.at(petiole_index), petiole_colors, context_ptr);
2729 if (!petiole_objIDs.at(petiole_index).empty()) {
2735 assert(leaf_objIDs.at(petiole_index).size() == leaf_bases.at(petiole_index).size());
2736 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2737 float ind_from_tip = float(leaf) - float(leaf_objIDs.at(petiole_index).size() - 1) / 2.f;
2739 float leaflet_offset_val = clampOffset(
int(leaf_objIDs.at(petiole_index).size()), phytomer_parameters.
leaf.leaflet_offset.val());
2741 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), -1 * leaf_bases.at(petiole_index).at(leaf));
2742 context_ptr->
scaleObject(leaf_objIDs.at(petiole_index).at(leaf), delta_scale *
make_vec3(1, 1, 1));
2743 if (ind_from_tip == 0) {
2745 leaf_bases.at(petiole_index).at(leaf) =
petiole_vertices.at(petiole_index).back();
2747 float offset = (fabs(ind_from_tip) - 0.5f) * leaflet_offset_val * phytomer_parameters.
petiole.length.val();
2748 vec3 leaf_base = PlantArchitecture::interpolateTube(
petiole_vertices.at(petiole_index), 1.f - offset / phytomer_parameters.
petiole.length.val());
2749 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), leaf_base);
2750 leaf_bases.at(petiole_index).at(leaf) = leaf_base;
2756 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2762 if (leaf_objIDs.size() <= petiole_index) {
2763 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf prototype scale.");
2765 if (leaf_prototype_scale < 0.f) {
2766 leaf_prototype_scale = 0;
2769 float tip_ind = ceil(scast<float>(leaf_size_max.at(petiole_index).size() - 1) / 2.f);
2770 float scale_factor = leaf_prototype_scale / leaf_size_max.at(petiole_index).at(tip_ind);
2771 current_leaf_scale_factor.at(petiole_index) *= scale_factor;
2773 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2774 leaf_size_max.at(petiole_index).at(leaf) *= scale_factor;
2775 context_ptr->
scaleObjectAboutPoint(leaf_objIDs.at(petiole_index).at(leaf), scale_factor *
make_vec3(1, 1, 1), leaf_bases.at(petiole_index).at(leaf));
2779 this->petiole_curvature.at(petiole_index) /= scale_factor;
2781 if (current_leaf_scale_factor.at(petiole_index) >= 1.f) {
2783 current_leaf_scale_factor.at(petiole_index) = 1.f;
2788 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2794 if (leaf_objIDs.size() <= petiole_index) {
2795 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf prototype scale.");
2797 if (scale_factor < 0.f) {
2801 current_leaf_scale_factor.at(petiole_index) /= scale_factor;
2803 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2804 leaf_size_max.at(petiole_index).at(leaf) *= scale_factor;
2805 context_ptr->
scaleObjectAboutPoint(leaf_objIDs.at(petiole_index).at(leaf), scale_factor *
make_vec3(1, 1, 1), leaf_bases.at(petiole_index).at(leaf));
2809 this->petiole_curvature.at(petiole_index) /= scale_factor;
2811 if (current_leaf_scale_factor.at(petiole_index) >= 1.f) {
2813 current_leaf_scale_factor.at(petiole_index) = 1.f;
2818 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2824 if (petiole_index >= petiole_length.size()) {
2825 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer::scalePetioleGeometry): Invalid petiole index " + std::to_string(petiole_index) +
".");
2827 if (target_length <= 0.f || target_base_radius <= 0.f) {
2828 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer::scalePetioleGeometry): Target length and radius must be positive.");
2832 float current_length = petiole_length.at(petiole_index);
2833 float current_base_radius = petiole_radii.at(petiole_index).at(0);
2835 if (current_length <= 0.f || current_base_radius <= 0.f) {
2837 petiole_length.at(petiole_index) = target_length;
2838 if (!petiole_radii.at(petiole_index).empty()) {
2839 petiole_radii.at(petiole_index).at(0) = target_base_radius;
2844 float length_scale = target_length / current_length;
2845 float radius_scale = target_base_radius / current_base_radius;
2853 petiole_vertices.at(petiole_index).at(j) = petiole_base + offset * length_scale;
2857 for (
size_t j = 0; j < petiole_radii.at(petiole_index).size(); j++) {
2858 petiole_radii.at(petiole_index).at(j) *= radius_scale;
2862 petiole_length.at(petiole_index) = target_length;
2865 if (!petiole_objIDs.at(petiole_index).empty()) {
2867 context_ptr->
deleteObject(petiole_objIDs.at(petiole_index));
2870 std::vector<RGBcolor> petiole_colors(petiole_radii.at(petiole_index).size(), phytomer_parameters.
petiole.color);
2871 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
2873 petiole_objIDs.at(petiole_index) =
makeTubeFromCones(Ndiv_petiole_radius,
petiole_vertices.at(petiole_index), petiole_radii.at(petiole_index), petiole_colors, context_ptr);
2876 if (!petiole_objIDs.at(petiole_index).empty()) {
2882 if (petiole_index < leaf_bases.size()) {
2883 for (
size_t leaf = 0; leaf < leaf_bases.at(petiole_index).size(); leaf++) {
2884 vec3 offset = leaf_bases.at(petiole_index).at(leaf) - petiole_base;
2885 leaf_bases.at(petiole_index).at(leaf) = petiole_base + offset * length_scale;
2888 if (petiole_index < leaf_objIDs.size() && leaf < leaf_objIDs.at(petiole_index).size()) {
2889 vec3 translation = offset * length_scale - offset;
2890 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), translation);
2896 if (petiole_index < floral_buds.size()) {
2897 for (
auto &fbud: floral_buds.at(petiole_index)) {
2898 vec3 offset = fbud.base_position - petiole_base;
2899 vec3 translation = offset * length_scale - offset;
2900 fbud.base_position = petiole_base + offset * length_scale;
2903 for (
size_t i = 0; i < fbud.inflorescence_bases.size(); i++) {
2904 fbud.inflorescence_bases.at(i) += translation;
2908 for (
size_t i = 0; i < fbud.inflorescence_objIDs.size(); i++) {
2909 context_ptr->
translateObject(fbud.inflorescence_objIDs.at(i), translation);
2911 for (
size_t i = 0; i < fbud.peduncle_objIDs.size(); i++) {
2912 context_ptr->
translateObject(fbud.peduncle_objIDs.at(i), translation);
2919 assert(inflorescence_scale_factor_fraction >= 0 && inflorescence_scale_factor_fraction <= 1);
2921 if (inflorescence_scale_factor_fraction == fbud.current_fruit_scale_factor) {
2925 float delta_scale = inflorescence_scale_factor_fraction / fbud.current_fruit_scale_factor;
2927 fbud.current_fruit_scale_factor = inflorescence_scale_factor_fraction;
2930 for (
int inflorescence = 0; inflorescence < fbud.inflorescence_objIDs.size(); inflorescence++) {
2931 context_ptr->
scaleObjectAboutPoint(fbud.inflorescence_objIDs.at(inflorescence), delta_scale *
make_vec3(1, 1, 1), fbud.inflorescence_bases.at(inflorescence));
2938 this->petiole_radii.resize(0);
2940 this->petiole_colors.resize(0);
2941 this->petiole_length.resize(0);
2942 this->leaf_size_max.resize(0);
2943 this->leaf_rotation.resize(0);
2944 this->leaf_bases.resize(0);
2947 leaf_objIDs.clear();
2950 if (build_context_geometry_petiole) {
2952 petiole_objIDs.resize(0);
2958 if (context_ptr->
doesObjectExist(parent_shoot_ptr->internode_tube_objID)) {
2961 uint tube_prune_index;
2963 tube_prune_index = 0;
2965 tube_prune_index = this->
shoot_index.
x * tube_segments + 1;
2967 if (tube_prune_index < tube_nodes) {
2968 context_ptr->
pruneTubeNodes(parent_shoot_ptr->internode_tube_objID, tube_prune_index);
2974 auto &phytomer = parent_shoot_ptr->phytomers.at(node);
2977 phytomer->removeLeaf();
2980 for (
auto &petiole: phytomer->floral_buds) {
2981 for (
auto &fbud: petiole) {
2982 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
2983 uint objID = fbud.inflorescence_objIDs.at(p);
2985 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
2986 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
2988 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
2991 fbud.peduncle_objIDs.clear();
2992 fbud.inflorescence_objIDs.clear();
2993 fbud.inflorescence_bases.clear();
3000 if (parent_shoot_ptr->childIDs.find(node) != parent_shoot_ptr->childIDs.end()) {
3001 for (
auto childID: parent_shoot_ptr->childIDs.at(node)) {
3002 auto child_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(childID);
3003 if (!child_shoot->phytomers.empty()) {
3004 child_shoot->phytomers.front()->deletePhytomer();
3011 parent_shoot_ptr->shoot_internode_radii.resize(this->
shoot_index.
x);
3012 parent_shoot_ptr->shoot_internode_vertices.resize(this->
shoot_index.
x);
3013 parent_shoot_ptr->phytomers.resize(this->
shoot_index.
x);
3016 for (
const auto &phytomer: parent_shoot_ptr->phytomers) {
3017 phytomer->shoot_index.y = scast<int>(parent_shoot_ptr->phytomers.size());
3019 parent_shoot_ptr->current_node_number = scast<int>(parent_shoot_ptr->phytomers.size());
3023 return (!leaf_bases.empty() && !leaf_bases.front().empty());
3032 current_node_number(current_node_number), base_position(shoot_base_position), base_rotation(shoot_base_rotation), ID(shoot_ID), parent_shoot_ID(parent_shoot_ID), plantID(plant_ID), parent_node_index(parent_node), rank(rank),
3033 parent_petiole_index(parent_petiole_index), internode_length_max_shoot_initial(internode_length_shoot_initial), shoot_parameters(shoot_params), shoot_type_label(std::move(shoot_type_label)), plantarchitecture_ptr(plant_architecture_ptr) {
3034 carbohydrate_pool_molC = 0;
3035 phyllochron_counter = 0;
3038 context_ptr = plant_architecture_ptr->context_ptr;
3042 if (parent_shoot_ID >= 0) {
3043 plant_architecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->childIDs[(int) parent_node_index].push_back(shoot_ID);
3047void Shoot::buildShootPhytomers(
float internode_radius,
float internode_length,
float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper) {
3048 for (
int i = 0; i < current_node_number; i++) {
3052 if (current_node_number > 1) {
3053 taper = 1.f - radius_taper * float(i) / float(current_node_number - 1);
3057 appendPhytomer(internode_radius * taper, internode_length, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, shoot_parameters.
phytomer_parameters);
3062 auto shoot_ptr =
this;
3064 assert(shoot_ptr->shoot_parameters.child_shoot_type_labels.size() == shoot_ptr->shoot_parameters.child_shoot_type_probabilities.size());
3066 std::string child_shoot_type_label;
3068 if (shoot_ptr->shoot_parameters.child_shoot_type_labels.empty()) {
3070 child_shoot_type_label = shoot_ptr->shoot_type_label;
3071 }
else if (shoot_ptr->shoot_parameters.child_shoot_type_labels.size() == 1) {
3073 child_shoot_type_label = shoot_ptr->shoot_parameters.child_shoot_type_labels.at(0);
3075 float randf = context_ptr->
randu();
3076 int shoot_type_index = -1;
3077 float cumulative_probability = 0;
3078 for (
int s = 0; s < shoot_ptr->shoot_parameters.child_shoot_type_labels.size(); s++) {
3079 cumulative_probability += shoot_ptr->shoot_parameters.child_shoot_type_probabilities.at(s);
3080 if (randf < cumulative_probability) {
3081 shoot_type_index = s;
3085 if (shoot_type_index < 0) {
3086 shoot_type_index = shoot_ptr->shoot_parameters.child_shoot_type_labels.size() - 1;
3088 child_shoot_type_label = shoot_ptr->shoot_type_label;
3089 if (shoot_type_index >= 0) {
3090 child_shoot_type_label = shoot_ptr->shoot_parameters.child_shoot_type_labels.at(shoot_type_index);
3094 return child_shoot_type_label;
3098 if (node_index >= phytomers.size()) {
3099 helios_runtime_error(
"ERROR (PlantArchitecture::sampleVegetativeBudBreak): Invalid node index. Node index must be less than the number of phytomers on the shoot.");
3102 float probability_min = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_min.val();
3103 float probability_max = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_max.val();
3104 float probability_decay = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_decay_rate.val();
3106 float bud_break_probability;
3108 bud_break_probability = probability_min;
3109 }
else if (probability_decay > 0) {
3111 bud_break_probability = std::fmax(probability_min, probability_max - probability_decay *
float(this->current_node_number - node_index - 1));
3112 }
else if (probability_decay < 0) {
3114 bud_break_probability = std::fmax(probability_min, probability_max - fabs(probability_decay) *
float(node_index));
3116 if (probability_decay == 0.f) {
3117 bud_break_probability = probability_min;
3119 bud_break_probability = probability_max;
3123 bool bud_break =
true;
3124 if (context_ptr->
randu() > bud_break_probability) {
3132 std::string epicormic_shoot_label = plantarchitecture_ptr->plant_instances.at(this->plantID).epicormic_shoot_probability_perlength_per_day.first;
3134 if (epicormic_shoot_label.empty()) {
3138 float epicormic_probability = plantarchitecture_ptr->plant_instances.at(this->plantID).epicormic_shoot_probability_perlength_per_day.second;
3140 if (epicormic_probability == 0) {
3146 epicormic_positions_fraction.clear();
3152 float dta = std::min(time, 1.f);
3154 float shoot_fraction = context_ptr->
randu();
3158 bool new_shoot =
uint((epicormic_probability * shoot_length * dta * elevation > context_ptr->
randu()));
3160 Nshoots +=
uint(new_shoot);
3163 epicormic_positions_fraction.push_back(shoot_fraction);
3169 assert(epicormic_positions_fraction.size() == Nshoots);
3175 float radius_taper,
const std::string &shoot_type_label) {
3176 if (plant_instances.find(plantID) == plant_instances.end()) {
3177 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3178 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3179 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3182 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3184 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3185 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3187 if (current_node_number > shoot_parameters.max_nodes.val()) {
3188 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Cannot add shoot with " + std::to_string(current_node_number) +
" nodes since the specified max node number is " + std::to_string(shoot_parameters.max_nodes.val()) +
".");
3191 uint shootID = shoot_tree_ptr->size();
3192 vec3 base_position = plant_instances.at(plantID).base_position;
3195 auto *shoot_new = (
new Shoot(plantID, shootID, -1, 0, 0, 0, base_position, base_rotation, current_node_number, internode_length_max, shoot_parameters, shoot_type_label,
this));
3196 shoot_tree_ptr->emplace_back(shoot_new);
3199 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3205 float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label) {
3206 if (plant_instances.find(plantID) == plant_instances.end()) {
3207 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3208 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3209 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3212 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3214 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3215 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3217 if (shoot_tree_ptr->empty()) {
3218 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Cannot append shoot to empty shoot. You must call addBaseStemShoot() first for each plant.");
3219 }
else if (parent_shoot_ID >=
int(shoot_tree_ptr->size())) {
3220 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Parent with ID of " + std::to_string(parent_shoot_ID) +
" does not exist.");
3221 }
else if (current_node_number > shoot_parameters.max_nodes.val()) {
3222 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Cannot add shoot with " + std::to_string(current_node_number) +
" nodes since the specified max node number is " + std::to_string(shoot_parameters.max_nodes.val()) +
".");
3223 }
else if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.empty()) {
3227 shoot_tree_ptr->at(parent_shoot_ID)->shoot_parameters.max_nodes = shoot_tree_ptr->at(parent_shoot_ID)->current_node_number;
3228 shoot_tree_ptr->at(parent_shoot_ID)->terminateApicalBud();
3231 int appended_shootID = int(shoot_tree_ptr->size());
3232 uint parent_node = shoot_tree_ptr->at(parent_shoot_ID)->current_node_number - 1;
3233 uint rank = shoot_tree_ptr->at(parent_shoot_ID)->rank;
3234 vec3 base_position = interpolateTube(shoot_tree_ptr->at(parent_shoot_ID)->phytomers.back()->getInternodeNodePositions(), 0.9f);
3237 auto *shoot_new = (
new Shoot(plantID, appended_shootID, parent_shoot_ID, parent_node, 0, rank, base_position, base_rotation, current_node_number, internode_length_max, shoot_parameters, shoot_type_label,
this));
3238 shoot_tree_ptr->emplace_back(shoot_new);
3241 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3243 return appended_shootID;
3247 float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label,
uint petiole_index) {
3248 if (plant_instances.find(plantID) == plant_instances.end()) {
3249 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3250 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3251 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3254 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3256 if (parent_shoot_ID <= -1 || parent_shoot_ID >= shoot_tree_ptr->size()) {
3257 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Parent with ID of " + std::to_string(parent_shoot_ID) +
" does not exist.");
3258 }
else if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.size() <= parent_node_index) {
3259 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Parent shoot does not have a node " + std::to_string(parent_node_index) +
".");
3263 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3264 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3265 uint parent_rank = (int) shoot_tree_ptr->at(parent_shoot_ID)->rank;
3266 int childID = int(shoot_tree_ptr->size());
3269 const auto parent_shoot_ptr = shoot_tree_ptr->at(parent_shoot_ID);
3271 vec3 shoot_base_position = parent_shoot_ptr->shoot_internode_vertices.at(parent_node_index).back();
3275 if (parent_shoot_ptr->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
3277 axis_vector = parent_shoot_ptr->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
3279 axis_vector = parent_shoot_ptr->phytomers.at(parent_node_index)->getPetioleAxisVector(0, petiole_index);
3281 shoot_base_position += 0.9f * axis_vector * parent_shoot_ptr->phytomers.at(parent_node_index)->getInternodeRadius(1.f);
3284 auto *shoot_new = (
new Shoot(plantID, childID, parent_shoot_ID, parent_node_index, petiole_index, parent_rank + 1, shoot_base_position, shoot_base_rotation, current_node_number, internode_length_max, shoot_parameters, shoot_type_label,
this));
3285 shoot_tree_ptr->emplace_back(shoot_new);
3288 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3294 float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label) {
3295 if (plant_instances.find(plantID) == plant_instances.end()) {
3296 helios_runtime_error(
"ERROR (PlantArchitecture::addEpicormicShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3297 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3298 helios_runtime_error(
"ERROR (PlantArchitecture::addEpicormicShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3301 auto &parent_shoot = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
3303 uint parent_node_index = 0;
3304 if (parent_position_fraction > 0) {
3305 parent_node_index = std::ceil(parent_position_fraction *
float(parent_shoot->phytomers.size())) - 1;
3309 if (plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
3311 axis_vector = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
3313 axis_vector = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->getPetioleAxisVector(0, 0);
3319 return addChildShoot(plantID, parent_shoot_ID, parent_node_index, current_node_number, base_rotation, internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper, shoot_type_label, 0);
3322void PlantArchitecture::validateShootTypes(
ShootParameters &shoot_parameters,
const std::map<std::string, ShootParameters> &shoot_types_ref)
const {
3323 assert(shoot_parameters.child_shoot_type_probabilities.size() == shoot_parameters.child_shoot_type_labels.size());
3325 for (
int ind = shoot_parameters.child_shoot_type_labels.size() - 1; ind >= 0; ind--) {
3326 if (shoot_types_ref.find(shoot_parameters.child_shoot_type_labels.at(ind)) == shoot_types_ref.end()) {
3327 shoot_parameters.child_shoot_type_labels.erase(shoot_parameters.child_shoot_type_labels.begin() + ind);
3328 shoot_parameters.child_shoot_type_probabilities.erase(shoot_parameters.child_shoot_type_probabilities.begin() + ind);
3334 float leaf_scale_factor_fraction) {
3335 if (plant_instances.find(plantID) == plant_instances.end()) {
3336 helios_runtime_error(
"ERROR (PlantArchitecture::appendPhytomerToShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3339 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3341 if (shootID >= shoot_tree_ptr->size()) {
3342 helios_runtime_error(
"ERROR (PlantArchitecture::appendPhytomerToShoot): Parent with ID of " + std::to_string(shootID) +
" does not exist.");
3345 auto current_shoot_ptr = plant_instances.at(plantID).shoot_tree.at(shootID);
3347 int pID = current_shoot_ptr->appendPhytomer(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, phytomer_parameters);
3349 current_shoot_ptr->current_node_number++;
3350 current_shoot_ptr->nodes_this_season++;
3352 for (
auto &phytomers: current_shoot_ptr->phytomers) {
3353 phytomers->shoot_index.y = current_shoot_ptr->current_node_number;
3357 if (current_shoot_ptr->current_node_number == current_shoot_ptr->shoot_parameters.max_nodes.val()) {
3358 if (!current_shoot_ptr->shoot_parameters.flowers_require_dormancy && current_shoot_ptr->shoot_parameters.max_terminal_floral_buds.val() > 0) {
3359 current_shoot_ptr->addTerminalFloralBud();
3361 if (current_shoot_ptr->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function !=
nullptr) {
3362 state = BUD_FLOWER_CLOSED;
3363 }
else if (current_shoot_ptr->shoot_parameters.phytomer_parameters.inflorescence.fruit_prototype_function !=
nullptr) {
3364 state = BUD_FRUITING;
3368 for (
auto &fbuds: current_shoot_ptr->phytomers.back()->floral_buds) {
3369 for (
auto &fbud: fbuds) {
3370 if (fbud.isterminal) {
3372 current_shoot_ptr->phytomers.back()->updateInflorescence(fbud);
3380 else if (current_shoot_ptr->nodes_this_season >= current_shoot_ptr->shoot_parameters.max_nodes_per_season.val()) {
3381 if (!current_shoot_ptr->shoot_parameters.flowers_require_dormancy && current_shoot_ptr->shoot_parameters.max_terminal_floral_buds.val() > 0) {
3382 current_shoot_ptr->addTerminalFloralBud();
3383 for (
auto &fbuds: current_shoot_ptr->phytomers.back()->floral_buds) {
3384 for (
auto &fbud: fbuds) {
3385 if (fbud.isterminal) {
3386 fbud.state = BUD_DORMANT;
3387 current_shoot_ptr->phytomers.back()->updateInflorescence(fbud);
3392 current_shoot_ptr->phytomers.at(pID)->isdormant =
true;
3399 if (plant_instances.find(plantID) == plant_instances.end()) {
3400 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3401 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(epicormic_shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3402 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Shoot type with label of " + epicormic_shoot_type_label +
" does not exist.");
3403 }
else if (epicormic_probability_perlength_perday < 0) {
3404 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Epicormic probability must be greater than or equal to zero.");
3407 plant_instances.at(plantID).epicormic_shoot_probability_perlength_per_day = std::make_pair(epicormic_shoot_type_label, epicormic_probability_perlength_perday);
3411 build_context_geometry_internode =
false;
3415 build_context_geometry_petiole =
false;
3419 build_context_geometry_peduncle =
false;
3423 ground_clipping_height = ground_height;
3426void PlantArchitecture::incrementPhytomerInternodeGirth(
uint plantID,
uint shootID,
uint node_number,
float dt,
bool update_context_geometry) {
3427 if (plant_instances.find(plantID) == plant_instances.end()) {
3428 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3431 auto shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3433 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3434 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3435 }
else if (node_number >= shoot->current_node_number) {
3436 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Cannot scale internode " + std::to_string(node_number) +
" because there are only " + std::to_string(shoot->current_node_number) +
" nodes in this shoot.");
3439 auto phytomer = shoot->phytomers.at(node_number);
3441 float leaf_area = phytomer->downstream_leaf_area;
3444 context_ptr->
setObjectData(shoot->internode_tube_objID,
"leaf_area", leaf_area);
3446 float phytomer_age = phytomer->age;
3447 float girth_area_factor = shoot->shoot_parameters.girth_area_factor.val();
3448 if (phytomer_age > 365) {
3449 girth_area_factor = shoot->shoot_parameters.girth_area_factor.val() * 365 / phytomer_age;
3452 float internode_area = girth_area_factor * leaf_area * 1e-4;
3453 float phytomer_radius = sqrtf(internode_area /
PI_F);
3455 auto &segment = shoot->shoot_internode_radii.at(node_number);
3456 for (
float &radius: segment) {
3457 if (phytomer_radius > radius) {
3459 radius = radius + 0.5 * (phytomer_radius - radius);
3463 if (update_context_geometry && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
3464 context_ptr->
setTubeRadii(shoot->internode_tube_objID,
flatten(shoot->shoot_internode_radii));
3468void PlantArchitecture::pruneGroundCollisions(
uint plantID) {
3469 if (plant_instances.find(plantID) == plant_instances.end()) {
3470 helios_runtime_error(
"ERROR (PlantArchitecture::pruneGroundCollisions): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3473 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3474 for (
auto &phytomer: shoot->phytomers) {
3476 if ((phytomer->shoot_index.x == 0 && phytomer->rank > 0) && context_ptr->
doesObjectExist(shoot->internode_tube_objID) && detectGroundCollision(shoot->internode_tube_objID)) {
3477 context_ptr->
deleteObject(shoot->internode_tube_objID);
3478 shoot->terminateApicalBud();
3482 for (
uint petiole = 0; petiole < phytomer->leaf_objIDs.size(); petiole++) {
3483 if (detectGroundCollision(phytomer->leaf_objIDs.at(petiole))) {
3484 phytomer->removeLeaf();
3489 for (
auto &petiole: phytomer->floral_buds) {
3490 for (
auto &fbud: petiole) {
3491 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
3492 uint objID = fbud.inflorescence_objIDs.at(p);
3493 if (detectGroundCollision(objID)) {
3495 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
3496 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
3499 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
3500 uint objID = fbud.peduncle_objIDs.at(p);
3501 if (detectGroundCollision(objID)) {
3504 fbud.peduncle_objIDs.clear();
3505 fbud.inflorescence_objIDs.clear();
3506 fbud.inflorescence_bases.clear();
3531 if (plant_instances.find(plantID) == plant_instances.end()) {
3532 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3535 auto parent_shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3537 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3538 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3539 }
else if (node_number >= parent_shoot->current_node_number) {
3540 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Cannot scale leaf " + std::to_string(node_number) +
" because there are only " + std::to_string(parent_shoot->current_node_number) +
" nodes in this shoot.");
3542 if (leaf_scale_factor_fraction < 0 || leaf_scale_factor_fraction > 1) {
3546 parent_shoot->phytomers.at(node_number)->setLeafScaleFraction(leaf_scale_factor_fraction);
3550 if (plant_instances.find(plantID) == plant_instances.end()) {
3551 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3554 plant_instances.at(plantID).base_position = base_position;
3557 if (!plant_instances.at(plantID).shoot_tree.empty()) {
3562 if (Beta_mu_inclination <= 0.f) {
3563 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3564 }
else if (Beta_nu_inclination <= 0.f) {
3565 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3568 setPlantLeafAngleDistribution_private({plantID}, Beta_mu_inclination, Beta_nu_inclination, 0.f, 0.f,
true,
false);
3572 if (Beta_mu_inclination <= 0.f) {
3573 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3574 }
else if (Beta_nu_inclination <= 0.f) {
3575 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3578 setPlantLeafAngleDistribution_private(plantIDs, Beta_mu_inclination, Beta_nu_inclination, 0.f, 0.f,
true,
false);
3582 if (eccentricity < 0.f || eccentricity > 1.f) {
3583 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAzimuthAngleDistribution): Eccentricity must be between 0 and 1.");
3586 setPlantLeafAngleDistribution_private({plantID}, 0.f, 0.f, eccentricity, ellipse_rotation_degrees,
false,
true);
3590 if (eccentricity < 0.f || eccentricity > 1.f) {
3591 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAzimuthAngleDistribution): Eccentricity must be between 0 and 1.");
3594 setPlantLeafAngleDistribution_private(plantIDs, 0.f, 0.f, eccentricity, ellipse_rotation_degrees,
false,
true);
3598 if (Beta_mu_inclination <= 0.f) {
3599 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3600 }
else if (Beta_nu_inclination <= 0.f) {
3601 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3602 }
else if (eccentricity < 0.f || eccentricity > 1.f) {
3603 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Eccentricity must be between 0 and 1.");
3606 setPlantLeafAngleDistribution_private({plantID}, Beta_mu_inclination, Beta_nu_inclination, eccentricity, ellipse_rotation_degrees,
true,
true);
3610 if (Beta_mu_inclination <= 0.f) {
3611 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3612 }
else if (Beta_nu_inclination <= 0.f) {
3613 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3614 }
else if (eccentricity < 0.f || eccentricity > 1.f) {
3615 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Eccentricity must be between 0 and 1.");
3618 setPlantLeafAngleDistribution_private(plantIDs, Beta_mu_inclination, Beta_nu_inclination, eccentricity, ellipse_rotation_degrees,
true,
true);
3623 if (plant_instances.find(plantID) == plant_instances.end()) {
3624 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3625 }
else if (plant_instances.at(plantID).shoot_tree.empty()) {
3626 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" has no shoots, so could not get a base position.");
3628 return plant_instances.at(plantID).base_position;
3632 std::vector<vec3> positions;
3633 positions.reserve(plantIDs.size());
3634 for (
uint plantID: plantIDs) {
3641 if (plant_instances.find(plantID) == plant_instances.end()) {
3642 helios_runtime_error(
"ERROR (PlantArchitecture::sumPlantLeafArea): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3648 for (
uint objID: leaf_objIDs) {
3656 if (plant_instances.find(plantID) == plant_instances.end()) {
3657 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantStemHeight): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3660 auto base_shoot_ptr = plant_instances.at(plantID).shoot_tree.front();
3662 std::vector<uint> stem_objID{base_shoot_ptr->internode_tube_objID};
3665 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantStemHeight): The plant does not contain any geometry.");
3669 if (base_shoot_ptr->childIDs.find(base_shoot_ptr->current_node_number - 1) != base_shoot_ptr->childIDs.end()) {
3670 auto terminal_children = base_shoot_ptr->childIDs.at(base_shoot_ptr->current_node_number - 1);
3671 for (
uint childID: terminal_children) {
3672 auto child_shoot_ptr = plant_instances.at(plantID).shoot_tree.at(childID);
3673 if (child_shoot_ptr->rank == base_shoot_ptr->rank) {
3674 if (context_ptr->
doesObjectExist(child_shoot_ptr->internode_tube_objID)) {
3675 stem_objID.push_back(child_shoot_ptr->internode_tube_objID);
3685 return max_box.
z - min_box.
z;
3690 if (plant_instances.find(plantID) == plant_instances.end()) {
3691 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantHeight): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3698 return max_box.
z - min_box.
z;
3702 if (plant_instances.find(plantID) == plant_instances.end()) {
3703 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafInclinationAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3709 std::vector<float> leaf_inclination_angles(Nbins, 0.f);
3710 const float dtheta = 0.5f *
PI_F / float(Nbins);
3711 for (
const uint UUID: leaf_UUIDs) {
3713 const float theta =
acos_safe(fabs(normal.
z));
3715 uint bin =
static_cast<uint>(std::floor(theta / dtheta));
3719 if (!std::isnan(area)) {
3720 leaf_inclination_angles.at(bin) += area;
3727 for (
float &angle: leaf_inclination_angles) {
3733 return leaf_inclination_angles;
3737 std::vector<float> leaf_inclination_angles(Nbins, 0.f);
3738 for (
const uint plantID: plantIDs) {
3745 for (
float &angle: leaf_inclination_angles) {
3751 return leaf_inclination_angles;
3755 if (plant_instances.find(plantID) == plant_instances.end()) {
3756 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafAzimuthAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3762 std::vector<float> leaf_azimuth_angles(Nbins, 0.f);
3763 const float dtheta = 2.f *
PI_F /
static_cast<float>(Nbins);
3764 for (
const uint UUID: leaf_UUIDs) {
3768 uint bin =
static_cast<uint>(std::floor(phi / dtheta));
3772 if (!std::isnan(area)) {
3773 leaf_azimuth_angles.at(bin) += area;
3780 for (
float &angle: leaf_azimuth_angles) {
3786 return leaf_azimuth_angles;
3790 std::vector<float> leaf_azimuth_angles(Nbins, 0.f);
3791 for (
const uint plantID: plantIDs) {
3798 for (
float &angle: leaf_azimuth_angles) {
3804 return leaf_azimuth_angles;
3809 if (plant_instances.find(plantID) == plant_instances.end()) {
3810 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafCount): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3817 if (plant_instances.find(plantID) == plant_instances.end()) {
3818 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafBases): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3821 std::vector<vec3> leaf_bases;
3824 size_t total_size = 0;
3825 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
3826 for (
const auto &phytomer: shoot->phytomers) {
3827 total_size += phytomer->leaf_bases.size() * phytomer->leaf_bases.front().size();
3830 leaf_bases.reserve(total_size);
3833 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
3834 for (
const auto &phytomer: shoot->phytomers) {
3835 std::vector<vec3> bases_flat =
flatten(phytomer->leaf_bases);
3836 leaf_bases.insert(leaf_bases.end(), bases_flat.begin(), bases_flat.end());
3844 std::vector<helios::vec3> leaf_bases;
3845 for (
const uint plantID: plantIDs) {
3847 leaf_bases.insert(leaf_bases.end(), bases.begin(), bases.end());
3853 if (plant_instances.find(plantID) == plant_instances.end()) {
3854 helios_runtime_error(
"ERROR (PlantArchitecture::isPlantDormant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3857 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
3858 if (!shoot->isdormant) {
3867 if (plant_instances.find(plantID) == plant_instances.end()) {
3868 helios_runtime_error(
"ERROR (PlantArchitecture::determinePhenologyStage): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3879 if (!flowers.empty() || !fruits.empty()) {
3880 return "reproductive";
3884 const auto &plant_instance = plant_instances.at(plantID);
3885 if (plant_instance.dd_to_dormancy > 0) {
3886 float senescence_threshold = plant_instance.dd_to_dormancy_break + plant_instance.dd_to_dormancy * 0.9f;
3887 if (plant_instance.time_since_dormancy > senescence_threshold) {
3893 return "vegetative";
3897 if (plant_instances.find(plantID) == plant_instances.end()) {
3898 helios_runtime_error(
"ERROR (PlantArchitecture::writePlantMeshVertices): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3904 file.open(filename);
3906 if (!file.is_open()) {
3907 helios_runtime_error(
"ERROR (PlantArchitecture::writePlantMeshVertices): Could not open file " + filename +
" for writing.");
3910 for (
uint UUID: plant_UUIDs) {
3912 for (
vec3 &v: vertex) {
3913 file << v.x <<
" " << v.y <<
" " << v.z << std::endl;
3926 if (plant_instances.find(plantID) == plant_instances.end()) {
3927 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantName): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3929 return plant_instances.at(plantID).plant_name;
3933 if (plant_instances.find(plantID) == plant_instances.end()) {
3934 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAge): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3935 }
else if (plant_instances.at(plantID).shoot_tree.empty()) {
3936 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAge): Plant with ID of " + std::to_string(plantID) +
" has no shoots, so could not get a base position.");
3938 return plant_instances.at(plantID).current_age;
3943 if (plant_instances.find(plantID) == plant_instances.end()) {
3944 helios_runtime_error(
"ERROR (PlantArchitecture::listShootTypeLabels): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3948 const auto &shoot_types_snap = plant_instances.at(plantID).shoot_types_snapshot;
3951 std::vector<std::string> labels;
3952 labels.reserve(shoot_types_snap.size());
3953 for (
const auto &pair: shoot_types_snap) {
3954 labels.push_back(pair.first);
3961 if (plant_instances.find(plantID) == plant_instances.end()) {
3962 helios_runtime_error(
"ERROR (PlantArchitecture::harvestPlant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3965 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3966 for (
auto &phytomer: shoot->phytomers) {
3967 for (
auto &petiole: phytomer->floral_buds) {
3968 for (
auto &fbud: petiole) {
3969 if (fbud.state != BUD_DORMANT) {
3970 phytomer->setFloralBudState(BUD_DEAD, fbud);
3979 if (plant_instances.find(plantID) == plant_instances.end()) {
3980 helios_runtime_error(
"ERROR (PlantArchitecture::removePlantLeaves): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3983 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3984 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootLeaves): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3987 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3989 for (
auto &phytomer: shoot->phytomers) {
3990 phytomer->removeLeaf();
3995 if (plant_instances.find(plantID) == plant_instances.end()) {
3996 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootVegetativeBuds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3999 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4000 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootVegetativeBuds): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
4003 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4005 for (
auto &phytomer: shoot->phytomers) {
4006 phytomer->setVegetativeBudState(BUD_DEAD);
4011 if (plant_instances.find(plantID) == plant_instances.end()) {
4012 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootFloralBuds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4015 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4016 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootFloralBuds): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
4019 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4021 for (
auto &phytomer: shoot->phytomers) {
4022 phytomer->setFloralBudState(BUD_DEAD);
4027 if (plant_instances.find(plantID) == plant_instances.end()) {
4028 helios_runtime_error(
"ERROR (PlantArchitecture::removePlantLeaves): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4031 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4032 for (
auto &phytomer: shoot->phytomers) {
4033 phytomer->removeLeaf();
4039 if (plant_instances.find(plantID) == plant_instances.end()) {
4040 helios_runtime_error(
"ERROR (PlantArchitecture::makePlantDormant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4043 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4044 shoot->makeDormant();
4046 plant_instances.at(plantID).time_since_dormancy = 0;
4050 if (plant_instances.find(plantID) == plant_instances.end()) {
4051 helios_runtime_error(
"ERROR (PlantArchitecture::breakPlantDormancy): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4054 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4055 shoot->breakDormancy();
4060 if (plant_instances.find(plantID) == plant_instances.end()) {
4061 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4062 }
else if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4063 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Shoot with ID of " + std::to_string(shootID) +
" does not exist on plant " + std::to_string(plantID) +
".");
4064 }
else if (node_index >= plant_instances.at(plantID).shoot_tree.at(shootID)->current_node_number) {
4065 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Node index " + std::to_string(node_index) +
" is out of range for shoot " + std::to_string(shootID) +
".");
4068 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4070 shoot->phytomers.at(node_index)->deletePhytomer();
4072 if (plant_instances.at(plantID).shoot_tree.empty()) {
4073 std::cout <<
"WARNING (PlantArchitecture::pruneBranch): Plant " << plantID <<
" base shoot was pruned." << std::endl;
4078static vec3 orthonormal_axis(
const vec3 &v) {
4087static vec3 rodrigues(
const vec3 &v,
const vec3 &k,
float a) {
4088 float c = std::cos(a);
4089 float s = std::sin(a);
4092 return v * c +
cross(k, v) * s + k * (kv * (1.f - c));
4095void PlantArchitecture::setPlantLeafAngleDistribution_private(
const std::vector<uint> &plantIDs,
float Beta_mu_inclination,
float Beta_nu_inclination,
float eccentricity_azimuth,
float ellipse_rotation_azimuth_degrees,
bool set_elevation,
4096 bool set_azimuth)
const {
4097 for (
uint plantID: plantIDs) {
4098 if (plant_instances.find(plantID) == plant_instances.end()) {
4099 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4106 size_t N = objIDs.size();
4107 assert(bases.size() == N);
4108 if (N == 0 || (!set_elevation && !set_azimuth))
4112 std::vector<float> theta(N), phi(N), theta_t(N), phi_t(N);
4113 for (
size_t i = 0; i < N; ++i) {
4116 if (!std::isfinite(n0.
x) || !std::isfinite(n0.
y) || !std::isfinite(n0.
z) || n0.
magnitude() < 1e-6f) {
4117 n0 =
vec3(0.f, 0.f, 1.f);
4127 if (set_elevation && !set_azimuth) {
4130 }
else if (!set_elevation && set_azimuth) {
4131 theta_t[i] = theta[i];
4141 if (set_elevation && !set_azimuth) {
4143 for (
size_t i = 0; i < N; ++i) {
4144 float elev =
PI_F * 0.5f - theta_t[i];
4150 if (!set_elevation && set_azimuth) {
4152 for (
size_t i = 0; i < N; ++i) {
4153 float elev =
PI_F * 0.5f - theta[i];
4161 std::vector<vec3> V0(N), V1(N);
4162 for (
size_t i = 0; i < N; ++i) {
4163 float e0 =
PI_F * 0.5f - theta[i];
4164 float e1 =
PI_F * 0.5f - theta_t[i];
4170 std::vector<int> assignment(N);
4173 std::vector<std::vector<double>> C(N, std::vector<double>(N));
4174 for (
size_t i = 0; i < N; ++i) {
4175 for (
size_t j = 0; j < N; ++j) {
4176 double d = (V0[i] - V1[j]).magnitude();
4177 C[i][j] = std::isfinite(d) ? d : ((std::numeric_limits<double>::max)() * 0.5);
4180 hung.Solve(C, assignment);
4184 for (
size_t i = 0; i < N; ++i) {
4185 int j = assignment[i];
4188 vec3 u = (j >= 0 && j < (int) N ? V1[j] : V0[i]);
4191 v = (v.
magnitude() < 1e-6f ?
vec3(0, 0, 1) : v.normalize());
4192 u = (u.
magnitude() < 1e-6f ?
vec3(0, 0, 1) : u.normalize());
4195 float dot = std::clamp(v * u, -1.f, 1.f);
4200 if (!set_elevation && set_azimuth) {
4202 axis =
vec3(0.f, 0.f, 1.f);
4205 axis = orthonormal_axis(v);
4211 vec3 r = rodrigues(v, axis, ang);
4212 if (!std::isfinite(r.
x) || !std::isfinite(r.
y) || !std::isfinite(r.
z) || r.
magnitude() < 1e-6f) {
4332 if (plant_instances.find(plantID) == plant_instances.end()) {
4333 helios_runtime_error(
"ERROR (PlantArchitecture::getShootNodeCount): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4334 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
4335 helios_runtime_error(
"ERROR (PlantArchitecture::getShootNodeCount): Shoot ID is out of range.");
4337 return plant_instances.at(plantID).shoot_tree.at(shootID)->current_node_number;
4341 if (plant_instances.find(plantID) == plant_instances.end()) {
4342 helios_runtime_error(
"ERROR (PlantArchitecture::getShootTaper): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4343 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
4347 float r0 = plant_instances.at(plantID).shoot_tree.at(shootID)->shoot_internode_radii.front().front();
4348 float r1 = plant_instances.at(plantID).shoot_tree.at(shootID)->shoot_internode_radii.back().back();
4350 float taper = (r0 - r1) / r0;
4353 }
else if (taper > 1) {
4361 std::vector<uint> objIDs;
4362 objIDs.reserve(plant_instances.size());
4364 for (
const auto &plant: plant_instances) {
4365 objIDs.push_back(plant.first);
4372 if (plant_instances.find(plantID) == plant_instances.end()) {
4373 helios_runtime_error(
"ERROR (PlantArchitecture::getAllPlantObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4376 std::vector<uint> objIDs;
4378 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
4380 objIDs.push_back(shoot->internode_tube_objID);
4382 for (
const auto &phytomer: shoot->phytomers) {
4383 std::vector<uint> petiole_objIDs_flat =
flatten(phytomer->petiole_objIDs);
4384 objIDs.insert(objIDs.end(), petiole_objIDs_flat.begin(), petiole_objIDs_flat.end());
4385 std::vector<uint> leaf_objIDs_flat =
flatten(phytomer->leaf_objIDs);
4386 objIDs.insert(objIDs.end(), leaf_objIDs_flat.begin(), leaf_objIDs_flat.end());
4387 for (
auto &petiole: phytomer->floral_buds) {
4388 for (
auto &fbud: petiole) {
4389 std::vector<uint> inflorescence_objIDs_flat = fbud.inflorescence_objIDs;
4390 objIDs.insert(objIDs.end(), inflorescence_objIDs_flat.begin(), inflorescence_objIDs_flat.end());
4391 std::vector<uint> peduncle_objIDs_flat = fbud.peduncle_objIDs;
4392 objIDs.insert(objIDs.end(), peduncle_objIDs_flat.begin(), peduncle_objIDs_flat.end());
4406 if (plant_instances.find(plantID) == plant_instances.end()) {
4407 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4410 std::vector<uint> objIDs;
4412 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4414 for (
auto &shoot: shoot_tree) {
4416 objIDs.push_back(shoot->internode_tube_objID);
4424 if (plant_instances.find(plantID) == plant_instances.end()) {
4425 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4428 std::vector<uint> objIDs;
4430 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4432 bool shoot_type_found =
false;
4433 for (
auto &shoot: shoot_tree) {
4434 if (shoot->shoot_type_label == shoot_type_label) {
4435 shoot_type_found =
true;
4437 objIDs.push_back(shoot->internode_tube_objID);
4442 if (!shoot_type_found) {
4443 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): No shoots with shoot type label '" + shoot_type_label +
"' exist for plant with ID " + std::to_string(plantID) +
".");
4450 if (plant_instances.find(plantID) == plant_instances.end()) {
4451 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantPetioleObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4454 std::vector<uint> objIDs;
4456 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4458 for (
auto &shoot: shoot_tree) {
4459 for (
auto &phytomer: shoot->phytomers) {
4460 for (
auto &petiole: phytomer->petiole_objIDs) {
4461 objIDs.insert(objIDs.end(), petiole.begin(), petiole.end());
4470 if (plant_instances.find(plantID) == plant_instances.end()) {
4471 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4474 std::vector<uint> objIDs;
4476 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4478 for (
auto &shoot: shoot_tree) {
4479 for (
auto &phytomer: shoot->phytomers) {
4480 for (
auto &leaf_objID: phytomer->leaf_objIDs) {
4481 objIDs.insert(objIDs.end(), leaf_objID.begin(), leaf_objID.end());
4490 std::vector<uint> objIDs;
4491 objIDs.reserve(50 * plantIDs.size());
4492 for (
const uint plantID: plantIDs) {
4494 objIDs.insert(objIDs.end(), leaf_objIDs.begin(), leaf_objIDs.end());
4500 if (plant_instances.find(plantID) == plant_instances.end()) {
4501 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantPeduncleObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4504 std::vector<uint> objIDs;
4506 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4508 for (
auto &shoot: shoot_tree) {
4509 for (
auto &phytomer: shoot->phytomers) {
4510 for (
auto &petiole: phytomer->floral_buds) {
4511 for (
auto &fbud: petiole) {
4512 objIDs.insert(objIDs.end(), fbud.peduncle_objIDs.begin(), fbud.peduncle_objIDs.end());
4522 if (plant_instances.find(plantID) == plant_instances.end()) {
4523 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4526 std::vector<uint> objIDs;
4528 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4530 for (
auto &shoot: shoot_tree) {
4531 for (
auto &phytomer: shoot->phytomers) {
4532 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4533 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4534 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FLOWER_OPEN || phytomer->floral_buds.at(petiole).at(bud).state == BUD_FLOWER_CLOSED) {
4535 objIDs.insert(objIDs.end(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.begin(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.end());
4546 if (plant_instances.find(plantID) == plant_instances.end()) {
4547 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4550 std::vector<uint> objIDs;
4552 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4554 for (
auto &shoot: shoot_tree) {
4555 for (
auto &phytomer: shoot->phytomers) {
4556 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4557 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4558 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FRUITING) {
4559 objIDs.insert(objIDs.end(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.begin(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.end());
4571 if (plant_instances.find(plantID) == plant_instances.end()) {
4572 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4575 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4577 for (
const auto& shoot : shoot_tree) {
4579 int fruit_count = 0;
4581 for (
const auto& phytomer : shoot->phytomers) {
4582 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4583 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4584 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FRUITING) {
4592 context_ptr->
setObjectData(shoot->internode_tube_objID,
"fruit_count", fruit_count);
4600 if (plant_instances.find(plantID) == plant_instances.end()) {
4601 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4604 std::vector<uint> objIDs;
4606 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4608 for (
auto &shoot: shoot_tree) {
4609 objIDs.push_back(shoot->internode_tube_objID);
4618 if (plant_instances.find(plantID) == plant_instances.end()) {
4619 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantCollisionRelevantObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4622 std::vector<uint> collision_relevant_objects;
4627 if (collision_include_internodes) {
4629 collision_relevant_objects.insert(collision_relevant_objects.end(), internodes.begin(), internodes.end());
4633 if (collision_include_leaves) {
4635 collision_relevant_objects.insert(collision_relevant_objects.end(), leaves.begin(), leaves.end());
4639 if (collision_include_petioles) {
4641 collision_relevant_objects.insert(collision_relevant_objects.end(), petioles.begin(), petioles.end());
4645 if (collision_include_flowers) {
4647 collision_relevant_objects.insert(collision_relevant_objects.end(), flowers.begin(), flowers.end());
4651 if (collision_include_fruit) {
4653 collision_relevant_objects.insert(collision_relevant_objects.end(), fruit.begin(), fruit.end());
4656 return collision_relevant_objects;
4660 std::vector<uint> UUIDs_all;
4661 for (
const auto &instance: plant_instances) {
4663 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4669 std::vector<uint> UUIDs_all;
4670 for (
const auto &instance: plant_instances) {
4673 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4679 std::vector<uint> UUIDs_all;
4680 for (
const auto &instance: plant_instances) {
4683 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4689 std::vector<uint> UUIDs_all;
4690 for (
const auto &instance: plant_instances) {
4693 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4699 std::vector<uint> UUIDs_all;
4700 for (
const auto &instance: plant_instances) {
4703 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4709 std::vector<uint> UUIDs_all;
4710 for (
const auto &instance: plant_instances) {
4713 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4719 std::vector<uint> UUIDs_all;
4720 for (
const auto &instance: plant_instances) {
4723 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4729 std::vector<uint> objIDs_all;
4730 for (
const auto &instance: plant_instances) {
4732 objIDs_all.insert(objIDs_all.end(), objIDs.begin(), objIDs.end());
4738 carbon_model_enabled =
true;
4742 carbon_model_enabled =
false;
4746 if (current_age < 0) {
4747 helios_runtime_error(
"ERROR (PlantArchitecture::addPlantInstance): Current age must be greater than or equal to zero.");
4750 PlantInstance instance(base_position, current_age,
"custom", context_ptr);
4752 plant_instances.emplace(plant_count, instance);
4755 plant_instances.at(plant_count).shoot_types_snapshot = shoot_types;
4759 return plant_count - 1;
4763 if (plant_instances.find(plantID) == plant_instances.end()) {
4764 helios_runtime_error(
"ERROR (PlantArchitecture::duplicatePlantInstance): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4767 auto plant_shoot_tree = &plant_instances.at(plantID).shoot_tree;
4772 plant_instances.at(plantID_new).shoot_types_snapshot = plant_instances.at(plantID).shoot_types_snapshot;
4774 if (plant_shoot_tree->empty()) {
4778 if (plant_shoot_tree->front()->phytomers.empty()) {
4783 for (
const auto &shoot: *plant_shoot_tree) {
4784 uint shootID_new = 0;
4785 for (
int node = 0; node < shoot->current_node_number; node++) {
4786 auto phytomer = shoot->phytomers.at(node);
4787 float internode_radius = phytomer->internode_radius_initial;
4788 float internode_length_max = phytomer->internode_length_max;
4789 float internode_scale_factor_fraction = phytomer->current_internode_scale_factor;
4790 float leaf_scale_factor_fraction = 1.f;
4794 AxisRotation original_base_rotation = shoot->base_rotation;
4795 if (shoot->parent_shoot_ID == -1) {
4797 shootID_new =
addBaseStemShoot(plantID_new, 1, original_base_rotation + base_rotation, internode_radius, internode_length_max, internode_scale_factor_fraction, leaf_scale_factor_fraction, 0, shoot->shoot_type_label);
4800 uint parent_node = plant_shoot_tree->at(shoot->parent_shoot_ID)->parent_node_index;
4801 uint parent_petiole_index = 0;
4802 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
4803 shootID_new =
addChildShoot(plantID_new, shoot->parent_shoot_ID, parent_node, 1, original_base_rotation, internode_radius, internode_length_max, internode_scale_factor_fraction, leaf_scale_factor_fraction, 0,
4804 shoot->shoot_type_label, parent_petiole_index);
4805 parent_petiole_index++;
4810 appendPhytomerToShoot(plantID_new, shootID_new, plant_instances.at(plantID).shoot_types_snapshot.at(shoot->shoot_type_label).phytomer_parameters, internode_radius, internode_length_max, internode_scale_factor_fraction,
4811 leaf_scale_factor_fraction);
4813 auto phytomer_new = plant_instances.at(plantID_new).shoot_tree.at(shootID_new)->phytomers.back();
4814 for (
uint petiole_index = 0; petiole_index < phytomer->petiole_objIDs.size(); petiole_index++) {
4815 phytomer_new->setLeafScaleFraction(petiole_index, phytomer->current_leaf_scale_factor.at(petiole_index));
4824 if (plant_instances.find(plantID) == plant_instances.end()) {
4830 plant_instances.erase(plantID);
4834 for (
uint ID: plantIDs) {
4840 float max_leaf_lifespan,
bool is_evergreen) {
4841 if (plant_instances.find(plantID) == plant_instances.end()) {
4842 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantPhenologicalThresholds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4845 plant_instances.at(plantID).dd_to_dormancy_break = time_to_dormancy_break;
4846 plant_instances.at(plantID).dd_to_flower_initiation = time_to_flower_initiation;
4847 plant_instances.at(plantID).dd_to_flower_opening = time_to_flower_opening;
4848 plant_instances.at(plantID).dd_to_fruit_set = time_to_fruit_set;
4849 plant_instances.at(plantID).dd_to_fruit_maturity = time_to_fruit_maturity;
4850 plant_instances.at(plantID).dd_to_dormancy = time_to_dormancy;
4851 if (max_leaf_lifespan == 0) {
4852 plant_instances.at(plantID).max_leaf_lifespan = 1e6;
4854 plant_instances.at(plantID).max_leaf_lifespan = max_leaf_lifespan;
4856 plant_instances.at(plantID).is_evergreen = is_evergreen;
4860 if (plant_instances.find(plantID) == plant_instances.end()) {
4861 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantCarbohydrateModelParameters): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4864 plant_instances.at(plantID).carb_parameters = carb_parameters;
4868 for (
uint plantID: plantIDs) {
4874 plant_instances.at(plantID).dd_to_dormancy_break = 0;
4875 plant_instances.at(plantID).dd_to_flower_initiation = -1;
4876 plant_instances.at(plantID).dd_to_flower_opening = -1;
4877 plant_instances.at(plantID).dd_to_fruit_set = -1;
4878 plant_instances.at(plantID).dd_to_fruit_maturity = -1;
4879 plant_instances.at(plantID).dd_to_dormancy = 1e6;
4891 std::vector<uint> plantIDs = {plantID};
4896 for (
uint plantID: plantIDs) {
4897 if (plant_instances.find(plantID) == plant_instances.end()) {
4898 helios_runtime_error(
"ERROR (PlantArchitecture::advanceTime): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4906 if (collision_detection_enabled && collision_detection_ptr !=
nullptr) {
4907 rebuildBVHForTimestep();
4911 float phyllochron_min = 9999;
4912 for (
uint plantID: plantIDs) {
4913 PlantInstance &plant_instance = plant_instances.at(plantID);
4914 auto shoot_tree = &plant_instance.shoot_tree;
4915 if (shoot_tree->empty()) {
4918 float phyllochron_min_shoot = shoot_tree->front()->shoot_parameters.phyllochron_min.val();
4919 if (phyllochron_min_shoot < phyllochron_min) {
4920 phyllochron_min = phyllochron_min_shoot;
4922 for (
int i = 1; i < shoot_tree->size(); i++) {
4923 if (shoot_tree->at(i)->shoot_parameters.phyllochron_min.val() < phyllochron_min) {
4924 phyllochron_min_shoot = shoot_tree->at(i)->shoot_parameters.phyllochron_min.val();
4925 if (phyllochron_min_shoot < phyllochron_min) {
4926 phyllochron_min = phyllochron_min_shoot;
4931 if (phyllochron_min == 9999) {
4936 if (carbon_model_enabled) {
4937 accumulateShootPhotosynthesis();
4943 if (time_step_days <= phyllochron_min) {
4944 Nsteps = time_step_days;
4947 Nsteps = std::floor(time_step_days / phyllochron_min);
4948 dt_max_days = phyllochron_min;
4951 float remainder_time = time_step_days - dt_max_days * float(Nsteps);
4952 if (remainder_time > 0.f) {
4957 helios::ProgressBar progress_bar(Nsteps, 50, Nsteps > 1 && printmessages,
"Advancing time");
4959 for (
int timestep = 0; timestep < Nsteps; timestep++) {
4962 bool should_rebuild_bvh =
false;
4963 if (collision_detection_enabled && collision_detection_ptr !=
nullptr) {
4967 should_rebuild_bvh = (timestep % 25 == 0);
4969 should_rebuild_bvh = (timestep % 10 == 0);
4973 if (should_rebuild_bvh) {
4974 rebuildBVHForTimestep();
4978 for (
uint plantID: plantIDs) {
4980 if (!plant_primitives.empty()) {
4981 collision_detection_ptr->
registerTree(plantID, plant_primitives);
4987 if (timestep == Nsteps - 1 && remainder_time != 0.f) {
4988 dt_max_days = remainder_time;
4991 for (
uint plantID: plantIDs) {
4992 PlantInstance &plant_instance = plant_instances.at(plantID);
4994 auto shoot_tree = &plant_instance.shoot_tree;
4996 if (shoot_tree->empty()) {
5000 if (plant_instance.current_age <= plant_instance.max_age && plant_instance.current_age + dt_max_days > plant_instance.max_age) {
5001 }
else if (plant_instance.current_age >= plant_instance.max_age) {
5003 shoot_tree->front()->updateShootNodes(
true);
5007 plant_instance.current_age += dt_max_days;
5008 plant_instance.time_since_dormancy += dt_max_days;
5010 if (plant_instance.time_since_dormancy > plant_instance.dd_to_dormancy_break + plant_instance.dd_to_dormancy) {
5011 plant_instance.time_since_dormancy = 0;
5012 for (
const auto &shoot: *shoot_tree) {
5013 shoot->makeDormant();
5014 shoot->phyllochron_counter = 0;
5020 size_t shoot_count = shoot_tree->size();
5021 for (
int i = 0; i < shoot_count; i++) {
5022 auto shoot = shoot_tree->at(i);
5024 for (
auto &phytomer: shoot->phytomers) {
5025 phytomer->age += dt_max_days;
5027 if (phytomer->phytomer_parameters.phytomer_callback_function !=
nullptr) {
5028 phytomer->phytomer_parameters.phytomer_callback_function(phytomer);
5035 if (shoot->isdormant && plant_instance.time_since_dormancy >= plant_instance.dd_to_dormancy_break) {
5036 shoot->phyllochron_counter = 0;
5037 shoot->breakDormancy();
5040 if (shoot->isdormant) {
5045 for (
auto &phytomer: shoot->phytomers) {
5046 if (phytomer->age > plant_instance.max_leaf_lifespan) {
5048 phytomer->removeLeaf();
5051 if (phytomer->floral_buds.empty()) {
5056 for (
auto &petiole: phytomer->floral_buds) {
5057 for (
auto &fbud: petiole) {
5058 if (fbud.state != BUD_DORMANT && fbud.state != BUD_DEAD) {
5059 fbud.time_counter += dt_max_days;
5063 if (shoot->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function !=
nullptr) {
5066 if (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation >= 0.f) {
5068 if ((!shoot->shoot_parameters.flowers_require_dormancy && fbud.time_counter >= plant_instance.dd_to_flower_initiation) ||
5069 (shoot->shoot_parameters.flowers_require_dormancy && fbud.time_counter >= plant_instance.dd_to_flower_initiation)) {
5070 fbud.time_counter = 0;
5071 if (context_ptr->
randu() < shoot->shoot_parameters.flower_bud_break_probability.val()) {
5072 phytomer->setFloralBudState(BUD_FLOWER_CLOSED, fbud);
5074 phytomer->setFloralBudState(BUD_DEAD, fbud);
5076 if (shoot->shoot_parameters.determinate_shoot_growth) {
5077 shoot->terminateApicalBud();
5078 shoot->terminateAxillaryVegetativeBuds();
5083 }
else if ((fbud.state == BUD_FLOWER_CLOSED && plant_instance.dd_to_flower_opening >= 0.f) || (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation < 0.f && plant_instance.dd_to_flower_opening >= 0.f)) {
5084 if (fbud.time_counter >= plant_instance.dd_to_flower_opening) {
5085 fbud.time_counter = 0;
5086 if (fbud.state == BUD_FLOWER_CLOSED) {
5087 phytomer->setFloralBudState(BUD_FLOWER_OPEN, fbud);
5089 if (context_ptr->
randu() < shoot->shoot_parameters.flower_bud_break_probability.val()) {
5090 phytomer->setFloralBudState(BUD_FLOWER_OPEN, fbud);
5092 phytomer->setFloralBudState(BUD_DEAD, fbud);
5095 if (shoot->shoot_parameters.determinate_shoot_growth) {
5096 shoot->terminateApicalBud();
5097 shoot->terminateAxillaryVegetativeBuds();
5105 if (shoot->shoot_parameters.phytomer_parameters.inflorescence.fruit_prototype_function !=
nullptr) {
5106 if ((fbud.state == BUD_FLOWER_OPEN && plant_instance.dd_to_fruit_set >= 0.f) ||
5108 (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation < 0.f &&
5109 (plant_instance.dd_to_flower_opening < 0.f || shoot->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function ==
nullptr) && plant_instance.dd_to_fruit_set >= 0.f) ||
5111 (fbud.state == BUD_FLOWER_CLOSED && plant_instance.dd_to_flower_opening < 0.f && plant_instance.dd_to_fruit_set >= 0.f)) {
5113 if (fbud.time_counter >= plant_instance.dd_to_fruit_set) {
5114 fbud.time_counter = 0;
5116 float fruit_set_prob = shoot->shoot_parameters.fruit_set_probability.val();
5117 if (fbud.state == BUD_ACTIVE) {
5119 fruit_set_prob *= shoot->shoot_parameters.flower_bud_break_probability.val();
5121 if (context_ptr->
randu() < fruit_set_prob) {
5122 phytomer->setFloralBudState(BUD_FRUITING, fbud);
5124 phytomer->setFloralBudState(BUD_DEAD, fbud);
5126 if (shoot->shoot_parameters.determinate_shoot_growth) {
5127 shoot->terminateApicalBud();
5128 shoot->terminateAxillaryVegetativeBuds();
5140 for (
auto &phytomer: shoot->phytomers) {
5142 if (phytomer->current_internode_scale_factor < 1) {
5143 float dL_internode = dt_max_days * shoot->elongation_rate_instantaneous * phytomer->internode_length_max;
5144 float length_scale = fmin(1.f, (phytomer->getInternodeLength() + dL_internode) / phytomer->internode_length_max);
5145 phytomer->setInternodeLengthScaleFraction(length_scale,
false);
5149 if (shoot->shoot_parameters.girth_area_factor.val() > 0.f) {
5150 if (carbon_model_enabled) {
5151 incrementPhytomerInternodeGirth_carb(plantID, shoot->ID, node_index, dt_max_days,
false);
5153 incrementPhytomerInternodeGirth(plantID, shoot->ID, node_index, dt_max_days,
false);
5161 for (
auto &phytomer: shoot->phytomers) {
5163 if (phytomer->hasLeaf()) {
5164 for (
uint petiole_index = 0; petiole_index < phytomer->current_leaf_scale_factor.size(); petiole_index++) {
5165 if (phytomer->current_leaf_scale_factor.at(petiole_index) >= 1) {
5176 float tip_ind = ceil(
float(phytomer->leaf_size_max.at(petiole_index).size() - 1) / 2.f);
5177 float leaf_length = phytomer->current_leaf_scale_factor.at(petiole_index) * phytomer->leaf_size_max.at(petiole_index).at(tip_ind);
5178 float dL_leaf = dt_max_days * shoot->elongation_rate_instantaneous * phytomer->leaf_size_max.at(petiole_index).at(tip_ind);
5179 float leaf_scale = fmin(1.f, (leaf_length + dL_leaf) / phytomer->phytomer_parameters.leaf.prototype_scale.val());
5183 float scale = fmin(1.f, (leaf_length + dL_leaf) / phytomer->phytomer_parameters.leaf.prototype_scale.val());
5184 phytomer->phytomer_parameters.leaf.prototype_scale.resample();
5185 phytomer->setLeafScaleFraction(petiole_index, scale);
5190 for (
auto &petiole: phytomer->floral_buds) {
5191 for (
auto &fbud: petiole) {
5193 if (fbud.state == BUD_FRUITING && fbud.time_counter > 0) {
5195 fbud.previous_fruit_scale_factor = fbud.current_fruit_scale_factor;
5196 float scale = fmin(1, 0.25f + 0.75f * fbud.time_counter / plant_instance.dd_to_fruit_maturity);
5197 phytomer->setInflorescenceScaleFraction(fbud, scale);
5203 uint parent_petiole_index = 0;
5204 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
5205 for (
auto &vbud: petiole) {
5206 if (vbud.state == BUD_ACTIVE && phytomer->age + dt_max_days > shoot->shoot_parameters.vegetative_bud_break_time.val()) {
5208 int parent_node_count = shoot->current_node_number;
5215 new_shoot_parameters->
base_yaw.resample();
5221 float internode_length_max;
5229 float internode_radius = phytomer->internode_radius_initial;
5231 uint childID =
addChildShoot(plantID, shoot->ID, node_index, 1, base_rotation, internode_radius, internode_length_max, 0.01, 0.01, 0, vbud.shoot_type_label, parent_petiole_index);
5233 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
5234 vbud.shoot_ID = childID;
5235 shoot_tree->at(childID)->isdormant =
false;
5238 parent_petiole_index++;
5245 if (shoot->current_node_number >= shoot->shoot_parameters.max_nodes.val()) {
5246 shoot->terminateApicalBud();
5250 if (!shoot->meristem_is_alive) {
5255 shoot->phyllochron_counter += dt_max_days;
5256 if (shoot->phyllochron_counter >= shoot->phyllochron_instantaneous && !shoot->phytomers.back()->isdormant) {
5257 float internode_radius = shoot->shoot_parameters.phytomer_parameters.internode.radius_initial.val();
5258 shoot->shoot_parameters.phytomer_parameters.internode.radius_initial.resample();
5259 float internode_length_max = shoot->internode_length_max_shoot_initial;
5262 shoot->phyllochron_counter = shoot->phyllochron_counter - shoot->phyllochron_instantaneous;
5266 std::string epicormic_shoot_label = plant_instance.epicormic_shoot_probability_perlength_per_day.first;
5267 if (!epicormic_shoot_label.empty()) {
5268 std::vector<float> epicormic_fraction;
5269 uint Nepicormic = shoot->sampleEpicormicShoot(time_step_days, epicormic_fraction);
5270 for (
int s = 0; s < Nepicormic; s++) {
5271 float internode_radius = plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).phytomer_parameters.internode.radius_initial.val();
5272 plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).phytomer_parameters.internode.radius_initial.resample();
5273 float internode_length_max = plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).internode_length_max.val();
5275 addEpicormicShoot(plantID, shoot->ID, epicormic_fraction.at(s), 1, 0, internode_radius, internode_length_max, 0.01, 0.01, 0, epicormic_shoot_label);
5278 if (carbon_model_enabled) {
5279 if (output_object_data.find(
"carbohydrate_concentration") != output_object_data.end() && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
5280 float shoot_volume = shoot->calculateShootInternodeVolume();
5281 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", shoot->carbohydrate_pool_molC / shoot_volume);
5288 bool should_update_context = collision_detection_enabled && (geometry_update_counter >= geometry_update_frequency);
5291 bool force_update = collision_avoidance_applied && force_update_on_collision;
5293 if (should_update_context || force_update) {
5294 shoot_tree->front()->updateShootNodes(
true);
5298 shoot_tree->front()->updateShootNodes(
false);
5302 collision_avoidance_applied =
false;
5305 if (ground_clipping_height != -99999) {
5306 pruneGroundCollisions(plantID);
5310 if (carbon_model_enabled) {
5311 subtractShootMaintenanceCarbon(dt_max_days);
5312 subtractShootGrowthCarbon();
5313 checkCarbonPool_transferCarbon(dt_max_days);
5314 checkCarbonPool_adjustPhyllochron(dt_max_days);
5315 checkCarbonPool_abortOrgans(dt_max_days);
5319 for (
auto &shoot: *shoot_tree) {
5320 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
5322 float volume_ratio = shoot->old_shoot_volume/shoot_volume;
5323 context_ptr->
setObjectData(shoot->internode_tube_objID,
"volume_ratio", volume_ratio);
5324 shoot->old_shoot_volume = shoot_volume;
5325 context_ptr->
setObjectData(shoot->internode_tube_objID,
"old_shoot_volume", shoot_volume);
5330 if (!plant_primitives.empty()) {
5331 if (output_object_data.at(
"plant_height")) {
5334 if (output_object_data.at(
"phenology_stage")) {
5341 if (nitrogen_model_enabled) {
5342 accumulateLeafNitrogen(dt_max_days);
5343 remobilizeNitrogen(dt_max_days);
5344 removeFruitNitrogen();
5345 updateNitrogenStressFactor();
5349 if (geometry_update_counter >= geometry_update_frequency) {
5350 geometry_update_counter = 0;
5352 geometry_update_counter++;
5363 if (solid_obstacle_pruning_enabled) {
5364 pruneSolidBoundaryCollisions();
5369 if (!collision_detection_enabled) {
5370 for (
uint plantID: plantIDs) {
5371 if (plant_instances.find(plantID) != plant_instances.end()) {
5372 plant_instances.at(plantID).shoot_tree.front()->updateShootNodes(
true);
5379 if (output_object_data.at(
"age")) {
5380 for (
uint plantID: plantIDs) {
5381 if (plant_instances.find(plantID) == plant_instances.end()) {
5385 auto shoot_tree = &plant_instances.at(plantID).shoot_tree;
5386 for (
auto &shoot: *shoot_tree) {
5389 if (shoot->build_context_geometry_internode && !shoot->phytomers.empty()) {
5392 float shoot_age = shoot->phytomers.back()->age;
5393 context_ptr->
setObjectData(shoot->internode_tube_objID,
"age", shoot_age);
5398 for (
auto &phytomer: shoot->phytomers) {
5399 if (phytomer->build_context_geometry_petiole) {
5400 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"age", phytomer->age);
5402 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"age", phytomer->age);
5413 if (!solid_obstacle_avoidance_enabled || solid_obstacle_UUIDs.empty() || !solid_obstacle_fruit_adjustment_enabled) {
5417 if (collision_detection_ptr ==
nullptr) {
5422 int debug_failures_shown = 0;
5423 const int max_debug_failures = 0;
5426 helios::ProgressBar progress_bar(plant_instances.size(), 50, plant_instances.size() > 1 && printmessages,
"Adjusting fruit collisions");
5429 for (
const auto &plant_instance: plant_instances) {
5430 uint plantID = plant_instance.first;
5435 if (fruit_objIDs.empty()) {
5440 for (
uint fruit_objID: fruit_objIDs) {
5444 if (fruit_UUIDs.empty()) {
5449 std::vector<uint> collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5451 if (!collisions.empty()) {
5455 vec3 bbox_min, bbox_max;
5461 const Phytomer *fruit_phytomer =
nullptr;
5462 uint fruit_petiole_index = 0;
5463 uint fruit_bud_index = 0;
5464 bool found_base =
false;
5467 for (
const auto &shoot: plant_instance.second.shoot_tree) {
5468 for (
const auto &phytomer: shoot->phytomers) {
5469 uint petiole_idx = 0;
5470 for (
const auto &petiole: phytomer->floral_buds) {
5471 for (
const auto &fbud: petiole) {
5473 for (
size_t idx = 0; idx < fbud.inflorescence_objIDs.size(); idx++) {
5474 if (fbud.inflorescence_objIDs[idx] == fruit_objID && idx < fbud.inflorescence_bases.size()) {
5476 fruit_base = fbud.inflorescence_bases[idx];
5477 fruit_phytomer = phytomer.get();
5478 fruit_petiole_index = petiole_idx;
5479 fruit_bud_index = fbud.bud_index;
5483 peduncle_axis = phytomer->getPeduncleAxisVector(1.0f, petiole_idx, fbud.bud_index);
5484 }
catch (
const std::exception &e) {
5513 float fruit_radius = 0;
5514 fruit_radius = std::max(fruit_radius, (bbox_max - fruit_base).magnitude());
5515 fruit_radius = std::max(fruit_radius, (bbox_min - fruit_base).magnitude());
5516 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_min.
y, bbox_max.
z) - fruit_base).magnitude());
5517 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_max.
y, bbox_min.
z) - fruit_base).magnitude());
5518 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_min.
y, bbox_min.
z) - fruit_base).magnitude());
5519 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_max.
y, bbox_max.
z) - fruit_base).magnitude());
5520 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_min.
y, bbox_max.
z) - fruit_base).magnitude());
5521 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_max.
y, bbox_min.
z) - fruit_base).magnitude());
5525 float penetration_depth = std::max(0.0f, -bbox_min.
z);
5528 float initial_rotation = 0;
5529 if (fruit_radius > 0 && penetration_depth > 0) {
5531 float angle_estimate = std::asin(std::min(1.0f, penetration_depth / fruit_radius));
5533 initial_rotation = std::min(
deg2rad(35.0f), angle_estimate * 1.5f);
5536 initial_rotation =
deg2rad(10.0f);
5540 initial_rotation = std::max(initial_rotation,
deg2rad(8.0f));
5546 if (peduncle_axis.
magnitude() < 1e-6f) {
5554 vec3 bbox_center = 0.5f * (bbox_min + bbox_max);
5555 vec3 to_fruit_center = bbox_center - fruit_base;
5556 if (to_fruit_center.
magnitude() > 1e-6f) {
5560 to_fruit_center = peduncle_axis;
5565 rotation_axis =
cross(peduncle_axis, to_fruit_center);
5566 if (rotation_axis.
magnitude() < 1e-6f) {
5568 if (std::abs(peduncle_axis.
z) < 0.9f) {
5577 float rotation_step = initial_rotation;
5578 float total_rotation = 0;
5579 const float max_rotation =
deg2rad(120.0f);
5580 const int max_iterations = 25;
5583 bool debug_this_fruit = (debug_failures_shown < max_debug_failures);
5584 if (debug_this_fruit && printmessages) {
5585 std::cout <<
"\n=== DEBUG: Fruit " << fruit_objID <<
" collision adjustment ===" << std::endl;
5586 std::cout <<
"Fruit base: " << fruit_base << std::endl;
5587 std::cout <<
"Fruit bbox: " << bbox_min <<
" to " << bbox_max << std::endl;
5588 std::cout <<
"Fruit radius: " << fruit_radius << std::endl;
5589 std::cout <<
"Penetration depth: " << penetration_depth << std::endl;
5590 std::cout <<
"Peduncle axis: " << peduncle_axis << std::endl;
5591 std::cout <<
"Rotation axis: " << rotation_axis << std::endl;
5592 std::cout <<
"Initial rotation: " <<
rad2deg(initial_rotation) <<
" degrees" << std::endl;
5593 std::cout <<
"Initial collisions: " << collisions.size() << std::endl;
5596 for (
int iter = 0; iter < max_iterations && total_rotation < max_rotation; iter++) {
5599 context_ptr->
rotateObject(fruit_objID, -rotation_step, fruit_base, rotation_axis);
5600 total_rotation += rotation_step;
5604 collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5606 if (debug_this_fruit && printmessages) {
5607 std::cout <<
"Iter " << iter <<
": rotated " <<
rad2deg(rotation_step) <<
" deg (total " <<
rad2deg(total_rotation) <<
"), collisions: " << collisions.size() << std::endl;
5610 if (collisions.empty()) {
5613 float fine_tune_step =
deg2rad(3.0f);
5614 float fine_tune_attempts = 5;
5615 float original_total = total_rotation;
5617 if (debug_this_fruit && printmessages) {
5618 std::cout <<
"Fine-tuning: trying to rotate back down from " <<
rad2deg(total_rotation) <<
" degrees" << std::endl;
5621 for (
int fine_iter = 0; fine_iter < fine_tune_attempts; fine_iter++) {
5623 context_ptr->
rotateObject(fruit_objID, fine_tune_step, fruit_base, rotation_axis);
5627 std::vector<uint> test_collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5629 if (!test_collisions.empty()) {
5631 context_ptr->
rotateObject(fruit_objID, -fine_tune_step, fruit_base, rotation_axis);
5635 total_rotation -= fine_tune_step;
5644 rotation_step *= 0.7f;
5648 if (!collisions.empty()) {
5649 if (debug_this_fruit && printmessages) {
5650 std::cout <<
"FAILED: Fruit " << fruit_objID <<
" still colliding after " <<
rad2deg(total_rotation) <<
" degrees rotation (" << max_iterations <<
" iterations)" << std::endl;
5653 vec3 final_bbox_min, final_bbox_max;
5655 std::cout <<
"Final bbox: " << final_bbox_min <<
" to " << final_bbox_max << std::endl;
5656 std::cout <<
"Lowest point: " << final_bbox_min.
z << std::endl;
5658 debug_failures_shown++;
5672void PlantArchitecture::pruneSolidBoundaryCollisions() {
5673 if (!solid_obstacle_avoidance_enabled || solid_obstacle_UUIDs.empty()) {
5677 if (collision_detection_ptr ==
nullptr) {
5681 if (printmessages) {
5682 std::cout <<
"Performing solid boundary collision detection..." << std::endl;
5687 std::vector<uint> all_plant_primitives;
5691 std::vector<uint> intersecting_primitives = collision_detection_ptr->
findCollisions(solid_obstacle_UUIDs, {}, all_plant_primitives, {},
false);
5696 if (intersecting_primitives.empty()) {
5697 if (printmessages) {
5698 std::cout <<
"No collisions detected - this is unexpected given visible fruit penetration" << std::endl;
5703 if (printmessages) {
5704 std::cout <<
"Intersecting primitives found: " << intersecting_primitives.size() << std::endl;
5708 std::unordered_set<uint> collision_set(intersecting_objIDs.begin(), intersecting_objIDs.end());
5711 for (
auto &[plantID, plant]: plant_instances) {
5712 for (
uint shootID = 0; shootID < plant.shoot_tree.size(); shootID++) {
5713 auto &shoot = plant.shoot_tree.at(shootID);
5714 bool shoot_was_deleted =
false;
5718 if (collision_set.count(shoot->internode_tube_objID)) {
5720 if (shoot->rank != 0) {
5723 shoot_was_deleted =
true;
5729 if (shoot_was_deleted) {
5733 for (
uint node = 0; node < shoot->current_node_number; node++) {
5734 auto &phytomer = shoot->phytomers.at(node);
5737 for (
uint petiole = 0; petiole < phytomer->leaf_objIDs.size(); petiole++) {
5738 for (
uint leaflet = 0; leaflet < phytomer->leaf_objIDs.at(petiole).size(); leaflet++) {
5739 uint leaf_objID = phytomer->leaf_objIDs.at(petiole).at(leaflet);
5740 if (collision_set.count(leaf_objID)) {
5741 phytomer->removeLeaf();
5748 for (
uint petiole = 0; petiole < phytomer->petiole_objIDs.size(); petiole++) {
5749 for (
uint segment = 0; segment < phytomer->petiole_objIDs.at(petiole).size(); segment++) {
5750 uint petiole_objID = phytomer->petiole_objIDs.at(petiole).at(segment);
5751 if (collision_set.count(petiole_objID)) {
5752 phytomer->removeLeaf();
5759 for (
auto &petiole: phytomer->floral_buds) {
5760 for (
auto &fbud: petiole) {
5762 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
5763 uint objID = fbud.inflorescence_objIDs.at(p);
5764 if (collision_set.count(objID)) {
5766 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
5767 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
5771 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
5772 uint objID = fbud.peduncle_objIDs.at(p);
5773 if (collision_set.count(objID)) {
5777 fbud.peduncle_objIDs.clear();
5778 fbud.inflorescence_objIDs.clear();
5779 fbud.inflorescence_bases.clear();
5787 if (shoot_was_deleted) {
5793 if (printmessages) {
5794 std::cout <<
"Solid boundary collision pruning completed" << std::endl;
5798std::vector<uint>
makeTubeFromCones(
uint radial_subdivisions,
const std::vector<helios::vec3> &vertices,
const std::vector<float> &radii,
const std::vector<helios::RGBcolor> &colors,
helios::Context *context_ptr) {
5799 uint Nverts = vertices.size();
5801 if (radii.size() != Nverts || colors.size() != Nverts) {
5802 helios_runtime_error(
"ERROR (makeTubeFromCones): Length of vertex vectors is not consistent.");
5806 bool all_radii_too_small =
true;
5807 float max_radius = 0.0f;
5808 for (
float radius: radii) {
5809 max_radius = std::max(max_radius, radius);
5810 if (radius >= MIN_TUBE_RADIUS_FOR_GEOMETRY) {
5811 all_radii_too_small =
false;
5817 float total_length = 0.0f;
5818 for (
uint v = 0; v < Nverts - 1; v++) {
5819 total_length += (vertices.at(v + 1) - vertices.at(v)).magnitude();
5824 if (all_radii_too_small || total_length < MIN_TUBE_LENGTH_FOR_GEOMETRY) {
5825 return std::vector<uint>();
5828 std::vector<uint> objIDs;
5829 objIDs.reserve(Nverts - 1);
5831 for (
uint v = 0; v < Nverts - 1; v++) {
5832 if ((vertices.at(v + 1) - vertices.at(v)).magnitude() < 1e-6f) {
5835 float r0 = std::max(radii.at(v), MIN_TUBE_RADIUS_FOR_GEOMETRY);
5836 float r1 = std::max(radii.at(v + 1), MIN_TUBE_RADIUS_FOR_GEOMETRY);
5837 objIDs.push_back(context_ptr->
addConeObject(radial_subdivisions, vertices.at(v), vertices.at(v + 1), r0, r1, colors.at(v)));
5843bool PlantArchitecture::detectGroundCollision(
uint objID) {
5844 std::vector<uint> objIDs = {objID};
5845 return detectGroundCollision(objIDs);
5848bool PlantArchitecture::detectGroundCollision(
const std::vector<uint> &objID)
const {
5849 for (
uint ID: objID) {
5852 for (
uint UUID: UUIDs) {
5854 for (
const vec3 &v: vertices) {
5855 if (v.
z < ground_clipping_height) {
5867 std::string label_lower = object_data_label;
5868 std::transform(label_lower.begin(), label_lower.end(), label_lower.begin(), ::tolower);
5871 if (label_lower ==
"all") {
5873 for (
auto &item: output_object_data) {
5880 if (output_object_data.find(object_data_label) == output_object_data.end()) {
5881 helios_runtime_error(
"ERROR (PlantArchitecture::optionalOutputObjectData): Output object data of '" + object_data_label +
"' is not a valid option.");
5884 output_object_data.at(object_data_label) =
true;
5888 for (
const auto &label: object_data_labels) {
5896 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
5897 delete collision_detection_ptr;
5898 collision_detection_ptr =
nullptr;
5899 owns_collision_detection =
false;
5906 owns_collision_detection =
true;
5907 collision_detection_enabled =
true;
5908 collision_target_UUIDs = target_object_UUIDs;
5909 collision_target_object_IDs = target_object_IDs;
5912 petiole_collision_detection_enabled = enable_petiole_collision;
5913 fruit_collision_detection_enabled = enable_fruit_collision;
5922 std::vector<uint> static_obstacles;
5923 static_obstacles.insert(static_obstacles.end(), target_object_UUIDs.begin(), target_object_UUIDs.end());
5924 static_obstacles.insert(static_obstacles.end(), target_object_IDs.begin(), target_object_IDs.end());
5927 rebuildBVHForTimestep();
5930 if (solid_obstacle_avoidance_enabled) {
5931 static_obstacles.insert(static_obstacles.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
5939 for (
uint plant_id: plant_ids) {
5941 if (!plant_primitives.empty()) {
5942 collision_detection_ptr->
registerTree(plant_id, plant_primitives);
5948 }
catch (
const std::exception &e) {
5949 helios_runtime_error(
"ERROR (PlantArchitecture::enableSoftCollisionAvoidance): Failed to create CollisionDetection instance: " + std::string(e.what()));
5954 collision_detection_enabled =
false;
5957 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
5958 delete collision_detection_ptr;
5959 owns_collision_detection =
false;
5962 collision_detection_ptr =
nullptr;
5963 collision_target_UUIDs.clear();
5964 collision_target_object_IDs.clear();
5966 if (printmessages) {
5967 std::cout <<
"Collision detection disabled for plant growth and internal instance cleaned up" << std::endl;
5972 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
5973 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): cone_half_angle_deg must be between 0 and 180 degrees.");
5975 if (look_ahead_distance <= 0.0f) {
5976 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): sample_count must be positive.");
5978 if (inertia_weight < 0.0f || inertia_weight > 1.0f) {
5979 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): inertia_weight must be between 0.0 and 1.0.");
5982 collision_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5983 collision_cone_height = look_ahead_distance;
5984 collision_sample_count = sample_count;
5985 collision_inertia_weight = inertia_weight;
5989 if (collision_detection_ptr ==
nullptr) {
5990 helios_runtime_error(
"ERROR (PlantArchitecture::setStaticObstacles): Collision detection must be enabled before setting static obstacles.");
5995 if (printmessages) {
5996 std::cout <<
"Marked " << target_UUIDs.size() <<
" primitives as static obstacles for collision detection" << std::endl;
6001 return collision_detection_ptr;
6005 collision_include_internodes = include_internodes;
6006 collision_include_leaves = include_leaves;
6007 collision_include_petioles = include_petioles;
6008 collision_include_flowers = include_flowers;
6009 collision_include_fruit = include_fruit;
6014 if (printmessages) {
6015 std::cout <<
"Set collision-relevant organs: internodes=" << (include_internodes ?
"yes" :
"no") <<
", leaves=" << (include_leaves ?
"yes" :
"no") <<
", petioles=" << (include_petioles ?
"yes" :
"no")
6016 <<
", flowers=" << (include_flowers ?
"yes" :
"no") <<
", fruit=" << (include_fruit ?
"yes" :
"no") << std::endl;
6022 solid_obstacle_avoidance_enabled =
true;
6023 solid_obstacle_UUIDs = obstacle_UUIDs;
6024 solid_obstacle_avoidance_distance = avoidance_distance;
6025 solid_obstacle_fruit_adjustment_enabled = enable_fruit_adjustment;
6026 solid_obstacle_pruning_enabled = enable_obstacle_pruning;
6029 if (collision_detection_ptr ==
nullptr) {
6033 owns_collision_detection =
true;
6034 collision_detection_enabled =
true;
6042 rebuildBVHForTimestep();
6043 }
catch (std::exception &e) {
6044 helios_runtime_error(
"ERROR (PlantArchitecture::enableSolidObstacleAvoidance): Failed to create CollisionDetection instance: " + std::string(e.what()));
6049 if (collision_detection_enabled && collision_detection_ptr !=
nullptr && collision_detection_ptr->
isTreeBasedBVHEnabled()) {
6050 std::vector<uint> static_obstacles;
6051 static_obstacles.insert(static_obstacles.end(), collision_target_UUIDs.begin(), collision_target_UUIDs.end());
6052 static_obstacles.insert(static_obstacles.end(), collision_target_object_IDs.begin(), collision_target_object_IDs.end());
6053 static_obstacles.insert(static_obstacles.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
6059void PlantArchitecture::clearBVHCache()
const {
6060 bvh_cached_for_current_growth =
false;
6061 cached_target_geometry.clear();
6062 cached_filtered_geometry.clear();
6066void PlantArchitecture::rebuildBVHForTimestep() {
6067 if (!collision_detection_enabled || collision_detection_ptr ==
nullptr) {
6073 std::vector<uint> target_geometry;
6076 if (solid_obstacle_avoidance_enabled && !solid_obstacle_UUIDs.empty()) {
6077 target_geometry.insert(target_geometry.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
6080 if (!collision_target_UUIDs.empty()) {
6082 std::vector<uint> valid_targets;
6083 for (
uint uuid: collision_target_UUIDs) {
6085 valid_targets.push_back(uuid);
6089 target_geometry.insert(target_geometry.end(), valid_targets.begin(), valid_targets.end());
6090 }
else if (!collision_target_object_IDs.empty()) {
6092 for (
uint objID: collision_target_object_IDs) {
6095 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
6101 std::vector<uint> preserved_solid_obstacles = target_geometry;
6102 target_geometry.clear();
6106 if (collision_include_internodes) {
6108 target_geometry.insert(target_geometry.end(), internode_uuids.begin(), internode_uuids.end());
6110 if (collision_include_leaves) {
6112 target_geometry.insert(target_geometry.end(), leaf_uuids.begin(), leaf_uuids.end());
6114 if (collision_include_petioles) {
6116 target_geometry.insert(target_geometry.end(), petiole_uuids.begin(), petiole_uuids.end());
6118 if (collision_include_flowers) {
6120 target_geometry.insert(target_geometry.end(), flower_uuids.begin(), flower_uuids.end());
6122 if (collision_include_fruit) {
6124 target_geometry.insert(target_geometry.end(), fruit_uuids.begin(), fruit_uuids.end());
6126 }
catch (
const std::exception &e) {
6127 if (printmessages) {
6128 std::cout <<
"Warning: Exception in organ filtering, falling back to all geometry: " << e.what() << std::endl;
6134 target_geometry.insert(target_geometry.end(), preserved_solid_obstacles.begin(), preserved_solid_obstacles.end());
6137 std::vector<uint> all_context_geometry = context_ptr->
getAllUUIDs();
6138 std::set<uint> all_plant_geometry_set;
6141 all_plant_geometry_set.insert(all_plant.begin(), all_plant.end());
6142 }
catch (
const std::exception &e) {
6143 if (printmessages) {
6144 std::cout <<
"Warning: Could not get plant geometry for external obstacle filtering: " << e.what() << std::endl;
6148 for (
uint uuid: all_context_geometry) {
6149 if (all_plant_geometry_set.find(uuid) == all_plant_geometry_set.end()) {
6150 target_geometry.push_back(uuid);
6155 if (!target_geometry.empty()) {
6157 std::vector<uint> plant_geometry;
6160 }
catch (
const std::exception &e) {
6161 if (printmessages) {
6162 std::cout <<
"Warning: Could not get plant geometry for hierarchical BVH: " << e.what() << std::endl;
6164 plant_geometry.clear();
6166 std::set<uint> plant_set(plant_geometry.begin(), plant_geometry.end());
6168 std::vector<uint> static_obstacles;
6169 for (
uint uuid: target_geometry) {
6170 if (plant_set.find(uuid) == plant_set.end()) {
6171 static_obstacles.push_back(uuid);
6178 collision_detection_ptr->
updateBVH(target_geometry,
true);
6182 cached_target_geometry = target_geometry;
6183 cached_filtered_geometry = target_geometry;
6184 bvh_cached_for_current_growth =
true;
6189 if (update_frequency < 1) {
6190 helios_runtime_error(
"ERROR (PlantArchitecture::setGeometryUpdateScheduling): update_frequency must be at least 1.");
6193 geometry_update_frequency = update_frequency;
6194 geometry_update_counter = 0;
6200 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6201 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6203 if (look_ahead_distance <= 0.0f) {
6204 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): look_ahead_distance must be positive.");
6206 if (attraction_weight_input < 0.0f || attraction_weight_input > 1.0f) {
6207 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): attraction_weight must be between 0.0 and 1.0.");
6211 attraction_points_enabled =
true;
6212 attraction_points = attraction_points_input;
6213 attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6214 attraction_cone_height = look_ahead_distance;
6215 attraction_weight = attraction_weight_input;
6218 for (
auto &[plantID, plant]: plant_instances) {
6219 plant.attraction_points_enabled =
true;
6220 plant.attraction_points = attraction_points_input;
6221 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6222 plant.attraction_cone_height = look_ahead_distance;
6223 plant.attraction_weight = attraction_weight_input;
6229 attraction_points_enabled =
false;
6230 attraction_points.clear();
6233 for (
auto &[plantID, plant]: plant_instances) {
6234 plant.attraction_points_enabled =
false;
6235 plant.attraction_points.clear();
6240 if (!attraction_points_enabled) {
6241 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Attraction points must be enabled before updating positions.");
6243 if (attraction_points_input.empty()) {
6244 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): attraction_points cannot be empty.");
6248 attraction_points = attraction_points_input;
6251 for (
auto &[plantID, plant]: plant_instances) {
6252 if (plant.attraction_points_enabled) {
6253 plant.attraction_points = attraction_points_input;
6259 if (!attraction_points_enabled) {
6260 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Attraction points must be enabled before updating positions.");
6262 if (attraction_points_input.empty()) {
6263 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): attraction_points cannot be empty.");
6267 attraction_points.insert(attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6270 for (
auto &[plantID, plant]: plant_instances) {
6271 if (plant.attraction_points_enabled) {
6272 plant.attraction_points.insert(plant.attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6278 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6279 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): view_half_angle_deg must be between 0 and 180 degrees.");
6281 if (look_ahead_distance <= 0.0f) {
6282 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): look_ahead_distance must be positive.");
6284 if (attraction_weight_input < 0.0f || attraction_weight_input > 1.0f) {
6285 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): attraction_weight must be between 0.0 and 1.0.");
6287 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6288 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): obstacle_reduction_factor must be between 0.0 and 1.0.");
6292 attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6293 attraction_cone_height = look_ahead_distance;
6294 attraction_weight = attraction_weight_input;
6295 attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6298 for (
auto &[plantID, plant]: plant_instances) {
6299 if (plant.attraction_points_enabled) {
6300 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6301 plant.attraction_cone_height = look_ahead_distance;
6302 plant.attraction_weight = attraction_weight_input;
6303 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6307 if (printmessages) {
6308 std::cout <<
"Updated attraction parameters: cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input <<
", obstacle_reduction=" << obstacle_reduction_factor << std::endl;
6309 if (!plant_instances.empty()) {
6310 std::cout <<
"Applied to " << plant_instances.size() <<
" existing plants with attraction points enabled" << std::endl;
6318 if (plant_instances.find(plantID) == plant_instances.end()) {
6319 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6322 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6323 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6325 if (look_ahead_distance <= 0.0f) {
6326 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): look_ahead_distance must be greater than 0.");
6328 if (attraction_points_input.empty()) {
6329 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): attraction_points cannot be empty.");
6332 auto &plant = plant_instances.at(plantID);
6333 plant.attraction_points_enabled =
true;
6334 plant.attraction_points = attraction_points_input;
6335 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6336 plant.attraction_cone_height = look_ahead_distance;
6337 plant.attraction_weight = attraction_weight_input;
6339 if (printmessages) {
6340 std::cout <<
"Enabled attraction points for plant " << plantID <<
" with " << attraction_points_input.size() <<
" target positions" << std::endl;
6341 std::cout <<
"Plant " << plantID <<
" attraction parameters: cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input << std::endl;
6346 if (plant_instances.find(plantID) == plant_instances.end()) {
6347 helios_runtime_error(
"ERROR (PlantArchitecture::disableAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6350 auto &plant = plant_instances.at(plantID);
6351 plant.attraction_points_enabled =
false;
6352 plant.attraction_points.clear();
6354 if (printmessages) {
6355 std::cout <<
"Disabled attraction points for plant " << plantID <<
" - will use natural growth patterns" << std::endl;
6360 if (plant_instances.find(plantID) == plant_instances.end()) {
6361 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6364 auto &plant = plant_instances.at(plantID);
6365 if (!plant.attraction_points_enabled) {
6366 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Attraction points must be enabled for plant " + std::to_string(plantID) +
" before updating positions.");
6368 if (attraction_points_input.empty()) {
6369 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): attraction_points cannot be empty.");
6372 plant.attraction_points = attraction_points_input;
6376 if (plant_instances.find(plantID) == plant_instances.end()) {
6377 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6380 auto &plant = plant_instances.at(plantID);
6381 if (!plant.attraction_points_enabled) {
6382 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Attraction points must be enabled for plant " + std::to_string(plantID) +
" before updating positions.");
6384 if (attraction_points_input.empty()) {
6385 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): attraction_points cannot be empty.");
6388 plant.attraction_points.insert(plant.attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6392 if (plant_instances.find(plantID) == plant_instances.end()) {
6393 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6396 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6397 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): view_half_angle_deg must be between 0 and 180 degrees.");
6399 if (look_ahead_distance <= 0.0f) {
6400 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): look_ahead_distance must be greater than 0.");
6402 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6403 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): obstacle_reduction_factor must be between 0 and 1.");
6406 auto &plant = plant_instances.at(plantID);
6407 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6408 plant.attraction_cone_height = look_ahead_distance;
6409 plant.attraction_weight = attraction_weight_input;
6410 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6412 if (printmessages) {
6413 std::cout <<
"Updated attraction parameters for plant " << plantID <<
": cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input
6414 <<
", obstacle_reduction=" << obstacle_reduction_factor << std::endl;
6418void PlantArchitecture::setPlantAttractionPoints(
uint plantID,
const std::vector<helios::vec3> &attraction_points_input,
float view_half_angle_deg,
float look_ahead_distance,
float attraction_weight_input,
float obstacle_reduction_factor) {
6419 if (plant_instances.find(plantID) == plant_instances.end()) {
6420 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6423 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6424 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6426 if (look_ahead_distance <= 0.0f) {
6427 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): look_ahead_distance must be greater than 0.");
6429 if (attraction_points_input.empty()) {
6430 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): attraction_points cannot be empty.");
6432 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6433 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): obstacle_reduction_factor must be between 0 and 1.");
6436 auto &plant = plant_instances.at(plantID);
6437 plant.attraction_points_enabled =
true;
6438 plant.attraction_points = attraction_points_input;
6439 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6440 plant.attraction_cone_height = look_ahead_distance;
6441 plant.attraction_weight = attraction_weight_input;
6442 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6446 printmessages =
false;
6447 if (collision_detection_ptr !=
nullptr) {
6453 printmessages =
true;
6454 if (collision_detection_ptr !=
nullptr) {