19#include <unordered_set>
22using namespace helios;
24static float clampOffset(
int count_per_axis,
float offset) {
25 if (count_per_axis > 2) {
26 float denom = 0.5f * float(count_per_axis) - 1.f;
27 if (offset * denom > 1.f) {
34float PlantArchitecture::interpolateTube(
const std::vector<float> &P,
const float frac) {
35 assert(frac >= 0 && frac <= 1);
38 float dl = 1.f / float(P.size() - 1);
41 for (
int i = 0; i < P.size() - 1; i++) {
48 if (frac >= f && (frac <= fplus || std::abs(frac - fplus) < 0.0001)) {
49 float V = P.at(i) + (frac - f) / (fplus - f) * (P.at(i + 1) - P.at(i));
60vec3 PlantArchitecture::interpolateTube(
const std::vector<vec3> &P,
const float frac) {
61 assert(frac >= 0 && frac <= 1);
65 for (
int i = 0; i < P.size() - 1; i++) {
66 dl += (P.at(i + 1) - P.at(i)).magnitude();
70 for (
int i = 0; i < P.size() - 1; i++) {
71 float dseg = (P.at(i + 1) - P.at(i)).magnitude();
73 float fplus = f + dseg / dl;
79 if (frac >= f && (frac <= fplus || fabs(frac - fplus) < 0.0001)) {
80 vec3 V = P.at(i) + (frac - f) / (fplus - f) * (P.at(i + 1) - P.at(i));
96 initializePlantModelRegistrations();
98 output_object_data[
"age"] =
false;
99 output_object_data[
"rank"] =
false;
100 output_object_data[
"plantID"] =
false;
101 output_object_data[
"leafID"] =
false;
102 output_object_data[
"peduncleID"] =
false;
103 output_object_data[
"closedflowerID"] =
false;
104 output_object_data[
"openflowerID"] =
false;
105 output_object_data[
"fruitID"] =
false;
106 output_object_data[
"carbohydrate_concentration"] =
false;
111 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
112 delete collision_detection_ptr;
113 collision_detection_ptr =
nullptr;
114 owns_collision_detection =
false;
119 leaf_aspect_ratio.initialize(1.f, generator);
133 if (generator !=
nullptr) {
143 internode.pitch.initialize(20, generator);
144 internode.phyllotactic_angle.initialize(137.5, generator);
145 internode.radius_initial.initialize(0.001, generator);
151 petiole.petioles_per_internode = 1;
152 petiole.pitch.initialize(90, generator);
153 petiole.radius.initialize(0.001, generator);
154 petiole.length.initialize(0.05, generator);
155 petiole.curvature.initialize(0, generator);
156 petiole.taper.initialize(0, generator);
157 petiole.color = RGB::forestgreen;
159 petiole.radial_subdivisions = 7;
162 leaf.leaves_per_petiole.initialize(1, generator);
163 leaf.pitch.initialize(0, generator);
164 leaf.yaw.initialize(0, generator);
165 leaf.roll.initialize(0, generator);
166 leaf.leaflet_offset.initialize(0, generator);
167 leaf.leaflet_scale = 1;
168 leaf.prototype_scale.initialize(0.05, generator);
172 peduncle.length.initialize(0.05, generator);
173 peduncle.radius.initialize(0.001, generator);
174 peduncle.pitch.initialize(0, generator);
175 peduncle.roll.initialize(0, generator);
176 peduncle.curvature.initialize(0, generator);
177 petiole.color = RGB::forestgreen;
186 inflorescence.flower_prototype_scale.initialize(0.0075, generator);
187 inflorescence.fruit_prototype_scale.initialize(0.0075, generator);
188 inflorescence.fruit_gravity_factor_fraction.initialize(0, generator);
236 if (a_child_shoot_type_labels.size() != a_child_shoot_type_probabilities.size()) {
237 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Child shoot type labels and probabilities must be the same size.");
238 }
else if (a_child_shoot_type_labels.empty()) {
239 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Input argument vectors were empty.");
240 }
else if (
sum(a_child_shoot_type_probabilities) != 1.f) {
241 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Child shoot type probabilities must sum to 1.");
244 this->child_shoot_type_labels = a_child_shoot_type_labels;
245 this->child_shoot_type_probabilities = a_child_shoot_type_probabilities;
249 if (plant_count_xy.
x <= 0 || plant_count_xy.
y <= 0) {
250 helios_runtime_error(
"ERROR (PlantArchitecture::buildPlantCanopyFromLibrary): Plant count must be greater than zero.");
253 vec2 canopy_extent(plant_spacing_xy.
x *
float(plant_count_xy.
x - 1), plant_spacing_xy.
y *
float(plant_count_xy.
y - 1));
255 std::vector<uint> plantIDs;
256 plantIDs.reserve(plant_count_xy.
x * plant_count_xy.
y);
257 for (
int j = 0; j < plant_count_xy.
y; j++) {
258 for (
int i = 0; i < plant_count_xy.
x; i++) {
259 if (context_ptr->
randu() < germination_rate) {
260 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));
273 std::vector<uint> plantIDs;
274 plantIDs.reserve(plant_count);
275 for (
int i = 0; i < plant_count; i++) {
276 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);
285 if (this->shoot_types.find(shoot_type_label) != this->shoot_types.end()) {
287 this->shoot_types.at(shoot_type_label) = shoot_params;
289 this->shoot_types.emplace(shoot_type_label, shoot_params);
294 std::vector<vec3> nodes = parent_shoot_ptr->shoot_internode_vertices.at(
shoot_index.
x);
297 int s_minus = parent_shoot_ptr->shoot_internode_vertices.at(p_minus).size() - 1;
298 nodes.insert(nodes.begin(), parent_shoot_ptr->shoot_internode_vertices.at(p_minus).at(s_minus));
304 std::vector<float> node_radii = parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x);
307 int s_minus = parent_shoot_ptr->shoot_internode_radii.at(p_minus).size() - 1;
308 node_radii.insert(node_radii.begin(), parent_shoot_ptr->shoot_internode_radii.at(p_minus).at(s_minus));
325 if (petiole_index >= this->peduncle_vertices.size()) {
328 if (bud_index >= this->peduncle_vertices.at(petiole_index).size()) {
329 helios_runtime_error(
"ERROR (Phytomer::getPeduncleAxisVector): Floral bud index out of range.");
331 return getAxisVector(stem_fraction, this->peduncle_vertices.at(petiole_index).at(bud_index));
335 assert(stem_fraction >= 0 && stem_fraction <= 1);
338 float frac_plus, frac_minus;
339 if (stem_fraction + df <= 1) {
340 frac_minus = stem_fraction;
341 frac_plus = stem_fraction + df;
343 frac_minus = stem_fraction - df;
344 frac_plus = stem_fraction;
347 const vec3 node_minus = PlantArchitecture::interpolateTube(axis_vertices, frac_minus);
348 const vec3 node_plus = PlantArchitecture::interpolateTube(axis_vertices, frac_plus);
350 vec3 norm = node_plus - node_minus;
357 return parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x).front();
363 for (
int i = 0; i < node_vertices.size() - 1; i++) {
364 length += (node_vertices.at(i + 1) - node_vertices.at(i)).magnitude();
375 return PlantArchitecture::interpolateTube(parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x), stem_fraction);
381 for (
auto &petiole: leaf_objIDs) {
382 for (
auto &leaf_objID: petiole) {
384 leaf_area += context_ptr->
getObjectArea(leaf_objID) /
powi(current_leaf_scale_factor.at(p), 2);
395 if (petiole_index >= leaf_bases.size()) {
397 }
else if (leaf_index >= leaf_bases.at(petiole_index).size()) {
401 return leaf_bases.at(petiole_index).at(leaf_index);
405 for (
auto &petiole: axillary_vegetative_buds) {
406 for (
auto &bud: petiole) {
413 if (petiole_index >= axillary_vegetative_buds.size()) {
416 if (bud_index >= axillary_vegetative_buds.at(petiole_index).size()) {
427 for (
auto &petiole: floral_buds) {
428 for (
auto &fbud: petiole) {
429 if (!fbud.isterminal) {
437 if (petiole_index >= floral_buds.size()) {
440 if (bud_index >= floral_buds.at(petiole_index).size()) {
448 if (fbud.state == state) {
450 }
else if (state == BUD_DORMANT || state == BUD_ACTIVE) {
456 if (plantarchitecture_ptr->carbon_model_enabled) {
457 if (state == BUD_FLOWER_CLOSED || (fbud.state == BUD_ACTIVE && state == BUD_FLOWER_OPEN)) {
459 float flower_cost = calculateFlowerConstructionCosts(fbud);
460 plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC -= flower_cost;
461 }
else if (state == BUD_FRUITING) {
463 float fruit_cost = calculateFruitConstructionCosts(fbud);
464 fbud.previous_fruit_scale_factor = fbud.current_fruit_scale_factor;
465 if (plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC > fruit_cost) {
466 plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC -= fruit_cost;
475 fbud.inflorescence_objIDs.resize(0);
476 fbud.inflorescence_bases.resize(0);
478 if (plantarchitecture_ptr->build_context_geometry_peduncle) {
480 fbud.peduncle_objIDs.resize(0);
485 if (state != BUD_DEAD) {
488 updateInflorescence(fbud);
489 fbud.time_counter = 0;
490 if (fbud.state == BUD_FRUITING) {
496helios::vec3 Phytomer::calculateCollisionAvoidanceDirection(
const helios::vec3 &internode_base_origin,
const helios::vec3 &internode_axis,
bool &collision_detection_active)
const {
497 vec3 collision_optimal_direction;
498 collision_detection_active =
false;
500 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
503 if (!plantarchitecture_ptr->bvh_cached_for_current_growth) {
504 if (plantarchitecture_ptr->printmessages) {
505 std::cout <<
"WARNING: BVH not cached - this indicates rebuildBVHForTimestep() was not called" << std::endl;
507 return collision_optimal_direction;
511 std::vector<uint> filtered_geometry;
515 float look_ahead_distance = plantarchitecture_ptr->collision_cone_height;
519 float max_relevant_distance = look_ahead_distance * 1.1f;
523 filtered_geometry = plantarchitecture_ptr->collision_detection_ptr->
filterGeometryByDistance(internode_base_origin, max_relevant_distance, plantarchitecture_ptr->cached_target_geometry);
527 plantarchitecture_ptr->cached_filtered_geometry = filtered_geometry;
529 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
531 vec3 apex = internode_base_origin;
532 vec3 central_axis = internode_axis;
534 float height = plantarchitecture_ptr->collision_cone_height;
535 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
536 int samples = plantarchitecture_ptr->collision_sample_count;
539 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
542 if (optimal_result.confidence > 0.0f) {
543 collision_optimal_direction = optimal_result.direction;
545 collision_detection_active =
true;
549 return collision_optimal_direction;
552helios::vec3 Phytomer::calculatePetioleCollisionAvoidanceDirection(
const helios::vec3 &petiole_base_origin,
const helios::vec3 &proposed_petiole_axis,
bool &collision_detection_active)
const {
553 vec3 collision_optimal_direction;
554 collision_detection_active =
false;
556 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
558 std::vector<uint> target_geometry;
559 if (!plantarchitecture_ptr->collision_target_UUIDs.empty()) {
560 target_geometry = plantarchitecture_ptr->collision_target_UUIDs;
561 }
else if (!plantarchitecture_ptr->collision_target_object_IDs.empty()) {
562 for (
uint objID: plantarchitecture_ptr->collision_target_object_IDs) {
564 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
572 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
574 vec3 apex = petiole_base_origin;
575 vec3 central_axis = proposed_petiole_axis;
577 float height = plantarchitecture_ptr->collision_cone_height;
578 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
579 int samples = plantarchitecture_ptr->collision_sample_count;
582 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
585 if (optimal_result.confidence > 0.0f) {
586 collision_optimal_direction = optimal_result.direction;
588 collision_detection_active =
true;
592 return collision_optimal_direction;
595helios::vec3 Phytomer::calculateFruitCollisionAvoidanceDirection(
const helios::vec3 &fruit_base_origin,
const helios::vec3 &proposed_fruit_axis,
bool &collision_detection_active)
const {
596 vec3 collision_optimal_direction;
597 collision_detection_active =
false;
600 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
602 std::vector<uint> target_geometry;
603 if (!plantarchitecture_ptr->collision_target_UUIDs.empty()) {
604 target_geometry = plantarchitecture_ptr->collision_target_UUIDs;
605 }
else if (!plantarchitecture_ptr->collision_target_object_IDs.empty()) {
606 for (
uint objID: plantarchitecture_ptr->collision_target_object_IDs) {
608 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
616 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
618 vec3 apex = fruit_base_origin;
619 vec3 central_axis = proposed_fruit_axis;
621 float height = plantarchitecture_ptr->collision_cone_height;
622 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
623 int samples = plantarchitecture_ptr->collision_sample_count;
626 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
629 if (optimal_result.confidence > 0.0f) {
630 collision_optimal_direction = optimal_result.direction;
632 collision_detection_active =
true;
636 static int no_collision_count = 0;
637 if (optimal_result.confidence <= 0.0f) {
638 no_collision_count++;
641 static int no_bvh_count = 0;
645 return collision_optimal_direction;
648bool Phytomer::applySolidObstacleAvoidance(
const helios::vec3 ¤t_position,
helios::vec3 &internode_axis)
const {
649 if (!plantarchitecture_ptr->solid_obstacle_avoidance_enabled || plantarchitecture_ptr->solid_obstacle_UUIDs.empty()) {
659 vec3 growth_direction = internode_axis;
663 float nearest_obstacle_distance;
664 vec3 nearest_obstacle_direction;
667 float hard_detection_cone_angle =
deg2rad(30.0f);
668 float detection_distance = plantarchitecture_ptr->solid_obstacle_avoidance_distance;
670 if (plantarchitecture_ptr->collision_detection_ptr !=
nullptr && plantarchitecture_ptr->collision_detection_ptr->
findNearestSolidObstacleInCone(current_position, growth_direction, hard_detection_cone_angle, detection_distance,
671 plantarchitecture_ptr->solid_obstacle_UUIDs, nearest_obstacle_distance, nearest_obstacle_direction)) {
674 float buffer_distance = detection_distance * 0.05f;
677 float normalized_distance = nearest_obstacle_distance / detection_distance;
678 float buffer_threshold = buffer_distance / detection_distance;
680 vec3 avoidance_direction;
681 float rotation_fraction;
683 if (nearest_obstacle_distance <= buffer_distance) {
686 avoidance_direction = current_position - (current_position + nearest_obstacle_direction * nearest_obstacle_distance);
687 if (avoidance_direction.
magnitude() < 0.001f) {
689 avoidance_direction =
cross(growth_direction, nearest_obstacle_direction);
690 if (avoidance_direction.
magnitude() < 0.001f) {
691 avoidance_direction =
make_vec3(0, 0, 1);
697 rotation_fraction = 1.0f;
700 float buffer_blend_factor = 0.8f;
701 internode_axis = (1.0f - buffer_blend_factor) * growth_direction + buffer_blend_factor * avoidance_direction;
708 float dot_with_obstacle = normalize(growth_direction) * normalize(nearest_obstacle_direction);
709 float angle_deficit =
asin_safe(fabs(dot_with_obstacle));
712 vec3 rotation_axis =
cross(growth_direction, -nearest_obstacle_direction);
714 if (rotation_axis.
magnitude() > 0.001f) {
720 if (rotation_axis.
magnitude() > 0.001f) {
724 float surface_threshold_fraction = 0.2f;
726 if (normalized_distance <= surface_threshold_fraction) {
728 rotation_fraction = 1.0f;
731 float remaining_distance = normalized_distance - surface_threshold_fraction;
732 float max_remaining_distance = 1.0f - surface_threshold_fraction;
735 float distance_factor = remaining_distance / max_remaining_distance;
736 float min_rotation_fraction = 0.05f;
739 rotation_fraction = min_rotation_fraction + (1.0f - min_rotation_fraction) * exp(-3.0f * distance_factor);
743 float rotation_this_step = angle_deficit * rotation_fraction;
746 internode_axis =
rotatePointAboutLine(internode_axis, nullorigin, rotation_axis, rotation_this_step);
758 vec3 attraction_direction;
759 attraction_active =
false;
762 if (plantarchitecture_ptr->plant_instances.find(plantID) != plantarchitecture_ptr->plant_instances.end()) {
763 const auto &plant = plantarchitecture_ptr->plant_instances.at(plantID);
764 if (plant.attraction_points_enabled && !plant.attraction_points.empty()) {
766 vec3 look_direction = internode_axis;
768 float half_angle_degrees =
rad2deg(plant.attraction_cone_half_angle_rad);
769 float look_ahead_distance = plant.attraction_cone_height;
771 vec3 direction_to_closest;
772 if (plantarchitecture_ptr->
detectAttractionPointsInCone(plant.attraction_points, internode_base_origin, look_direction, look_ahead_distance, half_angle_degrees, direction_to_closest)) {
773 attraction_direction = direction_to_closest;
775 attraction_active =
true;
777 return attraction_direction;
782 if (!plantarchitecture_ptr->attraction_points_enabled || plantarchitecture_ptr->attraction_points.empty()) {
783 return attraction_direction;
787 vec3 look_direction = internode_axis;
789 float half_angle_degrees =
rad2deg(plantarchitecture_ptr->attraction_cone_half_angle_rad);
790 float look_ahead_distance = plantarchitecture_ptr->attraction_cone_height;
792 vec3 direction_to_closest;
793 if (plantarchitecture_ptr->
detectAttractionPointsInCone(plantarchitecture_ptr->attraction_points, internode_base_origin, look_direction, look_ahead_distance, half_angle_degrees, direction_to_closest)) {
794 attraction_direction = direction_to_closest;
796 attraction_active =
true;
799 return attraction_direction;
805 if (attraction_points.empty()) {
809 if (look_ahead_distance <= 0.0f) {
811 std::cerr <<
"WARNING (PlantArchitecture::detectAttractionPointsInCone): Invalid look-ahead distance (<= 0)" << std::endl;
816 if (half_angle_degrees <= 0.0f || half_angle_degrees >= 180.0f) {
818 std::cerr <<
"WARNING (PlantArchitecture::detectAttractionPointsInCone): Invalid half-angle (must be in range (0, 180) degrees)" << std::endl;
824 float half_angle_rad = half_angle_degrees *
M_PI / 180.0f;
827 vec3 axis = look_direction;
831 bool found_any =
false;
832 float min_angular_distance = std::numeric_limits<float>::max();
836 for (
const vec3 &point: attraction_points) {
838 vec3 to_point = point - vertex;
839 float distance_to_point = to_point.
magnitude();
842 if (distance_to_point < 1e-6f || distance_to_point > look_ahead_distance) {
847 vec3 direction_to_point = to_point;
851 float cos_angle = axis * direction_to_point;
854 cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
856 float angle = std::acos(cos_angle);
859 if (angle <= half_angle_rad) {
863 if (angle < min_angular_distance) {
864 min_angular_distance = angle;
865 closest_point = point;
872 direction_to_closest = closest_point - vertex;
884 if (attraction_points_input.empty()) {
888 if (look_ahead_distance <= 0.0f) {
890 std::cerr <<
"WARNING (PlantArchitecture::detectAttractionPointsInCone): Invalid look-ahead distance (<= 0)" << std::endl;
895 if (half_angle_degrees <= 0.0f || half_angle_degrees >= 180.0f) {
897 std::cerr <<
"WARNING (PlantArchitecture::detectAttractionPointsInCone): Invalid half-angle (must be in range (0, 180) degrees)" << std::endl;
903 float half_angle_rad = half_angle_degrees *
M_PI / 180.0f;
906 vec3 axis = look_direction;
910 bool found_any =
false;
911 float min_angular_distance = std::numeric_limits<float>::max();
915 for (
const vec3 &point: attraction_points_input) {
917 vec3 to_point = point - vertex;
918 float distance_to_point = to_point.
magnitude();
921 if (distance_to_point <= 1e-6 || distance_to_point > look_ahead_distance) {
926 vec3 direction_to_point = to_point;
930 float cos_angle = axis * direction_to_point;
933 cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
935 float angle = std::acos(cos_angle);
938 if (angle <= half_angle_rad) {
942 if (angle < min_angular_distance) {
943 min_angular_distance = angle;
944 closest_point = point;
951 direction_to_closest = closest_point - vertex;
960 auto shoot_tree_ptr = &plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree;
963 vec3 parent_internode_axis;
964 vec3 parent_petiole_axis;
965 vec3 internode_base_position;
966 if (phytomers.empty()) {
968 if (parent_shoot_ID == -1) {
970 parent_internode_axis =
make_vec3(0, 0, 1);
971 parent_petiole_axis =
make_vec3(0, -1, 0);
974 assert(parent_shoot_ID < shoot_tree_ptr->size() && parent_node_index < shoot_tree_ptr->at(parent_shoot_ID)->phytomers.size());
975 parent_internode_axis = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
976 parent_petiole_axis = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->getPetioleAxisVector(0.f, parent_petiole_index);
978 internode_base_position = base_position;
981 parent_internode_axis = phytomers.back()->getInternodeAxisVector(1.f);
982 parent_petiole_axis = phytomers.back()->getPetioleAxisVector(0.f, 0);
983 internode_base_position = shoot_internode_vertices.back().back();
986 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,
987 internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, rank, plantarchitecture_ptr, context_ptr);
988 shoot_tree_ptr->at(ID)->phytomers.push_back(phytomer);
989 phytomer = shoot_tree_ptr->at(ID)->phytomers.back();
992 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
995 for (
auto &vbud: petiole) {
996 phytomer->setVegetativeBudState(BUD_DORMANT, vbud);
997 vbud.shoot_type_label = child_shoot_type_label;
1001 if (plantarchitecture_ptr->carbon_model_enabled) {
1002 if (sampleVegetativeBudBreak_carb(phytomer->shoot_index.x)) {
1004 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1006 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1011 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1013 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1021 uint petiole_index = 0;
1022 for (
auto &petiole: phytomer->floral_buds) {
1024 for (
auto &fbud: petiole) {
1026 phytomer->setFloralBudState(BUD_DORMANT, fbud);
1030 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1033 fbud.parent_index = petiole_index;
1034 fbud.bud_index = bud_index;
1046 if (plantarchitecture_ptr->build_context_geometry_internode && context_ptr->
doesObjectExist(internode_tube_objID)) {
1048 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1049 context_ptr->
setObjectData(internode_tube_objID,
"age", phytomer->age);
1051 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1052 context_ptr->
setObjectData(internode_tube_objID,
"rank", rank);
1054 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1055 context_ptr->
setObjectData(internode_tube_objID,
"plantID", (
int) plantID);
1058 if (plantarchitecture_ptr->build_context_geometry_petiole) {
1059 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1060 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"age", phytomer->age);
1062 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1063 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"rank", phytomer->rank);
1065 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1066 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"plantID", (
int) plantID);
1069 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1070 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"age", phytomer->age);
1072 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1073 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"rank", phytomer->rank);
1075 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1076 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"plantID", (
int) plantID);
1079 if (plantarchitecture_ptr->output_object_data.at(
"leafID")) {
1080 for (
auto &petiole: phytomer->leaf_objIDs) {
1081 for (
uint objID: petiole) {
1088 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);
1092 if (plantarchitecture_ptr->carbon_model_enabled) {
1093 this->carbohydrate_pool_molC -= phytomer->calculatePhytomerConstructionCosts();
1096 return (
int) phytomers.size() - 1;
1099void Shoot::breakDormancy() {
1102 int phytomer_ind = 0;
1103 for (
auto &phytomer: phytomers) {
1104 for (
auto &petiole: phytomer->floral_buds) {
1105 for (
auto &fbud: petiole) {
1106 if (fbud.state != BUD_DEAD) {
1107 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1109 if (meristem_is_alive && fbud.isterminal) {
1110 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1112 fbud.time_counter = 0;
1115 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1116 for (
auto &vbud: petiole) {
1117 if (vbud.state != BUD_DEAD) {
1118 if (plantarchitecture_ptr->carbon_model_enabled) {
1119 if (sampleVegetativeBudBreak_carb(phytomer_ind)) {
1121 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1123 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1128 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1130 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1137 phytomer->isdormant =
false;
1142void Shoot::makeDormant() {
1145 nodes_this_season = 0;
1147 for (
auto &phytomer: phytomers) {
1148 for (
auto &petiole: phytomer->floral_buds) {
1150 for (
auto &fbud: petiole) {
1151 if (fbud.state != BUD_DORMANT) {
1152 phytomer->setFloralBudState(BUD_DEAD, fbud);
1156 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1157 for (
auto &vbud: petiole) {
1158 if (vbud.state != BUD_DORMANT) {
1159 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1163 if (!plantarchitecture_ptr->plant_instances.at(plantID).is_evergreen) {
1164 phytomer->removeLeaf();
1166 phytomer->isdormant =
true;
1175 this->meristem_is_alive =
false;
1176 this->phyllochron_counter = 0;
1180 for (
auto &phytomer: phytomers) {
1181 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1182 for (
auto &vbud: petiole) {
1183 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1191 for (
int bud = 0; bud < Nbuds; bud++) {
1193 bud_new.isterminal =
true;
1194 bud_new.parent_index = 0;
1195 bud_new.bud_index = bud;
1196 bud_new.base_position = shoot_internode_vertices.back().back();
1197 float pitch_adjustment = 0;
1199 pitch_adjustment =
deg2rad(30);
1201 float yaw_adjustment =
static_cast<float>(bud_new.bud_index) * 2.f *
PI_F /
float(Nbuds);
1203 bud_new.base_rotation = make_AxisRotation(pitch_adjustment, yaw_adjustment, 0);
1204 bud_new.bending_axis =
make_vec3(1, 0, 0);
1206 phytomers.back()->floral_buds.push_back({bud_new});
1213 float shoot_volume = 0;
1214 for (
const auto &phytomer: phytomers) {
1219 return shoot_volume;
1223 float shoot_length = 0;
1224 for (
const auto &phytomer: phytomers) {
1225 shoot_length += phytomer->getInternodeLength();
1227 return shoot_length;
1232 if (parent_shoot_ID >= 0) {
1235 auto parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
1237 const vec3 current_origin = shoot_internode_vertices.front().front();
1238 const vec3 updated_origin = parent_shoot->shoot_internode_vertices.at(this->parent_node_index).back();
1239 vec3 shift = updated_origin - current_origin;
1245 for (
auto &phytomer: shoot_internode_vertices) {
1246 for (
vec3 &node: phytomer) {
1253 if (update_context_geometry && plantarchitecture_ptr->build_context_geometry_internode && context_ptr->
doesObjectExist(internode_tube_objID)) {
1259 for (
int p = 0; p < phytomers.size(); p++) {
1260 vec3 petiole_base = shoot_internode_vertices.at(p).back();
1261 if (parent_shoot_ID >= 0) {
1263 auto parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
1266 phytomers.at(p)->setPetioleBase(petiole_base);
1270 for (
const auto &node: childIDs) {
1271 for (
int child_shoot_ID: node.second) {
1272 plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->updateShootNodes(update_context_geometry);
1278 uint phytomer_count = this->phytomers.size();
1280 uint phytomer_index = 0;
1281 if (shoot_fraction > 0) {
1282 phytomer_index = std::ceil(shoot_fraction *
float(phytomer_count)) - 1;
1285 assert(phytomer_index < phytomer_count);
1287 return this->phytomers.at(phytomer_index)->getInternodeAxisVector(0.5);
1291 for (
int i = node_index; i >= 0; i--) {
1292 shoot->phytomers.at(i)->downstream_leaf_area += leaf_area;
1293 shoot->phytomers.at(i)->downstream_leaf_area = std::max(0.f, shoot->phytomers.at(i)->downstream_leaf_area);
1296 if (shoot->parent_shoot_ID >= 0) {
1297 Shoot *parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(shoot->parent_shoot_ID).get();
1303 if (start_node_index >= phytomers.size()) {
1309 for (
uint p = start_node_index; p < phytomers.size(); p++) {
1311 auto phytomer = phytomers.at(p);
1312 for (
auto &petiole: phytomer->leaf_objIDs) {
1313 for (
uint objID: petiole) {
1321 if (childIDs.find(p) != childIDs.end()) {
1322 for (
int child_shoot_ID: childIDs.at(p)) {
1323 area += plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->sumShootLeafArea(0);
1333 if (start_node_index >= phytomers.size()) {
1339 for (
uint p = start_node_index; p < phytomers.size(); p++) {
1341 if (childIDs.find(p) != childIDs.end()) {
1342 for (
int child_shoot_ID: childIDs.at(p)) {
1343 volume += plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
1352 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,
1353 helios::Context *context_ptr) : rank(rank), context_ptr(context_ptr), plantarchitecture_ptr(plantarchitecture_ptr) {
1354 this->phytomer_parameters = params;
1357 ShootParameters parent_shoot_parameters = parent_shoot->shoot_parameters;
1359 this->internode_radius_initial = internode_radius;
1360 this->internode_length_max = internode_length_max;
1363 this->rank = parent_shoot->rank;
1364 this->plantID = parent_shoot->plantID;
1365 this->parent_shoot_ID = parent_shoot->ID;
1366 this->parent_shoot_ptr = parent_shoot;
1368 bool build_context_geometry_internode = plantarchitecture_ptr->build_context_geometry_internode;
1369 bool build_context_geometry_petiole = plantarchitecture_ptr->build_context_geometry_petiole;
1370 bool build_context_geometry_peduncle = plantarchitecture_ptr->build_context_geometry_peduncle;
1378 uint Ndiv_internode_length = std::max(
uint(1), phytomer_parameters.
internode.length_segments);
1379 uint Ndiv_internode_radius = std::max(
uint(3), phytomer_parameters.
internode.radial_subdivisions);
1380 uint Ndiv_petiole_length = std::max(
uint(1), phytomer_parameters.
petiole.length_segments);
1381 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
1384 if (phytomer_parameters.
internode.length_segments == 0 || phytomer_parameters.
internode.radial_subdivisions < 3) {
1385 build_context_geometry_internode =
false;
1387 if (phytomer_parameters.
petiole.length_segments == 0 || phytomer_parameters.
petiole.radial_subdivisions < 3) {
1388 build_context_geometry_petiole =
false;
1391 if (phytomer_parameters.
petiole.petioles_per_internode < 1) {
1392 build_context_geometry_petiole =
false;
1393 phytomer_parameters.
petiole.petioles_per_internode = 1;
1394 phytomer_parameters.
leaf.leaves_per_petiole = 0;
1397 if (phytomer_parameters.
petiole.petioles_per_internode == 0) {
1398 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Number of petioles per internode must be greater than zero.");
1401 current_internode_scale_factor = internode_length_scale_factor_fraction;
1402 current_leaf_scale_factor.resize(phytomer_parameters.
petiole.petioles_per_internode);
1403 std::fill(current_leaf_scale_factor.begin(), current_leaf_scale_factor.end(), leaf_scale_factor_fraction);
1405 if (internode_radius == 0.f) {
1406 internode_radius = 1e-5;
1410 float internode_length = internode_length_scale_factor_fraction * internode_length_max;
1411 float dr_internode = internode_length / float(phytomer_parameters.
internode.length_segments);
1412 float dr_internode_max = internode_length_max / float(phytomer_parameters.
internode.length_segments);
1413 std::vector<vec3> phytomer_internode_vertices;
1414 std::vector<float> phytomer_internode_radii;
1415 phytomer_internode_vertices.resize(Ndiv_internode_length + 1);
1416 phytomer_internode_vertices.at(0) = internode_base_origin;
1417 phytomer_internode_radii.resize(Ndiv_internode_length + 1);
1418 phytomer_internode_radii.at(0) = internode_radius;
1420 phytomer_parameters.
internode.pitch.resample();
1421 internode_phyllotactic_angle =
deg2rad(phytomer_parameters.
internode.phyllotactic_angle.val());
1422 phytomer_parameters.
internode.phyllotactic_angle.resample();
1425 petiole_length.resize(phytomer_parameters.
petiole.petioles_per_internode);
1427 petiole_radii.resize(phytomer_parameters.
petiole.petioles_per_internode);
1430 peduncle_vertices.resize(phytomer_parameters.
petiole.petioles_per_internode);
1431 petiole_pitch.resize(phytomer_parameters.
petiole.petioles_per_internode);
1432 petiole_curvature.resize(phytomer_parameters.
petiole.petioles_per_internode);
1433 std::vector<float> dr_petiole(phytomer_parameters.
petiole.petioles_per_internode);
1434 std::vector<float> dr_petiole_max(phytomer_parameters.
petiole.petioles_per_internode);
1435 for (
int p = 0; p < phytomer_parameters.
petiole.petioles_per_internode; p++) {
1437 petiole_radii.at(p).resize(Ndiv_petiole_length + 1);
1439 petiole_length.at(p) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.length.val();
1440 if (petiole_length.at(p) <= 0.f) {
1441 petiole_length.at(p) = 1e-5f;
1443 dr_petiole.at(p) = petiole_length.at(p) / float(phytomer_parameters.
petiole.length_segments);
1444 dr_petiole_max.at(p) = phytomer_parameters.
petiole.length.val() / float(phytomer_parameters.
petiole.length_segments);
1446 petiole_radii.at(p).at(0) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.radius.val();
1447 if (petiole_radii.at(p).at(0) <= 0.f) {
1448 petiole_radii.at(p).at(0) = 1e-5f;
1451 phytomer_parameters.
petiole.length.resample();
1452 if (build_context_geometry_petiole) {
1453 petiole_objIDs.resize(phytomer_parameters.
petiole.petioles_per_internode);
1457 leaf_bases.resize(phytomer_parameters.
petiole.petioles_per_internode);
1458 leaf_objIDs.resize(phytomer_parameters.
petiole.petioles_per_internode);
1459 leaf_size_max.resize(phytomer_parameters.
petiole.petioles_per_internode);
1460 leaf_rotation.resize(phytomer_parameters.
petiole.petioles_per_internode);
1461 int leaves_per_petiole = phytomer_parameters.
leaf.leaves_per_petiole.val();
1462 float leaflet_offset_val = clampOffset(leaves_per_petiole, phytomer_parameters.
leaf.leaflet_offset.val());
1463 phytomer_parameters.
leaf.leaves_per_petiole.resample();
1464 for (
uint petiole = 0; petiole < phytomer_parameters.
petiole.petioles_per_internode; petiole++) {
1465 leaf_size_max.at(petiole).resize(leaves_per_petiole);
1466 leaf_rotation.at(petiole).resize(leaves_per_petiole);
1469 internode_colors.resize(Ndiv_internode_length + 1);
1470 internode_colors.at(0) = phytomer_parameters.
internode.color;
1471 petiole_colors.resize(Ndiv_petiole_length + 1);
1472 petiole_colors.at(0) = phytomer_parameters.
petiole.color;
1474 vec3 internode_axis = parent_internode_axis;
1476 vec3 petiole_rotation_axis =
cross(parent_internode_axis, parent_petiole_axis);
1477 if (petiole_rotation_axis ==
make_vec3(0, 0, 0)) {
1478 petiole_rotation_axis =
make_vec3(1, 0, 0);
1481 if (phytomer_index == 0) {
1484 if (internode_pitch != 0.f) {
1488 float roll_nudge = 0.f;
1493 if (shoot_base_rotation.roll != 0.f || roll_nudge != 0.f) {
1499 vec3 base_pitch_axis = -1 *
cross(parent_internode_axis, parent_petiole_axis);
1502 if (shoot_base_rotation.pitch != 0.f) {
1508 if (shoot_base_rotation.yaw != 0) {
1521 if (internode_pitch != 0) {
1529 if (internode_axis ==
make_vec3(0, 0, 1)) {
1530 shoot_bending_axis =
make_vec3(0, 1, 0);
1534 vec3 collision_optimal_direction;
1535 bool collision_detection_active =
false;
1536 vec3 attraction_direction;
1537 bool attraction_active =
false;
1538 bool obstacle_found =
false;
1541 collision_optimal_direction = calculateCollisionAvoidanceDirection(internode_base_origin, internode_axis, collision_detection_active);
1544 attraction_direction = calculateAttractionPointDirection(internode_base_origin, internode_axis, attraction_active);
1549 for (
int inode_segment = 1; inode_segment <= Ndiv_internode_length; inode_segment++) {
1551 if ((fabs(parent_shoot->gravitropic_curvature) > 0 || parent_shoot_parameters.
tortuosity.val() > 0) &&
shoot_index.
x > 0) {
1554 float current_curvature_fact = 0.5f - internode_axis.
z / 2.f;
1555 if (internode_axis.
z < 0) {
1556 current_curvature_fact *= 2.f;
1559 float dt = dr_internode_max / float(Ndiv_internode_length);
1561 parent_shoot->curvature_perturbation += -0.5f * parent_shoot->curvature_perturbation * dt + parent_shoot_parameters.
tortuosity.val() * context_ptr->
randn() * sqrt(dt);
1562 float curvature_angle =
deg2rad((parent_shoot->gravitropic_curvature * current_curvature_fact * dr_internode_max + parent_shoot->curvature_perturbation));
1565 parent_shoot->yaw_perturbation += -0.5f * parent_shoot->yaw_perturbation * dt + parent_shoot_parameters.
tortuosity.val() * context_ptr->
randn() * sqrt(dt);
1566 float yaw_angle =
deg2rad((parent_shoot->yaw_perturbation));
1571 vec3 current_position = phytomer_internode_vertices.at(inode_segment - 1);
1572 obstacle_found = applySolidObstacleAvoidance(current_position, internode_axis);
1577 vec3 final_direction = internode_axis;
1579 if (attraction_active) {
1581 float attraction_weight = plantarchitecture_ptr->attraction_weight;
1583 if (obstacle_found) {
1586 attraction_weight *= plantarchitecture_ptr->attraction_obstacle_reduction_factor;
1590 final_direction = (1.0f - attraction_weight) * final_direction + attraction_weight * attraction_direction;
1594 plantarchitecture_ptr->collision_avoidance_applied =
true;
1596 }
else if (collision_detection_active && !obstacle_found) {
1598 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
1601 final_direction = inertia_weight * final_direction + (1.0f - inertia_weight) * collision_optimal_direction;
1605 plantarchitecture_ptr->collision_avoidance_applied =
true;
1608 if (obstacle_found) {
1610 plantarchitecture_ptr->collision_avoidance_applied =
true;
1614 internode_axis = final_direction;
1630 phytomer_internode_vertices.at(inode_segment) = phytomer_internode_vertices.at(inode_segment - 1) + dr_internode * internode_axis;
1632 phytomer_internode_radii.at(inode_segment) = internode_radius;
1633 internode_colors.at(inode_segment) = phytomer_parameters.
internode.color;
1638 parent_shoot_ptr->shoot_internode_vertices.push_back(phytomer_internode_vertices);
1639 parent_shoot_ptr->shoot_internode_radii.push_back(phytomer_internode_radii);
1642 parent_shoot_ptr->shoot_internode_vertices.emplace_back(phytomer_internode_vertices.begin() + 1, phytomer_internode_vertices.end());
1643 parent_shoot_ptr->shoot_internode_radii.emplace_back(phytomer_internode_radii.begin() + 1, phytomer_internode_radii.end());
1647 if (build_context_geometry_internode) {
1649 float texture_repeat_length = 0.25f;
1651 for (
auto &phytomer: parent_shoot_ptr->phytomers) {
1652 length += phytomer->internode_length_max;
1654 std::vector<float> uv_y(phytomer_internode_vertices.size());
1655 float dy = internode_length_max / float(uv_y.size() - 1);
1656 for (
int j = 0; j < uv_y.size(); j++) {
1657 uv_y.at(j) = (length + j * dy) / texture_repeat_length - std::floor((length + j * dy) / texture_repeat_length);
1660 if (!context_ptr->
doesObjectExist(parent_shoot->internode_tube_objID)) {
1662 if (!phytomer_parameters.
internode.image_texture.empty()) {
1663 parent_shoot->internode_tube_objID = context_ptr->
addTubeObject(Ndiv_internode_radius, phytomer_internode_vertices, phytomer_internode_radii, phytomer_parameters.
internode.image_texture.c_str(), uv_y);
1665 parent_shoot->internode_tube_objID = context_ptr->
addTubeObject(Ndiv_internode_radius, phytomer_internode_vertices, phytomer_internode_radii, internode_colors);
1670 for (
int inode_segment = 1; inode_segment <= Ndiv_internode_length; inode_segment++) {
1671 if (!phytomer_parameters.
internode.image_texture.empty()) {
1672 context_ptr->
appendTubeSegment(parent_shoot->internode_tube_objID, phytomer_internode_vertices.at(inode_segment), phytomer_internode_radii.at(inode_segment), phytomer_parameters.
internode.image_texture.c_str(),
1673 {uv_y.at(inode_segment - 1), uv_y.at(inode_segment)});
1675 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));
1684 for (
int petiole = 0; petiole < phytomer_parameters.
petiole.petioles_per_internode; petiole++) {
1687 vec3 petiole_axis = internode_axis;
1693 petiole_pitch.at(petiole) = 0.0f;
1696 petiole_pitch.at(petiole) =
deg2rad(phytomer_parameters.
petiole.pitch.val());
1697 phytomer_parameters.
petiole.pitch.resample();
1698 if (fabs(petiole_pitch.at(petiole)) <
deg2rad(5.f)) {
1699 petiole_pitch.at(petiole) =
deg2rad(5.f);
1705 if (phytomer_index != 0 && internode_phyllotactic_angle != 0) {
1712 petiole_curvature.at(petiole) = phytomer_parameters.
petiole.curvature.val();
1713 phytomer_parameters.
petiole.curvature.resample();
1715 vec3 petiole_rotation_axis_actual = petiole_rotation_axis;
1716 vec3 petiole_axis_actual = petiole_axis;
1719 float budrot = float(petiole) * 2.f *
PI_F / float(phytomer_parameters.
petiole.petioles_per_internode);
1725 vec3 collision_optimal_petiole_direction;
1726 bool petiole_collision_active =
false;
1728 if (plantarchitecture_ptr->petiole_collision_detection_enabled) {
1729 collision_optimal_petiole_direction = calculatePetioleCollisionAvoidanceDirection(phytomer_internode_vertices.back(),
1730 petiole_axis_actual, petiole_collision_active);
1733 if (petiole_collision_active) {
1734 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
1735 vec3 natural_petiole_direction = petiole_axis_actual;
1740 petiole_axis_actual = inertia_weight * natural_petiole_direction + (1.0f - inertia_weight) * collision_optimal_petiole_direction;
1745 vec3 bending_direction = collision_optimal_petiole_direction - (collision_optimal_petiole_direction * natural_petiole_direction) * natural_petiole_direction;
1747 if (bending_direction.
magnitude() > 1e-6f) {
1752 vec3 curvature_axis =
cross(natural_petiole_direction, bending_direction);
1754 if (curvature_axis.
magnitude() > 1e-6f) {
1758 float angular_deviation = acosf(std::max(-1.0f, std::min(1.0f, collision_optimal_petiole_direction * natural_petiole_direction)));
1761 float desired_curvature_deg =
rad2deg(angular_deviation) * (1.0f - inertia_weight);
1764 float curvature_sign = (curvature_axis * petiole_rotation_axis_actual > 0) ? 1.0f : -1.0f;
1767 petiole_curvature.at(petiole) += curvature_sign * desired_curvature_deg * 0.5f;
1774 for (
int j = 1; j <= Ndiv_petiole_length; j++) {
1775 if (fabs(petiole_curvature.at(petiole)) > 0) {
1781 petiole_radii.at(petiole).at(j) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.radius.val() * (1.f - phytomer_parameters.
petiole.taper.val() / float(Ndiv_petiole_length) * float(j));
1782 petiole_colors.at(j) = phytomer_parameters.
petiole.color;
1785 assert(!std::isnan(petiole_radii.at(petiole).at(j)) && std::isfinite(petiole_radii.at(petiole).at(j)));
1788 if (build_context_geometry_petiole && petiole_radii.at(petiole).front() > 0.f) {
1795 std::vector<VegetativeBud> vegetative_buds_new;
1796 vegetative_buds_new.resize(phytomer_parameters.
internode.max_vegetative_buds_per_petiole.val());
1797 phytomer_parameters.
internode.max_vegetative_buds_per_petiole.resample();
1799 axillary_vegetative_buds.push_back(vegetative_buds_new);
1801 std::vector<FloralBud> floral_buds_new;
1802 floral_buds_new.resize(phytomer_parameters.
internode.max_floral_buds_per_petiole.val());
1803 phytomer_parameters.
internode.max_floral_buds_per_petiole.resample();
1806 for (
auto &fbud: floral_buds_new) {
1807 fbud.bud_index = index;
1808 fbud.parent_index = petiole;
1809 float pitch_adjustment = fbud.bud_index * 0.1f *
PI_F / float(axillary_vegetative_buds.size());
1810 float yaw_adjustment = -0.25f *
PI_F + fbud.bud_index * 0.5f *
PI_F / float(axillary_vegetative_buds.size());
1811 fbud.base_rotation = make_AxisRotation(pitch_adjustment, yaw_adjustment, 0);
1812 fbud.base_position = phytomer_internode_vertices.back();
1813 fbud.bending_axis = shoot_bending_axis;
1817 floral_buds.push_back(floral_buds_new);
1821 if (phytomer_parameters.
leaf.prototype.prototype_function ==
nullptr) {
1822 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Leaf prototype function was not defined for shoot type " + parent_shoot->shoot_type_label +
".");
1828 assert(phytomer_parameters.
leaf.prototype.unique_prototype_identifier != 0);
1829 if (phytomer_parameters.
leaf.prototype.unique_prototypes > 0 &&
1830 plantarchitecture_ptr->unique_leaf_prototype_objIDs.find(phytomer_parameters.
leaf.prototype.unique_prototype_identifier) == plantarchitecture_ptr->unique_leaf_prototype_objIDs.end()) {
1831 plantarchitecture_ptr->unique_leaf_prototype_objIDs[phytomer_parameters.
leaf.prototype.unique_prototype_identifier].resize(phytomer_parameters.
leaf.prototype.unique_prototypes);
1832 for (
int prototype = 0; prototype < phytomer_parameters.
leaf.prototype.unique_prototypes; prototype++) {
1833 for (
int leaf = 0; leaf < leaves_per_petiole; leaf++) {
1834 float ind_from_tip = float(leaf) - float(leaves_per_petiole - 1) / 2.f;
1835 uint objID_leaf = phytomer_parameters.
leaf.prototype.prototype_function(context_ptr, &phytomer_parameters.
leaf.prototype, ind_from_tip);
1836 if (phytomer_parameters.
leaf.prototype.prototype_function == GenericLeafPrototype) {
1839 plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).push_back(objID_leaf);
1847 for (
int leaf = 0; leaf < leaves_per_petiole; leaf++) {
1848 float ind_from_tip = float(leaf) - float(leaves_per_petiole - 1) / 2.f;
1851 if (phytomer_parameters.
leaf.prototype.unique_prototypes > 0) {
1853 int prototype = context_ptr->
randu(0, phytomer_parameters.
leaf.prototype.unique_prototypes - 1);
1854 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.find(phytomer_parameters.
leaf.prototype.unique_prototype_identifier) != plantarchitecture_ptr->unique_leaf_prototype_objIDs.end());
1855 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).size() > prototype);
1856 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).size() > leaf);
1857 objID_leaf = context_ptr->
copyObject(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).at(leaf));
1860 objID_leaf = phytomer_parameters.
leaf.prototype.prototype_function(context_ptr, &phytomer_parameters.
leaf.prototype, ind_from_tip);
1865 if (leaves_per_petiole > 0 && phytomer_parameters.
leaf.leaflet_scale.val() != 1.f && ind_from_tip != 0) {
1866 leaf_size_max.at(petiole).at(leaf) = powf(phytomer_parameters.
leaf.leaflet_scale.val(), fabs(ind_from_tip)) * phytomer_parameters.
leaf.prototype_scale.val();
1868 leaf_size_max.at(petiole).at(leaf) = phytomer_parameters.
leaf.prototype_scale.val();
1870 vec3 leaf_scale = leaf_scale_factor_fraction * leaf_size_max.at(petiole).at(leaf) *
make_vec3(1, 1, 1);
1874 float compound_rotation = 0;
1875 if (leaves_per_petiole > 1) {
1876 if (leaflet_offset_val == 0) {
1877 float dphi =
PI_F / (floor(0.5 *
float(leaves_per_petiole - 1)) + 1);
1878 compound_rotation = -float(
PI_F) + dphi * (leaf + 0.5f);
1880 if (leaf ==
float(leaves_per_petiole - 1) / 2.f) {
1882 compound_rotation = 0;
1883 }
else if (leaf <
float(leaves_per_petiole - 1) / 2.f) {
1884 compound_rotation = -0.5 *
PI_F;
1886 compound_rotation = 0.5 *
PI_F;
1895 if (leaves_per_petiole == 1) {
1898 }
else if (ind_from_tip != 0) {
1899 roll_rot = (
asin_safe(petiole_tip_axis.
z) +
deg2rad(phytomer_parameters.
leaf.roll.val())) * compound_rotation / std::fabs(compound_rotation);
1901 leaf_rotation.at(petiole).at(leaf).roll =
deg2rad(phytomer_parameters.
leaf.roll.val());
1902 phytomer_parameters.
leaf.roll.resample();
1906 leaf_rotation.at(petiole).at(leaf).pitch =
deg2rad(phytomer_parameters.
leaf.pitch.val());
1907 float pitch_rot = leaf_rotation.at(petiole).at(leaf).pitch;
1908 phytomer_parameters.
leaf.pitch.resample();
1909 if (ind_from_tip == 0) {
1912 context_ptr->
rotateObject(objID_leaf, -pitch_rot,
"y");
1915 if (ind_from_tip != 0) {
1916 float sign = -compound_rotation / fabs(compound_rotation);
1917 leaf_rotation.at(petiole).at(leaf).yaw = sign *
deg2rad(phytomer_parameters.
leaf.yaw.val());
1918 float yaw_rot = leaf_rotation.at(petiole).at(leaf).yaw;
1919 phytomer_parameters.
leaf.yaw.resample();
1922 leaf_rotation.at(petiole).at(leaf).yaw = 0;
1926 context_ptr->
rotateObject(objID_leaf, -std::atan2(petiole_tip_axis.
y, petiole_tip_axis.
x) + compound_rotation,
"z");
1932 if (leaves_per_petiole > 1 && leaflet_offset_val > 0) {
1933 if (ind_from_tip != 0) {
1934 float offset = (fabs(ind_from_tip) - 0.5f) * leaflet_offset_val * phytomer_parameters.
petiole.length.val();
1935 leaf_base = PlantArchitecture::interpolateTube(
petiole_vertices.at(petiole), 1.f - offset / phytomer_parameters.
petiole.length.val());
1941 leaf_objIDs.at(petiole).push_back(objID_leaf);
1942 leaf_bases.at(petiole).push_back(leaf_base);
1944 phytomer_parameters.
leaf.prototype_scale.resample();
1946 if (petiole_axis_actual ==
make_vec3(0, 0, 1)) {
1947 inflorescence_bending_axis =
make_vec3(1, 0, 0);
1949 inflorescence_bending_axis =
cross(
make_vec3(0, 0, 1), petiole_axis_actual);
1956 const auto &segment = parent_shoot_ptr->shoot_internode_radii.at(node_number);
1959 float avg_radius = 0.0f;
1960 for (
float radius: segment) {
1961 avg_radius += radius;
1963 avg_radius /= scast<float>(segment.size());
1969 float volume =
PI_F * avg_radius * avg_radius * length;
1974void Phytomer::updateInflorescence(
FloralBud &fbud) {
1975 bool build_context_geometry_peduncle = plantarchitecture_ptr->build_context_geometry_peduncle;
1977 uint Ndiv_peduncle_length = std::max(
uint(1), phytomer_parameters.
peduncle.length_segments);
1978 uint Ndiv_peduncle_radius = std::max(
uint(3), phytomer_parameters.
peduncle.radial_subdivisions);
1979 if (phytomer_parameters.
peduncle.length_segments == 0 || phytomer_parameters.
peduncle.radial_subdivisions < 3) {
1980 build_context_geometry_peduncle =
false;
1983 float dr_peduncle = phytomer_parameters.
peduncle.length.val() / float(Ndiv_peduncle_length);
1984 phytomer_parameters.
peduncle.length.resample();
1986 std::vector<vec3> peduncle_vertices(phytomer_parameters.
peduncle.length_segments + 1);
1987 peduncle_vertices.at(0) = fbud.base_position;
1988 std::vector<float> peduncle_radii(phytomer_parameters.
peduncle.length_segments + 1);
1989 peduncle_radii.at(0) = phytomer_parameters.
peduncle.radius.val();
1990 std::vector<RGBcolor> peduncle_colors(phytomer_parameters.
peduncle.length_segments + 1);
1991 peduncle_colors.at(0) = phytomer_parameters.
peduncle.color;
1996 if (phytomer_parameters.
peduncle.pitch.val() != 0.f || fbud.base_rotation.pitch != 0.f) {
1997 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, inflorescence_bending_axis,
deg2rad(phytomer_parameters.
peduncle.pitch.val()) + fbud.base_rotation.pitch);
1998 phytomer_parameters.
peduncle.pitch.resample();
2004 float parent_petiole_azimuth = -std::atan2(parent_petiole_base_axis.
y, parent_petiole_base_axis.
x);
2005 float current_peduncle_azimuth = -std::atan2(peduncle_axis.
y, peduncle_axis.
x);
2006 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, internode_axis, (current_peduncle_azimuth - parent_petiole_azimuth));
2009 float theta_base = fabs(
cart2sphere(peduncle_axis).zenith);
2012 vec3 collision_optimal_peduncle_direction;
2013 bool peduncle_collision_active =
false;
2015 if (plantarchitecture_ptr->fruit_collision_detection_enabled) {
2016 collision_optimal_peduncle_direction = calculateFruitCollisionAvoidanceDirection(fbud.base_position,
2017 peduncle_axis, peduncle_collision_active);
2020 if (peduncle_collision_active) {
2021 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
2022 vec3 natural_peduncle_direction = peduncle_axis;
2027 peduncle_axis = inertia_weight * natural_peduncle_direction + (1.0f - inertia_weight) * collision_optimal_peduncle_direction;
2031 for (
int i = 1; i <= phytomer_parameters.
peduncle.length_segments; i++) {
2032 if (phytomer_parameters.
peduncle.curvature.val() != 0.f) {
2033 float theta_curvature = -
deg2rad(phytomer_parameters.
peduncle.curvature.val() * dr_peduncle);
2034 phytomer_parameters.
peduncle.curvature.resample();
2035 if (fabs(theta_curvature) *
float(i) <
PI_F - theta_base) {
2036 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, inflorescence_bending_axis, theta_curvature);
2042 peduncle_vertices.at(i) = peduncle_vertices.at(i - 1) + dr_peduncle * peduncle_axis;
2044 peduncle_radii.at(i) = phytomer_parameters.
peduncle.radius.val();
2045 peduncle_colors.at(i) = phytomer_parameters.
peduncle.color;
2047 phytomer_parameters.
peduncle.radius.resample();
2049 if (build_context_geometry_peduncle) {
2050 fbud.peduncle_objIDs.push_back(context_ptr->
addTubeObject(Ndiv_peduncle_radius, peduncle_vertices, peduncle_radii, peduncle_colors));
2056 uint petiole_idx = fbud.parent_index;
2059 if (petiole_idx < this->peduncle_vertices.size()) {
2060 if (this->peduncle_vertices.at(petiole_idx).size() <= fbud.bud_index) {
2061 this->peduncle_vertices.at(petiole_idx).resize(fbud.bud_index + 1);
2063 this->peduncle_vertices.at(petiole_idx).at(fbud.bud_index) = peduncle_vertices;
2067 std::string parent_shoot_type_label = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(parent_shoot_ID)->shoot_type_label;
2068 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2070 if (phytomer_parameters.
inflorescence.flower_prototype_function !=
nullptr &&
2071 plantarchitecture_ptr->unique_closed_flower_prototype_objIDs.find(phytomer_parameters.
inflorescence.flower_prototype_function) == plantarchitecture_ptr->unique_closed_flower_prototype_objIDs.end()) {
2072 plantarchitecture_ptr->unique_closed_flower_prototype_objIDs[phytomer_parameters.
inflorescence.flower_prototype_function].resize(phytomer_parameters.
inflorescence.unique_prototypes);
2073 for (
int prototype = 0; prototype < phytomer_parameters.
inflorescence.unique_prototypes; prototype++) {
2074 uint objID_flower = phytomer_parameters.
inflorescence.flower_prototype_function(context_ptr, 1,
false);
2075 plantarchitecture_ptr->unique_closed_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype) = objID_flower;
2080 if (phytomer_parameters.
inflorescence.flower_prototype_function !=
nullptr &&
2081 plantarchitecture_ptr->unique_open_flower_prototype_objIDs.find(phytomer_parameters.
inflorescence.flower_prototype_function) == plantarchitecture_ptr->unique_open_flower_prototype_objIDs.end()) {
2082 plantarchitecture_ptr->unique_open_flower_prototype_objIDs[phytomer_parameters.
inflorescence.flower_prototype_function].resize(phytomer_parameters.
inflorescence.unique_prototypes);
2083 for (
int prototype = 0; prototype < phytomer_parameters.
inflorescence.unique_prototypes; prototype++) {
2084 uint objID_flower = phytomer_parameters.
inflorescence.flower_prototype_function(context_ptr, 1,
true);
2085 plantarchitecture_ptr->unique_open_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype) = objID_flower;
2090 if (phytomer_parameters.
inflorescence.fruit_prototype_function !=
nullptr &&
2091 plantarchitecture_ptr->unique_fruit_prototype_objIDs.find(phytomer_parameters.
inflorescence.fruit_prototype_function) == plantarchitecture_ptr->unique_fruit_prototype_objIDs.end()) {
2092 plantarchitecture_ptr->unique_fruit_prototype_objIDs[phytomer_parameters.
inflorescence.fruit_prototype_function].resize(phytomer_parameters.
inflorescence.unique_prototypes);
2093 for (
int prototype = 0; prototype < phytomer_parameters.
inflorescence.unique_prototypes; prototype++) {
2094 uint objID_fruit = phytomer_parameters.
inflorescence.fruit_prototype_function(context_ptr, 1);
2095 plantarchitecture_ptr->unique_fruit_prototype_objIDs.at(phytomer_parameters.
inflorescence.fruit_prototype_function).at(prototype) = objID_fruit;
2101 int flowers_per_peduncle = phytomer_parameters.
inflorescence.flowers_per_peduncle.val();
2102 float flower_offset_val = clampOffset(flowers_per_peduncle, phytomer_parameters.
inflorescence.flower_offset.val());
2103 for (
int fruit = 0; fruit < flowers_per_peduncle; fruit++) {
2107 if (fbud.state == BUD_FRUITING) {
2108 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2110 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2111 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_fruit_prototype_objIDs.at(phytomer_parameters.
inflorescence.fruit_prototype_function).at(prototype));
2114 objID_fruit = phytomer_parameters.
inflorescence.fruit_prototype_function(context_ptr, 1);
2117 phytomer_parameters.
inflorescence.fruit_prototype_scale.resample();
2119 bool flower_is_open;
2120 if (fbud.state == BUD_FLOWER_CLOSED) {
2121 flower_is_open =
false;
2122 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2124 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2125 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_closed_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype));
2128 objID_fruit = phytomer_parameters.
inflorescence.flower_prototype_function(context_ptr, 1, flower_is_open);
2131 flower_is_open =
true;
2132 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2134 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2135 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_open_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype));
2138 objID_fruit = phytomer_parameters.
inflorescence.flower_prototype_function(context_ptr, 1, flower_is_open);
2142 phytomer_parameters.
inflorescence.flower_prototype_scale.resample();
2145 float ind_from_tip = fabs(fruit -
float(flowers_per_peduncle - 1) /
float(phytomer_parameters.
petiole.petioles_per_internode));
2147 context_ptr->
scaleObject(objID_fruit, fruit_scale);
2150 vec3 fruit_base = peduncle_vertices.back();
2152 if (flowers_per_peduncle > 1 && flower_offset_val > 0) {
2153 if (ind_from_tip != 0) {
2154 float offset = (ind_from_tip - 0.5f) * flower_offset_val * phytomer_parameters.
peduncle.length.val();
2155 if (phytomer_parameters.
peduncle.length.val() > 0) {
2156 frac = 1.f - offset / phytomer_parameters.
peduncle.length.val();
2158 fruit_base = PlantArchitecture::interpolateTube(peduncle_vertices, frac);
2163 float compound_rotation = 0;
2164 if (flowers_per_peduncle > 1) {
2165 if (flower_offset_val == 0) {
2167 float dphi =
PI_F / (floor(0.5 *
float(flowers_per_peduncle - 1)) + 1);
2168 compound_rotation = -float(
PI_F) + dphi * (fruit + 0.5f);
2170 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);
2171 phytomer_parameters.
internode.phyllotactic_angle.resample();
2177 vec3 fruit_axis = peduncle_axis;
2188 if (fbud.state == BUD_FRUITING) {
2190 pitch_inflorescence = pitch_inflorescence + phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.val() * (0.5f *
PI_F - pitch_inflorescence);
2194 context_ptr->
rotateObject(objID_fruit, pitch_inflorescence,
"y");
2198 context_ptr->
rotateObject(objID_fruit, -std::atan2(peduncle_axis.y, peduncle_axis.x),
"z");
2204 if (phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.val() != 0 && fbud.state == BUD_FRUITING) {
2207 context_ptr->
rotateObject(objID_fruit,
deg2rad(phytomer_parameters.
peduncle.roll.val()) + compound_rotation, fruit_base, peduncle_axis);
2210 phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.resample();
2213 fbud.inflorescence_bases.push_back(fruit_base);
2215 fbud.inflorescence_objIDs.push_back(objID_fruit);
2217 phytomer_parameters.
inflorescence.flowers_per_peduncle.resample();
2218 phytomer_parameters.
peduncle.roll.resample();
2220 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
2221 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"rank", rank);
2222 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"rank", rank);
2225 if (plantarchitecture_ptr->output_object_data.at(
"peduncleID")) {
2226 for (
uint objID: fbud.peduncle_objIDs) {
2227 context_ptr->
setObjectData(objID,
"peduncleID", (
int) objID);
2230 for (
uint objID: fbud.inflorescence_objIDs) {
2231 if (fbud.state == BUD_FLOWER_CLOSED && plantarchitecture_ptr->output_object_data.at(
"closedflowerID")) {
2232 context_ptr->
setObjectData(objID,
"closedflowerID", (
int) objID);
2233 }
else if (fbud.state == BUD_FLOWER_OPEN && plantarchitecture_ptr->output_object_data.at(
"openflowerID")) {
2235 context_ptr->
setObjectData(objID,
"openflowerID", (
int) objID);
2236 }
else if (plantarchitecture_ptr->output_object_data.at(
"fruitID")) {
2244 vec3 shift = base_position - old_base;
2247 for (
auto &vertex: petiole_vertice) {
2252 if (build_context_geometry_petiole) {
2257 for (
auto &petiole: leaf_bases) {
2258 for (
auto &leaf_base: petiole) {
2262 for (
auto &floral_bud: floral_buds) {
2263 for (
auto &fbud: floral_bud) {
2266 for (
auto &base: fbud.inflorescence_bases) {
2269 if (build_context_geometry_peduncle) {
2277 if (petiole_index >= leaf_objIDs.size()) {
2279 }
else if (leaf_index >= leaf_objIDs.at(petiole_index).size()) {
2288 vec3 pitch_axis = -1 *
cross(internode_axis, petiole_axis);
2290 int leaves_per_petiole = leaf_rotation.at(petiole_index).size();
2293 float compound_rotation = 0;
2294 if (leaves_per_petiole > 1 && leaf_index ==
float(leaves_per_petiole - 1) / 2.f) {
2298 compound_rotation = 0;
2299 }
else if (leaves_per_petiole > 1 && leaf_index <
float(leaves_per_petiole - 1) / 2.f) {
2301 yaw = -rotation.yaw;
2302 roll = -rotation.roll;
2303 compound_rotation = -0.5 *
PI_F;
2306 yaw = -rotation.yaw;
2307 roll = rotation.roll;
2308 compound_rotation = 0;
2314 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), roll, leaf_bases.at(petiole_index).at(leaf_index), roll_axis);
2315 leaf_rotation.at(petiole_index).at(leaf_index).roll += roll;
2319 if (rotation.pitch != 0) {
2321 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), rotation.pitch, leaf_bases.at(petiole_index).at(leaf_index), pitch_axis);
2322 leaf_rotation.at(petiole_index).at(leaf_index).pitch += rotation.pitch;
2327 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), yaw, leaf_bases.at(petiole_index).at(leaf_index), {0, 0, 1});
2328 leaf_rotation.at(petiole_index).at(leaf_index).yaw += yaw;
2333 assert(internode_scale_factor_fraction >= 0 && internode_scale_factor_fraction <= 1);
2335 if (internode_scale_factor_fraction == current_internode_scale_factor) {
2339 float delta_scale = internode_scale_factor_fraction / current_internode_scale_factor;
2341 current_internode_scale_factor = internode_scale_factor_fraction;
2344 int s_start = (p == 0) ? 1 : 0;
2346 for (
int s = s_start; s < parent_shoot_ptr->shoot_internode_vertices.at(p).size(); s++) {
2350 int s_minus = s - 1;
2353 s_minus =
static_cast<int>(parent_shoot_ptr->shoot_internode_vertices.at(p_minus).size() - 1);
2356 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));
2357 float current_length = central_axis.
magnitude();
2358 central_axis = central_axis / current_length;
2359 vec3 dL = central_axis * current_length * (delta_scale - 1);
2362 for (
int p_downstream = p; p_downstream < parent_shoot_ptr->shoot_internode_vertices.size(); p_downstream++) {
2363 int sd_start = (p_downstream == p) ? s : 0;
2364 for (
int s_downstream = sd_start; s_downstream < parent_shoot_ptr->shoot_internode_vertices.at(p_downstream).size(); s_downstream++) {
2365 parent_shoot_ptr->shoot_internode_vertices.at(p_downstream).at(s_downstream) += dL;
2374 this->internode_length_max *= scale_factor;
2376 current_internode_scale_factor = current_internode_scale_factor / scale_factor;
2378 if (current_internode_scale_factor >= 1.f) {
2380 current_internode_scale_factor = 1.f;
2385 float scale_factor = internode_length_max_new / this->internode_length_max;
2390 this->internode_radius_max = internode_radius_max_new;
2394 assert(leaf_scale_factor_fraction >= 0 && leaf_scale_factor_fraction <= 1);
2396 if (current_leaf_scale_factor.size() <= petiole_index) {
2397 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf scale factor.");
2401 if (leaf_scale_factor_fraction == current_leaf_scale_factor.at(petiole_index) || (leaf_objIDs.at(petiole_index).empty() && petiole_objIDs.at(petiole_index).empty())) {
2405 float delta_scale = leaf_scale_factor_fraction / current_leaf_scale_factor.at(petiole_index);
2407 petiole_length.at(petiole_index) *= delta_scale;
2409 current_leaf_scale_factor.at(petiole_index) = leaf_scale_factor_fraction;
2411 assert(leaf_objIDs.size() == leaf_bases.size());
2415 if (!petiole_objIDs.at(petiole_index).empty()) {
2418 for (
uint objID: petiole_objIDs.at(petiole_index)) {
2422 petiole_radii.at(petiole_index).at(node) *= delta_scale;
2436 assert(leaf_objIDs.at(petiole_index).size() == leaf_bases.at(petiole_index).size());
2437 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2438 float ind_from_tip = float(leaf) - float(leaf_objIDs.at(petiole_index).size() - 1) / 2.f;
2440 float leaflet_offset_val = clampOffset(
int(leaf_objIDs.at(petiole_index).size()), phytomer_parameters.
leaf.leaflet_offset.val());
2442 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), -1 * leaf_bases.at(petiole_index).at(leaf));
2443 context_ptr->
scaleObject(leaf_objIDs.at(petiole_index).at(leaf), delta_scale *
make_vec3(1, 1, 1));
2444 if (ind_from_tip == 0) {
2446 leaf_bases.at(petiole_index).at(leaf) =
petiole_vertices.at(petiole_index).back();
2448 float offset = (fabs(ind_from_tip) - 0.5f) * leaflet_offset_val * phytomer_parameters.
petiole.length.val();
2449 vec3 leaf_base = PlantArchitecture::interpolateTube(
petiole_vertices.at(petiole_index), 1.f - offset / phytomer_parameters.
petiole.length.val());
2450 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), leaf_base);
2451 leaf_bases.at(petiole_index).at(leaf) = leaf_base;
2457 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2463 if (leaf_objIDs.size() <= petiole_index) {
2464 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf prototype scale.");
2466 if (leaf_prototype_scale < 0.f) {
2467 leaf_prototype_scale = 0;
2470 float tip_ind = ceil(scast<float>(leaf_size_max.at(petiole_index).size() - 1) / 2.f);
2471 float scale_factor = leaf_prototype_scale / leaf_size_max.at(petiole_index).at(tip_ind);
2472 current_leaf_scale_factor.at(petiole_index) *= scale_factor;
2474 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2475 leaf_size_max.at(petiole_index).at(leaf) *= scale_factor;
2476 context_ptr->
scaleObjectAboutPoint(leaf_objIDs.at(petiole_index).at(leaf), scale_factor *
make_vec3(1, 1, 1), leaf_bases.at(petiole_index).at(leaf));
2480 this->petiole_curvature.at(petiole_index) /= scale_factor;
2482 if (current_leaf_scale_factor.at(petiole_index) >= 1.f) {
2484 current_leaf_scale_factor.at(petiole_index) = 1.f;
2489 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2495 if (leaf_objIDs.size() <= petiole_index) {
2496 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf prototype scale.");
2498 if (scale_factor < 0.f) {
2502 current_leaf_scale_factor.at(petiole_index) /= scale_factor;
2504 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2505 leaf_size_max.at(petiole_index).at(leaf) *= scale_factor;
2506 context_ptr->
scaleObjectAboutPoint(leaf_objIDs.at(petiole_index).at(leaf), scale_factor *
make_vec3(1, 1, 1), leaf_bases.at(petiole_index).at(leaf));
2510 this->petiole_curvature.at(petiole_index) /= scale_factor;
2512 if (current_leaf_scale_factor.at(petiole_index) >= 1.f) {
2514 current_leaf_scale_factor.at(petiole_index) = 1.f;
2519 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2525 assert(inflorescence_scale_factor_fraction >= 0 && inflorescence_scale_factor_fraction <= 1);
2527 if (inflorescence_scale_factor_fraction == fbud.current_fruit_scale_factor) {
2531 float delta_scale = inflorescence_scale_factor_fraction / fbud.current_fruit_scale_factor;
2533 fbud.current_fruit_scale_factor = inflorescence_scale_factor_fraction;
2536 for (
int inflorescence = 0; inflorescence < fbud.inflorescence_objIDs.size(); inflorescence++) {
2537 context_ptr->
scaleObjectAboutPoint(fbud.inflorescence_objIDs.at(inflorescence), delta_scale *
make_vec3(1, 1, 1), fbud.inflorescence_bases.at(inflorescence));
2544 this->petiole_radii.resize(0);
2546 this->petiole_colors.resize(0);
2547 this->petiole_length.resize(0);
2548 this->leaf_size_max.resize(0);
2549 this->leaf_rotation.resize(0);
2550 this->leaf_bases.resize(0);
2553 leaf_objIDs.clear();
2556 if (build_context_geometry_petiole) {
2558 petiole_objIDs.resize(0);
2564 if (context_ptr->
doesObjectExist(parent_shoot_ptr->internode_tube_objID)) {
2567 uint tube_prune_index;
2569 tube_prune_index = 0;
2571 tube_prune_index = this->
shoot_index.
x * tube_segments + 1;
2573 if (tube_prune_index < tube_nodes) {
2574 context_ptr->
pruneTubeNodes(parent_shoot_ptr->internode_tube_objID, tube_prune_index);
2580 auto &phytomer = parent_shoot_ptr->phytomers.at(node);
2583 phytomer->removeLeaf();
2586 for (
auto &petiole: phytomer->floral_buds) {
2587 for (
auto &fbud: petiole) {
2588 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
2589 uint objID = fbud.inflorescence_objIDs.at(p);
2591 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
2592 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
2594 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
2597 fbud.peduncle_objIDs.clear();
2598 fbud.inflorescence_objIDs.clear();
2599 fbud.inflorescence_bases.clear();
2606 if (parent_shoot_ptr->childIDs.find(node) != parent_shoot_ptr->childIDs.end()) {
2607 for (
auto childID: parent_shoot_ptr->childIDs.at(node)) {
2608 auto child_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(childID);
2609 if (!child_shoot->phytomers.empty()) {
2610 child_shoot->phytomers.front()->deletePhytomer();
2617 parent_shoot_ptr->shoot_internode_radii.resize(this->
shoot_index.
x);
2618 parent_shoot_ptr->shoot_internode_vertices.resize(this->
shoot_index.
x);
2619 parent_shoot_ptr->phytomers.resize(this->
shoot_index.
x);
2622 for (
const auto &phytomer: parent_shoot_ptr->phytomers) {
2623 phytomer->shoot_index.y = scast<int>(parent_shoot_ptr->phytomers.size());
2625 parent_shoot_ptr->current_node_number = scast<int>(parent_shoot_ptr->phytomers.size());
2629 return (!leaf_bases.empty() && !leaf_bases.front().empty());
2638 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),
2639 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) {
2640 carbohydrate_pool_molC = 0;
2641 phyllochron_counter = 0;
2644 context_ptr = plant_architecture_ptr->context_ptr;
2648 if (parent_shoot_ID >= 0) {
2649 plant_architecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->childIDs[(int) parent_node_index].push_back(shoot_ID);
2653void Shoot::buildShootPhytomers(
float internode_radius,
float internode_length,
float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper) {
2654 for (
int i = 0; i < current_node_number; i++) {
2658 if (current_node_number > 1) {
2659 taper = 1.f - radius_taper * float(i) / float(current_node_number - 1);
2663 appendPhytomer(internode_radius * taper, internode_length, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, shoot_parameters.
phytomer_parameters);
2668 auto shoot_ptr =
this;
2670 assert(shoot_ptr->shoot_parameters.child_shoot_type_labels.size() == shoot_ptr->shoot_parameters.child_shoot_type_probabilities.size());
2672 std::string child_shoot_type_label;
2674 if (shoot_ptr->shoot_parameters.child_shoot_type_labels.empty()) {
2676 child_shoot_type_label = shoot_ptr->shoot_type_label;
2677 }
else if (shoot_ptr->shoot_parameters.child_shoot_type_labels.size() == 1) {
2679 child_shoot_type_label = shoot_ptr->shoot_parameters.child_shoot_type_labels.at(0);
2681 float randf = context_ptr->
randu();
2682 int shoot_type_index = -1;
2683 float cumulative_probability = 0;
2684 for (
int s = 0; s < shoot_ptr->shoot_parameters.child_shoot_type_labels.size(); s++) {
2685 cumulative_probability += shoot_ptr->shoot_parameters.child_shoot_type_probabilities.at(s);
2686 if (randf < cumulative_probability) {
2687 shoot_type_index = s;
2691 if (shoot_type_index < 0) {
2692 shoot_type_index = shoot_ptr->shoot_parameters.child_shoot_type_labels.size() - 1;
2694 child_shoot_type_label = shoot_ptr->shoot_type_label;
2695 if (shoot_type_index >= 0) {
2696 child_shoot_type_label = shoot_ptr->shoot_parameters.child_shoot_type_labels.at(shoot_type_index);
2700 return child_shoot_type_label;
2704 if (node_index >= phytomers.size()) {
2705 helios_runtime_error(
"ERROR (PlantArchitecture::sampleVegetativeBudBreak): Invalid node index. Node index must be less than the number of phytomers on the shoot.");
2708 float probability_min = plantarchitecture_ptr->shoot_types.at(this->shoot_type_label).vegetative_bud_break_probability_min.val();
2709 float probability_decay = plantarchitecture_ptr->shoot_types.at(this->shoot_type_label).vegetative_bud_break_probability_decay_rate.val();
2711 float bud_break_probability;
2713 bud_break_probability = probability_min;
2714 }
else if (probability_decay > 0) {
2716 bud_break_probability = std::fmax(probability_min, 1.f - probability_decay *
float(this->current_node_number - node_index - 1));
2717 }
else if (probability_decay < 0) {
2719 bud_break_probability = std::fmax(probability_min, 1.f - fabs(probability_decay) *
float(node_index));
2721 if (probability_decay == 0.f) {
2722 bud_break_probability = probability_min;
2724 bud_break_probability = 1.f;
2728 bool bud_break =
true;
2729 if (context_ptr->
randu() > bud_break_probability) {
2737 std::string epicormic_shoot_label = plantarchitecture_ptr->plant_instances.at(this->plantID).epicormic_shoot_probability_perlength_per_day.first;
2739 if (epicormic_shoot_label.empty()) {
2743 float epicormic_probability = plantarchitecture_ptr->plant_instances.at(this->plantID).epicormic_shoot_probability_perlength_per_day.second;
2745 if (epicormic_probability == 0) {
2751 epicormic_positions_fraction.clear();
2757 float dta = std::min(time, 1.f);
2759 float shoot_fraction = context_ptr->
randu();
2763 bool new_shoot =
uint((epicormic_probability * shoot_length * dta * elevation > context_ptr->
randu()));
2765 Nshoots +=
uint(new_shoot);
2768 epicormic_positions_fraction.push_back(shoot_fraction);
2774 assert(epicormic_positions_fraction.size() == Nshoots);
2780 float radius_taper,
const std::string &shoot_type_label) {
2781 if (plant_instances.find(plantID) == plant_instances.end()) {
2782 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
2783 }
else if (shoot_types.find(shoot_type_label) == shoot_types.end()) {
2784 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
2787 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
2789 auto shoot_parameters = shoot_types.at(shoot_type_label);
2790 validateShootTypes(shoot_parameters);
2792 if (current_node_number > shoot_parameters.max_nodes.val()) {
2793 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()) +
".");
2796 uint shootID = shoot_tree_ptr->size();
2797 vec3 base_position = plant_instances.at(plantID).base_position;
2800 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));
2801 shoot_tree_ptr->emplace_back(shoot_new);
2804 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
2810 float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label) {
2811 if (plant_instances.find(plantID) == plant_instances.end()) {
2812 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
2813 }
else if (shoot_types.find(shoot_type_label) == shoot_types.end()) {
2814 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
2817 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
2819 auto shoot_parameters = shoot_types.at(shoot_type_label);
2820 validateShootTypes(shoot_parameters);
2822 if (shoot_tree_ptr->empty()) {
2823 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Cannot append shoot to empty shoot. You must call addBaseStemShoot() first for each plant.");
2824 }
else if (parent_shoot_ID >=
int(shoot_tree_ptr->size())) {
2825 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Parent with ID of " + std::to_string(parent_shoot_ID) +
" does not exist.");
2826 }
else if (current_node_number > shoot_parameters.max_nodes.val()) {
2827 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()) +
".");
2828 }
else if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.empty()) {
2829 std::cerr <<
"WARNING (PlantArchitecture::appendShoot): Shoot does not have any phytomers to append." << std::endl;
2833 shoot_tree_ptr->at(parent_shoot_ID)->shoot_parameters.max_nodes = shoot_tree_ptr->at(parent_shoot_ID)->current_node_number;
2834 shoot_tree_ptr->at(parent_shoot_ID)->terminateApicalBud();
2837 int appended_shootID = int(shoot_tree_ptr->size());
2838 uint parent_node = shoot_tree_ptr->at(parent_shoot_ID)->current_node_number - 1;
2839 uint rank = shoot_tree_ptr->at(parent_shoot_ID)->rank;
2840 vec3 base_position = interpolateTube(shoot_tree_ptr->at(parent_shoot_ID)->phytomers.back()->getInternodeNodePositions(), 0.9f);
2843 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));
2844 shoot_tree_ptr->emplace_back(shoot_new);
2847 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
2849 return appended_shootID;
2853 float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label,
uint petiole_index) {
2854 if (plant_instances.find(plantID) == plant_instances.end()) {
2855 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
2856 }
else if (shoot_types.find(shoot_type_label) == shoot_types.end()) {
2857 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
2860 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
2862 if (parent_shoot_ID <= -1 || parent_shoot_ID >= shoot_tree_ptr->size()) {
2863 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Parent with ID of " + std::to_string(parent_shoot_ID) +
" does not exist.");
2864 }
else if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.size() <= parent_node_index) {
2865 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Parent shoot does not have a node " + std::to_string(parent_node_index) +
".");
2869 auto shoot_parameters = shoot_types.at(shoot_type_label);
2870 validateShootTypes(shoot_parameters);
2871 uint parent_rank = (int) shoot_tree_ptr->at(parent_shoot_ID)->rank;
2872 int childID = int(shoot_tree_ptr->size());
2875 const auto parent_shoot_ptr = shoot_tree_ptr->at(parent_shoot_ID);
2877 vec3 shoot_base_position = parent_shoot_ptr->shoot_internode_vertices.at(parent_node_index).back();
2880 vec3 petiole_axis = parent_shoot_ptr->phytomers.at(parent_node_index)->getPetioleAxisVector(0, petiole_index);
2881 shoot_base_position += 0.9f * petiole_axis * parent_shoot_ptr->phytomers.at(parent_node_index)->getInternodeRadius(1.f);
2884 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));
2885 shoot_tree_ptr->emplace_back(shoot_new);
2888 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
2894 float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label) {
2895 if (plant_instances.find(plantID) == plant_instances.end()) {
2896 helios_runtime_error(
"ERROR (PlantArchitecture::addEpicormicShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
2897 }
else if (shoot_types.find(shoot_type_label) == shoot_types.end()) {
2898 helios_runtime_error(
"ERROR (PlantArchitecture::addEpicormicShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
2901 auto &parent_shoot = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
2903 uint parent_node_index = 0;
2904 if (parent_position_fraction > 0) {
2905 parent_node_index = std::ceil(parent_position_fraction *
float(parent_shoot->phytomers.size())) - 1;
2908 vec3 petiole_axis = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->getPetioleAxisVector(0, 0);
2913 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);
2916void PlantArchitecture::validateShootTypes(
ShootParameters &shoot_parameters)
const {
2917 assert(shoot_parameters.child_shoot_type_probabilities.size() == shoot_parameters.child_shoot_type_labels.size());
2919 for (
int ind = shoot_parameters.child_shoot_type_labels.size() - 1; ind >= 0; ind--) {
2920 if (shoot_types.find(shoot_parameters.child_shoot_type_labels.at(ind)) == shoot_types.end()) {
2921 shoot_parameters.child_shoot_type_labels.erase(shoot_parameters.child_shoot_type_labels.begin() + ind);
2922 shoot_parameters.child_shoot_type_probabilities.erase(shoot_parameters.child_shoot_type_probabilities.begin() + ind);
2928 float leaf_scale_factor_fraction) {
2929 if (plant_instances.find(plantID) == plant_instances.end()) {
2930 helios_runtime_error(
"ERROR (PlantArchitecture::appendPhytomerToShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
2933 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
2935 if (shootID >= shoot_tree_ptr->size()) {
2936 helios_runtime_error(
"ERROR (PlantArchitecture::appendPhytomerToShoot): Parent with ID of " + std::to_string(shootID) +
" does not exist.");
2939 auto current_shoot_ptr = plant_instances.at(plantID).shoot_tree.at(shootID);
2941 int pID = current_shoot_ptr->appendPhytomer(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, phytomer_parameters);
2943 current_shoot_ptr->current_node_number++;
2944 current_shoot_ptr->nodes_this_season++;
2946 for (
auto &phytomers: current_shoot_ptr->phytomers) {
2947 phytomers->shoot_index.y = current_shoot_ptr->current_node_number;
2951 if (current_shoot_ptr->current_node_number == current_shoot_ptr->shoot_parameters.max_nodes.val()) {
2952 if (!current_shoot_ptr->shoot_parameters.flowers_require_dormancy && current_shoot_ptr->shoot_parameters.max_terminal_floral_buds.val() > 0) {
2953 current_shoot_ptr->addTerminalFloralBud();
2955 if (current_shoot_ptr->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function !=
nullptr) {
2956 state = BUD_FLOWER_CLOSED;
2957 }
else if (current_shoot_ptr->shoot_parameters.phytomer_parameters.inflorescence.fruit_prototype_function !=
nullptr) {
2958 state = BUD_FRUITING;
2962 for (
auto &fbuds: current_shoot_ptr->phytomers.back()->floral_buds) {
2963 for (
auto &fbud: fbuds) {
2964 if (fbud.isterminal) {
2966 current_shoot_ptr->phytomers.back()->updateInflorescence(fbud);
2974 else if (current_shoot_ptr->nodes_this_season >= current_shoot_ptr->shoot_parameters.max_nodes_per_season.val()) {
2975 if (!current_shoot_ptr->shoot_parameters.flowers_require_dormancy && current_shoot_ptr->shoot_parameters.max_terminal_floral_buds.val() > 0) {
2976 current_shoot_ptr->addTerminalFloralBud();
2977 for (
auto &fbuds: current_shoot_ptr->phytomers.back()->floral_buds) {
2978 for (
auto &fbud: fbuds) {
2979 if (fbud.isterminal) {
2980 fbud.state = BUD_DORMANT;
2981 current_shoot_ptr->phytomers.back()->updateInflorescence(fbud);
2986 current_shoot_ptr->phytomers.at(pID)->isdormant =
true;
2993 if (shoot_types.find(epicormic_shoot_type_label) == shoot_types.end()) {
2994 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Shoot type with label of " + epicormic_shoot_type_label +
" does not exist.");
2995 }
else if (epicormic_probability_perlength_perday < 0) {
2996 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Epicormic probability must be greater than or equal to zero.");
2997 }
else if (plant_instances.find(plantID) == plant_instances.end()) {
2998 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3001 plant_instances.at(plantID).epicormic_shoot_probability_perlength_per_day = std::make_pair(epicormic_shoot_type_label, epicormic_probability_perlength_perday);
3005 build_context_geometry_internode =
false;
3009 build_context_geometry_petiole =
false;
3013 build_context_geometry_peduncle =
false;
3017 ground_clipping_height = ground_height;
3020void PlantArchitecture::incrementPhytomerInternodeGirth(
uint plantID,
uint shootID,
uint node_number,
float dt,
bool update_context_geometry) {
3021 if (plant_instances.find(plantID) == plant_instances.end()) {
3022 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3025 auto shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3027 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3028 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3029 }
else if (node_number >= shoot->current_node_number) {
3030 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.");
3033 auto phytomer = shoot->phytomers.at(node_number);
3036 float leaf_area = phytomer->downstream_leaf_area;
3038 context_ptr->
setObjectData(shoot->internode_tube_objID,
"leaf_area", leaf_area);
3040 float phytomer_age = phytomer->age;
3041 float girth_area_factor = shoot->shoot_parameters.girth_area_factor.val();
3042 if (phytomer_age > 365) {
3043 girth_area_factor = shoot->shoot_parameters.girth_area_factor.val() * 365 / phytomer_age;
3047 float internode_area = girth_area_factor * leaf_area * 1e-4;
3048 phytomer->parent_shoot_ptr->shoot_parameters.girth_area_factor.resample();
3050 float phytomer_radius = sqrtf(internode_area /
PI_F);
3052 auto &segment = shoot->shoot_internode_radii.at(node_number);
3053 for (
float &radius: segment) {
3054 if (phytomer_radius > radius) {
3056 radius = radius + 0.5 * (phytomer_radius - radius);
3060 if (update_context_geometry && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
3061 context_ptr->
setTubeRadii(shoot->internode_tube_objID,
flatten(shoot->shoot_internode_radii));
3065void PlantArchitecture::pruneGroundCollisions(
uint plantID) {
3066 if (plant_instances.find(plantID) == plant_instances.end()) {
3067 helios_runtime_error(
"ERROR (PlantArchitecture::pruneGroundCollisions): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3070 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3071 for (
auto &phytomer: shoot->phytomers) {
3073 if ((phytomer->shoot_index.x == 0 && phytomer->rank > 0) && context_ptr->
doesObjectExist(shoot->internode_tube_objID) && detectGroundCollision(shoot->internode_tube_objID)) {
3074 context_ptr->
deleteObject(shoot->internode_tube_objID);
3075 shoot->terminateApicalBud();
3079 for (
uint petiole = 0; petiole < phytomer->leaf_objIDs.size(); petiole++) {
3080 if (detectGroundCollision(phytomer->leaf_objIDs.at(petiole))) {
3081 phytomer->removeLeaf();
3086 for (
auto &petiole: phytomer->floral_buds) {
3087 for (
auto &fbud: petiole) {
3088 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
3089 uint objID = fbud.inflorescence_objIDs.at(p);
3090 if (detectGroundCollision(objID)) {
3092 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
3093 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
3096 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
3097 uint objID = fbud.peduncle_objIDs.at(p);
3098 if (detectGroundCollision(objID)) {
3101 fbud.peduncle_objIDs.clear();
3102 fbud.inflorescence_objIDs.clear();
3103 fbud.inflorescence_bases.clear();
3128 if (plant_instances.find(plantID) == plant_instances.end()) {
3129 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3132 auto parent_shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3134 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3135 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3136 }
else if (node_number >= parent_shoot->current_node_number) {
3137 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.");
3139 if (leaf_scale_factor_fraction < 0 || leaf_scale_factor_fraction > 1) {
3140 std::cerr <<
"WARNING (PlantArchitecture::setPhytomerLeafScale): Leaf scaling factor was outside the range of 0 to 1. No scaling was applied." << std::endl;
3144 parent_shoot->phytomers.at(node_number)->setLeafScaleFraction(leaf_scale_factor_fraction);
3148 if (plant_instances.find(plantID) == plant_instances.end()) {
3149 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3152 plant_instances.at(plantID).base_position = base_position;
3155 if (!plant_instances.at(plantID).shoot_tree.empty()) {
3156 std::cerr <<
"WARNING (PlantArchitecture::setPlantBasePosition): This function does not work after shoots have been added to the plant." << std::endl;
3161 if (Beta_mu_inclination <= 0.f) {
3162 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3163 }
else if (Beta_nu_inclination <= 0.f) {
3164 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3167 setPlantLeafAngleDistribution_private({plantID}, Beta_mu_inclination, Beta_nu_inclination, 0.f, 0.f,
true,
false);
3171 if (Beta_mu_inclination <= 0.f) {
3172 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3173 }
else if (Beta_nu_inclination <= 0.f) {
3174 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3177 setPlantLeafAngleDistribution_private(plantIDs, Beta_mu_inclination, Beta_nu_inclination, 0.f, 0.f,
true,
false);
3181 if (eccentricity < 0.f || eccentricity > 1.f) {
3182 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAzimuthAngleDistribution): Eccentricity must be between 0 and 1.");
3185 setPlantLeafAngleDistribution_private({plantID}, 0.f, 0.f, eccentricity, ellipse_rotation_degrees,
false,
true);
3189 if (eccentricity < 0.f || eccentricity > 1.f) {
3190 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAzimuthAngleDistribution): Eccentricity must be between 0 and 1.");
3193 setPlantLeafAngleDistribution_private(plantIDs, 0.f, 0.f, eccentricity, ellipse_rotation_degrees,
false,
true);
3197 if (Beta_mu_inclination <= 0.f) {
3198 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3199 }
else if (Beta_nu_inclination <= 0.f) {
3200 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3201 }
else if (eccentricity < 0.f || eccentricity > 1.f) {
3202 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Eccentricity must be between 0 and 1.");
3205 setPlantLeafAngleDistribution_private({plantID}, Beta_mu_inclination, Beta_nu_inclination, eccentricity, ellipse_rotation_degrees,
true,
true);
3209 if (Beta_mu_inclination <= 0.f) {
3210 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3211 }
else if (Beta_nu_inclination <= 0.f) {
3212 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3213 }
else if (eccentricity < 0.f || eccentricity > 1.f) {
3214 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Eccentricity must be between 0 and 1.");
3217 setPlantLeafAngleDistribution_private(plantIDs, Beta_mu_inclination, Beta_nu_inclination, eccentricity, ellipse_rotation_degrees,
true,
true);
3222 if (plant_instances.find(plantID) == plant_instances.end()) {
3223 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3224 }
else if (plant_instances.at(plantID).shoot_tree.empty()) {
3225 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" has no shoots, so could not get a base position.");
3227 return plant_instances.at(plantID).base_position;
3231 std::vector<vec3> positions;
3232 positions.reserve(plantIDs.size());
3233 for (
uint plantID: plantIDs) {
3240 if (plant_instances.find(plantID) == plant_instances.end()) {
3241 helios_runtime_error(
"ERROR (PlantArchitecture::sumPlantLeafArea): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3247 for (
uint objID: leaf_objIDs) {
3255 if (plant_instances.find(plantID) == plant_instances.end()) {
3256 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantStemHeight): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3259 auto base_shoot_ptr = plant_instances.at(plantID).shoot_tree.front();
3261 std::vector<uint> stem_objID{base_shoot_ptr->internode_tube_objID};
3264 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantStemHeight): The plant does not contain any geometry.");
3268 if (base_shoot_ptr->childIDs.find(base_shoot_ptr->current_node_number - 1) != base_shoot_ptr->childIDs.end()) {
3269 auto terminal_children = base_shoot_ptr->childIDs.at(base_shoot_ptr->current_node_number - 1);
3270 for (
uint childID: terminal_children) {
3271 auto child_shoot_ptr = plant_instances.at(plantID).shoot_tree.at(childID);
3272 if (child_shoot_ptr->rank == base_shoot_ptr->rank) {
3273 if (context_ptr->
doesObjectExist(child_shoot_ptr->internode_tube_objID)) {
3274 stem_objID.push_back(child_shoot_ptr->internode_tube_objID);
3284 return max_box.
z - min_box.
z;
3289 if (plant_instances.find(plantID) == plant_instances.end()) {
3290 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantHeight): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3297 return max_box.
z - min_box.
z;
3301 if (plant_instances.find(plantID) == plant_instances.end()) {
3302 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafInclinationAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3308 std::vector<float> leaf_inclination_angles(Nbins, 0.f);
3309 const float dtheta = 0.5f *
PI_F / float(Nbins);
3310 for (
const uint UUID: leaf_UUIDs) {
3312 const float theta =
acos_safe(fabs(normal.
z));
3314 uint bin =
static_cast<uint>(std::floor(theta / dtheta));
3318 if (!std::isnan(area)) {
3319 leaf_inclination_angles.at(bin) += area;
3326 for (
float &angle: leaf_inclination_angles) {
3332 return leaf_inclination_angles;
3336 std::vector<float> leaf_inclination_angles(Nbins, 0.f);
3337 for (
const uint plantID: plantIDs) {
3344 for (
float &angle: leaf_inclination_angles) {
3350 return leaf_inclination_angles;
3354 if (plant_instances.find(plantID) == plant_instances.end()) {
3355 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafAzimuthAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3361 std::vector<float> leaf_azimuth_angles(Nbins, 0.f);
3362 const float dtheta = 2.f *
PI_F /
static_cast<float>(Nbins);
3363 for (
const uint UUID: leaf_UUIDs) {
3367 uint bin =
static_cast<uint>(std::floor(phi / dtheta));
3371 if (!std::isnan(area)) {
3372 leaf_azimuth_angles.at(bin) += area;
3379 for (
float &angle: leaf_azimuth_angles) {
3385 return leaf_azimuth_angles;
3389 std::vector<float> leaf_azimuth_angles(Nbins, 0.f);
3390 for (
const uint plantID: plantIDs) {
3397 for (
float &angle: leaf_azimuth_angles) {
3403 return leaf_azimuth_angles;
3408 if (plant_instances.find(plantID) == plant_instances.end()) {
3409 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafCount): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3416 if (plant_instances.find(plantID) == plant_instances.end()) {
3417 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafBases): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3420 std::vector<vec3> leaf_bases;
3423 size_t total_size = 0;
3424 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
3425 for (
const auto &phytomer: shoot->phytomers) {
3426 total_size += phytomer->leaf_bases.size() * phytomer->leaf_bases.front().size();
3429 leaf_bases.reserve(total_size);
3432 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
3433 for (
const auto &phytomer: shoot->phytomers) {
3434 std::vector<vec3> bases_flat =
flatten(phytomer->leaf_bases);
3435 leaf_bases.insert(leaf_bases.end(), bases_flat.begin(), bases_flat.end());
3443 std::vector<helios::vec3> leaf_bases;
3444 for (
const uint plantID: plantIDs) {
3446 leaf_bases.insert(leaf_bases.end(), bases.begin(), bases.end());
3452 if (plant_instances.find(plantID) == plant_instances.end()) {
3453 helios_runtime_error(
"ERROR (PlantArchitecture::isPlantDormant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3456 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
3457 if (!shoot->isdormant) {
3466 if (plant_instances.find(plantID) == plant_instances.end()) {
3467 helios_runtime_error(
"ERROR (PlantArchitecture::writePlantMeshVertices): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3473 file.open(filename);
3475 if (!file.is_open()) {
3476 helios_runtime_error(
"ERROR (PlantArchitecture::writePlantMeshVertices): Could not open file " + filename +
" for writing.");
3479 for (
uint UUID: plant_UUIDs) {
3481 for (
vec3 &v: vertex) {
3482 file << v.x <<
" " << v.y <<
" " << v.z << std::endl;
3495 if (plant_instances.find(plantID) == plant_instances.end()) {
3496 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantName): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3498 return plant_instances.at(plantID).plant_name;
3502 if (plant_instances.find(plantID) == plant_instances.end()) {
3503 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAge): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3504 }
else if (plant_instances.at(plantID).shoot_tree.empty()) {
3505 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAge): Plant with ID of " + std::to_string(plantID) +
" has no shoots, so could not get a base position.");
3507 return plant_instances.at(plantID).current_age;
3511 if (plant_instances.find(plantID) == plant_instances.end()) {
3512 helios_runtime_error(
"ERROR (PlantArchitecture::harvestPlant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3515 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3516 for (
auto &phytomer: shoot->phytomers) {
3517 for (
auto &petiole: phytomer->floral_buds) {
3518 for (
auto &fbud: petiole) {
3519 if (fbud.state != BUD_DORMANT) {
3520 phytomer->setFloralBudState(BUD_DEAD, fbud);
3529 if (plant_instances.find(plantID) == plant_instances.end()) {
3530 helios_runtime_error(
"ERROR (PlantArchitecture::removePlantLeaves): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3533 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3534 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootLeaves): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3537 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3539 for (
auto &phytomer: shoot->phytomers) {
3540 phytomer->removeLeaf();
3545 if (plant_instances.find(plantID) == plant_instances.end()) {
3546 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootVegetativeBuds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3549 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3550 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootVegetativeBuds): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3553 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3555 for (
auto &phytomer: shoot->phytomers) {
3556 phytomer->setVegetativeBudState(BUD_DEAD);
3561 if (plant_instances.find(plantID) == plant_instances.end()) {
3562 helios_runtime_error(
"ERROR (PlantArchitecture::removePlantLeaves): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3565 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3566 for (
auto &phytomer: shoot->phytomers) {
3567 phytomer->removeLeaf();
3573 if (plant_instances.find(plantID) == plant_instances.end()) {
3574 helios_runtime_error(
"ERROR (PlantArchitecture::makePlantDormant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3577 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3578 shoot->makeDormant();
3580 plant_instances.at(plantID).time_since_dormancy = 0;
3584 if (plant_instances.find(plantID) == plant_instances.end()) {
3585 helios_runtime_error(
"ERROR (PlantArchitecture::breakPlantDormancy): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3588 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3589 shoot->breakDormancy();
3594 if (plant_instances.find(plantID) == plant_instances.end()) {
3595 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3596 }
else if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3597 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Shoot with ID of " + std::to_string(shootID) +
" does not exist on plant " + std::to_string(plantID) +
".");
3598 }
else if (node_index >= plant_instances.at(plantID).shoot_tree.at(shootID)->current_node_number) {
3599 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Node index " + std::to_string(node_index) +
" is out of range for shoot " + std::to_string(shootID) +
".");
3602 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3604 shoot->phytomers.at(node_index)->deletePhytomer();
3606 if (plant_instances.at(plantID).shoot_tree.empty()) {
3607 std::cout <<
"WARNING (PlantArchitecture::pruneBranch): Plant " << plantID <<
" base shoot was pruned." << std::endl;
3612static vec3 orthonormal_axis(
const vec3 &v) {
3621static vec3 rodrigues(
const vec3 &v,
const vec3 &k,
float a) {
3622 float c = std::cos(a);
3623 float s = std::sin(a);
3626 return v * c +
cross(k, v) * s + k * (kv * (1.f - c));
3629void 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,
3630 bool set_azimuth)
const {
3631 for (
uint plantID: plantIDs) {
3632 if (plant_instances.find(plantID) == plant_instances.end()) {
3633 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3640 size_t N = objIDs.size();
3641 assert(bases.size() == N);
3642 if (N == 0 || (!set_elevation && !set_azimuth))
3646 std::vector<float> theta(N), phi(N), theta_t(N), phi_t(N);
3647 for (
size_t i = 0; i < N; ++i) {
3650 if (!std::isfinite(n0.
x) || !std::isfinite(n0.
y) || !std::isfinite(n0.
z) || n0.
magnitude() < 1e-6f) {
3651 n0 =
vec3(0.f, 0.f, 1.f);
3661 if (set_elevation && !set_azimuth) {
3664 }
else if (!set_elevation && set_azimuth) {
3665 theta_t[i] = theta[i];
3675 if (set_elevation && !set_azimuth) {
3677 for (
size_t i = 0; i < N; ++i) {
3678 float elev =
PI_F * 0.5f - theta_t[i];
3684 if (!set_elevation && set_azimuth) {
3686 for (
size_t i = 0; i < N; ++i) {
3687 float elev =
PI_F * 0.5f - theta[i];
3695 std::vector<vec3> V0(N), V1(N);
3696 for (
size_t i = 0; i < N; ++i) {
3697 float e0 =
PI_F * 0.5f - theta[i];
3698 float e1 =
PI_F * 0.5f - theta_t[i];
3704 std::vector<int> assignment(N);
3707 std::vector<std::vector<double>> C(N, std::vector<double>(N));
3708 for (
size_t i = 0; i < N; ++i) {
3709 for (
size_t j = 0; j < N; ++j) {
3710 double d = (V0[i] - V1[j]).magnitude();
3711 C[i][j] = std::isfinite(d) ? d : ((std::numeric_limits<double>::max)() * 0.5);
3714 hung.Solve(C, assignment);
3718 for (
size_t i = 0; i < N; ++i) {
3719 int j = assignment[i];
3722 vec3 u = (j >= 0 && j < (int) N ? V1[j] : V0[i]);
3725 v = (v.
magnitude() < 1e-6f ?
vec3(0, 0, 1) : v.normalize());
3726 u = (u.
magnitude() < 1e-6f ?
vec3(0, 0, 1) : u.normalize());
3729 float dot = std::clamp(v * u, -1.f, 1.f);
3734 if (!set_elevation && set_azimuth) {
3736 axis =
vec3(0.f, 0.f, 1.f);
3739 axis = orthonormal_axis(v);
3745 vec3 r = rodrigues(v, axis, ang);
3746 if (!std::isfinite(r.
x) || !std::isfinite(r.
y) || !std::isfinite(r.
z) || r.
magnitude() < 1e-6f) {
3866 if (plant_instances.find(plantID) == plant_instances.end()) {
3867 helios_runtime_error(
"ERROR (PlantArchitecture::getShootNodeCount): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3868 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
3869 helios_runtime_error(
"ERROR (PlantArchitecture::getShootNodeCount): Shoot ID is out of range.");
3871 return plant_instances.at(plantID).shoot_tree.at(shootID)->current_node_number;
3875 if (plant_instances.find(plantID) == plant_instances.end()) {
3876 helios_runtime_error(
"ERROR (PlantArchitecture::getShootTaper): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3877 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
3881 float r0 = plant_instances.at(plantID).shoot_tree.at(shootID)->shoot_internode_radii.front().front();
3882 float r1 = plant_instances.at(plantID).shoot_tree.at(shootID)->shoot_internode_radii.back().back();
3884 float taper = (r0 - r1) / r0;
3887 }
else if (taper > 1) {
3895 std::vector<uint> objIDs;
3896 objIDs.reserve(plant_instances.size());
3898 for (
const auto &plant: plant_instances) {
3899 objIDs.push_back(plant.first);
3906 if (plant_instances.find(plantID) == plant_instances.end()) {
3907 helios_runtime_error(
"ERROR (PlantArchitecture::getAllPlantObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3910 std::vector<uint> objIDs;
3912 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
3914 objIDs.push_back(shoot->internode_tube_objID);
3916 for (
const auto &phytomer: shoot->phytomers) {
3917 std::vector<uint> petiole_objIDs_flat =
flatten(phytomer->petiole_objIDs);
3918 objIDs.insert(objIDs.end(), petiole_objIDs_flat.begin(), petiole_objIDs_flat.end());
3919 std::vector<uint> leaf_objIDs_flat =
flatten(phytomer->leaf_objIDs);
3920 objIDs.insert(objIDs.end(), leaf_objIDs_flat.begin(), leaf_objIDs_flat.end());
3921 for (
auto &petiole: phytomer->floral_buds) {
3922 for (
auto &fbud: petiole) {
3923 std::vector<uint> inflorescence_objIDs_flat = fbud.inflorescence_objIDs;
3924 objIDs.insert(objIDs.end(), inflorescence_objIDs_flat.begin(), inflorescence_objIDs_flat.end());
3925 std::vector<uint> peduncle_objIDs_flat = fbud.peduncle_objIDs;
3926 objIDs.insert(objIDs.end(), peduncle_objIDs_flat.begin(), peduncle_objIDs_flat.end());
3940 if (plant_instances.find(plantID) == plant_instances.end()) {
3941 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3944 std::vector<uint> objIDs;
3946 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
3948 for (
auto &shoot: shoot_tree) {
3950 objIDs.push_back(shoot->internode_tube_objID);
3958 if (plant_instances.find(plantID) == plant_instances.end()) {
3959 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantPetioleObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3962 std::vector<uint> objIDs;
3964 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
3966 for (
auto &shoot: shoot_tree) {
3967 for (
auto &phytomer: shoot->phytomers) {
3968 for (
auto &petiole: phytomer->petiole_objIDs) {
3969 objIDs.insert(objIDs.end(), petiole.begin(), petiole.end());
3978 if (plant_instances.find(plantID) == plant_instances.end()) {
3979 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3982 std::vector<uint> objIDs;
3984 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
3986 for (
auto &shoot: shoot_tree) {
3987 for (
auto &phytomer: shoot->phytomers) {
3988 for (
auto &leaf_objID: phytomer->leaf_objIDs) {
3989 objIDs.insert(objIDs.end(), leaf_objID.begin(), leaf_objID.end());
3998 std::vector<uint> objIDs;
3999 objIDs.reserve(50 * plantIDs.size());
4000 for (
const uint plantID: plantIDs) {
4002 objIDs.insert(objIDs.end(), leaf_objIDs.begin(), leaf_objIDs.end());
4008 if (plant_instances.find(plantID) == plant_instances.end()) {
4009 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantPeduncleObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4012 std::vector<uint> objIDs;
4014 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4016 for (
auto &shoot: shoot_tree) {
4017 for (
auto &phytomer: shoot->phytomers) {
4018 for (
auto &petiole: phytomer->floral_buds) {
4019 for (
auto &fbud: petiole) {
4020 objIDs.insert(objIDs.end(), fbud.peduncle_objIDs.begin(), fbud.peduncle_objIDs.end());
4030 if (plant_instances.find(plantID) == plant_instances.end()) {
4031 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4034 std::vector<uint> objIDs;
4036 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4038 for (
auto &shoot: shoot_tree) {
4039 for (
auto &phytomer: shoot->phytomers) {
4040 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4041 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4042 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FLOWER_OPEN || phytomer->floral_buds.at(petiole).at(bud).state == BUD_FLOWER_CLOSED) {
4043 objIDs.insert(objIDs.end(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.begin(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.end());
4054 if (plant_instances.find(plantID) == plant_instances.end()) {
4055 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4058 std::vector<uint> objIDs;
4060 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4062 for (
auto &shoot: shoot_tree) {
4063 for (
auto &phytomer: shoot->phytomers) {
4064 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4065 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4066 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FRUITING) {
4067 objIDs.insert(objIDs.end(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.begin(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.end());
4078 if (plant_instances.find(plantID) == plant_instances.end()) {
4079 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantCollisionRelevantObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4082 std::vector<uint> collision_relevant_objects;
4087 if (collision_include_internodes) {
4089 collision_relevant_objects.insert(collision_relevant_objects.end(), internodes.begin(), internodes.end());
4093 if (collision_include_leaves) {
4095 collision_relevant_objects.insert(collision_relevant_objects.end(), leaves.begin(), leaves.end());
4099 if (collision_include_petioles) {
4101 collision_relevant_objects.insert(collision_relevant_objects.end(), petioles.begin(), petioles.end());
4105 if (collision_include_flowers) {
4107 collision_relevant_objects.insert(collision_relevant_objects.end(), flowers.begin(), flowers.end());
4111 if (collision_include_fruit) {
4113 collision_relevant_objects.insert(collision_relevant_objects.end(), fruit.begin(), fruit.end());
4116 return collision_relevant_objects;
4120 std::vector<uint> UUIDs_all;
4121 for (
const auto &instance: plant_instances) {
4123 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4129 std::vector<uint> UUIDs_all;
4130 for (
const auto &instance: plant_instances) {
4133 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4139 std::vector<uint> UUIDs_all;
4140 for (
const auto &instance: plant_instances) {
4143 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4149 std::vector<uint> UUIDs_all;
4150 for (
const auto &instance: plant_instances) {
4153 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4159 std::vector<uint> UUIDs_all;
4160 for (
const auto &instance: plant_instances) {
4163 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4169 std::vector<uint> UUIDs_all;
4170 for (
const auto &instance: plant_instances) {
4173 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4179 std::vector<uint> UUIDs_all;
4180 for (
const auto &instance: plant_instances) {
4183 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4189 std::vector<uint> objIDs_all;
4190 for (
const auto &instance: plant_instances) {
4192 objIDs_all.insert(objIDs_all.end(), objIDs.begin(), objIDs.end());
4198 carbon_model_enabled =
true;
4202 carbon_model_enabled =
false;
4206 if (current_age < 0) {
4207 helios_runtime_error(
"ERROR (PlantArchitecture::addPlantInstance): Current age must be greater than or equal to zero.");
4210 PlantInstance instance(base_position, current_age,
"custom", context_ptr);
4212 plant_instances.emplace(plant_count, instance);
4216 return plant_count - 1;
4220 if (plant_instances.find(plantID) == plant_instances.end()) {
4221 helios_runtime_error(
"ERROR (PlantArchitecture::duplicatePlantInstance): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4224 auto plant_shoot_tree = &plant_instances.at(plantID).shoot_tree;
4228 if (plant_shoot_tree->empty()) {
4232 if (plant_shoot_tree->front()->phytomers.empty()) {
4237 for (
const auto &shoot: *plant_shoot_tree) {
4238 uint shootID_new = 0;
4239 for (
int node = 0; node < shoot->current_node_number; node++) {
4240 auto phytomer = shoot->phytomers.at(node);
4241 float internode_radius = phytomer->internode_radius_initial;
4242 float internode_length_max = phytomer->internode_length_max;
4243 float internode_scale_factor_fraction = phytomer->current_internode_scale_factor;
4244 float leaf_scale_factor_fraction = 1.f;
4248 AxisRotation original_base_rotation = shoot->base_rotation;
4249 if (shoot->parent_shoot_ID == -1) {
4251 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);
4254 uint parent_node = plant_shoot_tree->at(shoot->parent_shoot_ID)->parent_node_index;
4255 uint parent_petiole_index = 0;
4256 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
4257 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,
4258 shoot->shoot_type_label, parent_petiole_index);
4259 parent_petiole_index++;
4264 appendPhytomerToShoot(plantID_new, shootID_new, shoot_types.at(shoot->shoot_type_label).phytomer_parameters, internode_radius, internode_length_max, internode_scale_factor_fraction, leaf_scale_factor_fraction);
4266 auto phytomer_new = plant_instances.at(plantID_new).shoot_tree.at(shootID_new)->phytomers.back();
4267 for (
uint petiole_index = 0; petiole_index < phytomer->petiole_objIDs.size(); petiole_index++) {
4268 phytomer_new->setLeafScaleFraction(petiole_index, phytomer->current_leaf_scale_factor.at(petiole_index));
4277 if (plant_instances.find(plantID) == plant_instances.end()) {
4283 plant_instances.erase(plantID);
4287 for (
uint ID: plantIDs) {
4293 float max_leaf_lifespan,
bool is_evergreen) {
4294 if (plant_instances.find(plantID) == plant_instances.end()) {
4295 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantPhenologicalThresholds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4298 plant_instances.at(plantID).dd_to_dormancy_break = time_to_dormancy_break;
4299 plant_instances.at(plantID).dd_to_flower_initiation = time_to_flower_initiation;
4300 plant_instances.at(plantID).dd_to_flower_opening = time_to_flower_opening;
4301 plant_instances.at(plantID).dd_to_fruit_set = time_to_fruit_set;
4302 plant_instances.at(plantID).dd_to_fruit_maturity = time_to_fruit_maturity;
4303 plant_instances.at(plantID).dd_to_dormancy = time_to_dormancy;
4304 if (max_leaf_lifespan == 0) {
4305 plant_instances.at(plantID).max_leaf_lifespan = 1e6;
4307 plant_instances.at(plantID).max_leaf_lifespan = max_leaf_lifespan;
4309 plant_instances.at(plantID).is_evergreen = is_evergreen;
4313 if (plant_instances.find(plantID) == plant_instances.end()) {
4314 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantCarbohydrateModelParameters): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4317 plant_instances.at(plantID).carb_parameters = carb_parameters;
4321 for (
uint plantID: plantIDs) {
4327 plant_instances.at(plantID).dd_to_dormancy_break = 0;
4328 plant_instances.at(plantID).dd_to_flower_initiation = -1;
4329 plant_instances.at(plantID).dd_to_flower_opening = -1;
4330 plant_instances.at(plantID).dd_to_fruit_set = -1;
4331 plant_instances.at(plantID).dd_to_fruit_maturity = -1;
4332 plant_instances.at(plantID).dd_to_dormancy = 1e6;
4344 std::vector<uint> plantIDs = {plantID};
4349 for (
uint plantID: plantIDs) {
4350 if (plant_instances.find(plantID) == plant_instances.end()) {
4351 helios_runtime_error(
"ERROR (PlantArchitecture::advanceTime): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4359 if (collision_detection_enabled && collision_detection_ptr !=
nullptr) {
4360 rebuildBVHForTimestep();
4364 float phyllochron_min = 9999;
4365 for (
uint plantID: plantIDs) {
4366 PlantInstance &plant_instance = plant_instances.at(plantID);
4367 auto shoot_tree = &plant_instance.shoot_tree;
4368 if (shoot_tree->empty()) {
4371 float phyllochron_min_shoot = shoot_tree->front()->shoot_parameters.phyllochron_min.val();
4372 if (phyllochron_min_shoot < phyllochron_min) {
4373 phyllochron_min = phyllochron_min_shoot;
4375 for (
int i = 1; i < shoot_tree->size(); i++) {
4376 if (shoot_tree->at(i)->shoot_parameters.phyllochron_min.val() < phyllochron_min) {
4377 phyllochron_min_shoot = shoot_tree->at(i)->shoot_parameters.phyllochron_min.val();
4378 if (phyllochron_min_shoot < phyllochron_min) {
4379 phyllochron_min = phyllochron_min_shoot;
4384 if (phyllochron_min == 9999) {
4385 std::cerr <<
"WARNING (PlantArchitecture::advanceTime): No shoots have been added ot the model. Returning.." << std::endl;
4390 if (carbon_model_enabled) {
4391 accumulateShootPhotosynthesis();
4397 if (time_step_days <= phyllochron_min) {
4398 Nsteps = time_step_days;
4401 Nsteps = std::floor(time_step_days / phyllochron_min);
4402 dt_max_days = phyllochron_min;
4405 float remainder_time = time_step_days - dt_max_days * float(Nsteps);
4406 if (remainder_time > 0.f) {
4411 helios::ProgressBar progress_bar(Nsteps, 50, Nsteps > 1 && printmessages,
"Advancing time");
4413 for (
int timestep = 0; timestep < Nsteps; timestep++) {
4416 bool should_rebuild_bvh =
false;
4417 if (collision_detection_enabled && collision_detection_ptr !=
nullptr) {
4421 should_rebuild_bvh = (timestep % 25 == 0);
4423 should_rebuild_bvh = (timestep % 10 == 0);
4427 if (should_rebuild_bvh) {
4428 rebuildBVHForTimestep();
4432 for (
uint plantID: plantIDs) {
4434 if (!plant_primitives.empty()) {
4435 collision_detection_ptr->
registerTree(plantID, plant_primitives);
4441 if (timestep == Nsteps - 1 && remainder_time != 0.f) {
4442 dt_max_days = remainder_time;
4445 for (
uint plantID: plantIDs) {
4446 PlantInstance &plant_instance = plant_instances.at(plantID);
4448 auto shoot_tree = &plant_instance.shoot_tree;
4450 if (shoot_tree->empty()) {
4454 if (plant_instance.current_age <= plant_instance.max_age && plant_instance.current_age + dt_max_days > plant_instance.max_age) {
4455 }
else if (plant_instance.current_age >= plant_instance.max_age) {
4457 shoot_tree->front()->updateShootNodes(
true);
4461 plant_instance.current_age += dt_max_days;
4462 plant_instance.time_since_dormancy += dt_max_days;
4464 if (plant_instance.time_since_dormancy > plant_instance.dd_to_dormancy_break + plant_instance.dd_to_dormancy) {
4465 plant_instance.time_since_dormancy = 0;
4466 for (
const auto &shoot: *shoot_tree) {
4467 shoot->makeDormant();
4468 shoot->phyllochron_counter = 0;
4474 size_t shoot_count = shoot_tree->size();
4475 for (
int i = 0; i < shoot_count; i++) {
4476 auto shoot = shoot_tree->at(i);
4478 for (
auto &phytomer: shoot->phytomers) {
4479 phytomer->age += dt_max_days;
4481 if (phytomer->phytomer_parameters.phytomer_callback_function !=
nullptr) {
4482 phytomer->phytomer_parameters.phytomer_callback_function(phytomer);
4489 if (shoot->isdormant && plant_instance.time_since_dormancy >= plant_instance.dd_to_dormancy_break) {
4490 shoot->phyllochron_counter = 0;
4491 shoot->breakDormancy();
4494 if (shoot->isdormant) {
4499 for (
auto &phytomer: shoot->phytomers) {
4500 if (phytomer->age > plant_instance.max_leaf_lifespan) {
4502 phytomer->removeLeaf();
4505 if (phytomer->floral_buds.empty()) {
4510 for (
auto &petiole: phytomer->floral_buds) {
4511 for (
auto &fbud: petiole) {
4512 if (fbud.state != BUD_DORMANT && fbud.state != BUD_DEAD) {
4513 fbud.time_counter += dt_max_days;
4517 if (shoot->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function !=
nullptr) {
4520 if (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation >= 0.f) {
4522 if ((!shoot->shoot_parameters.flowers_require_dormancy && fbud.time_counter >= plant_instance.dd_to_flower_initiation) ||
4523 (shoot->shoot_parameters.flowers_require_dormancy && fbud.time_counter >= plant_instance.dd_to_flower_initiation)) {
4524 fbud.time_counter = 0;
4525 if (context_ptr->
randu() < shoot->shoot_parameters.flower_bud_break_probability.val()) {
4526 phytomer->setFloralBudState(BUD_FLOWER_CLOSED, fbud);
4528 phytomer->setFloralBudState(BUD_DEAD, fbud);
4530 if (shoot->shoot_parameters.determinate_shoot_growth) {
4531 shoot->terminateApicalBud();
4532 shoot->terminateAxillaryVegetativeBuds();
4537 }
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)) {
4538 if (fbud.time_counter >= plant_instance.dd_to_flower_opening) {
4539 fbud.time_counter = 0;
4540 if (fbud.state == BUD_FLOWER_CLOSED) {
4541 phytomer->setFloralBudState(BUD_FLOWER_OPEN, fbud);
4543 if (context_ptr->
randu() < shoot->shoot_parameters.flower_bud_break_probability.val()) {
4544 phytomer->setFloralBudState(BUD_FLOWER_OPEN, fbud);
4546 phytomer->setFloralBudState(BUD_DEAD, fbud);
4549 if (shoot->shoot_parameters.determinate_shoot_growth) {
4550 shoot->terminateApicalBud();
4551 shoot->terminateAxillaryVegetativeBuds();
4559 if (shoot->shoot_parameters.phytomer_parameters.inflorescence.fruit_prototype_function !=
nullptr) {
4560 if ((fbud.state == BUD_FLOWER_OPEN && plant_instance.dd_to_fruit_set >= 0.f) ||
4562 (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation < 0.f && plant_instance.dd_to_flower_opening < 0.f && plant_instance.dd_to_fruit_set >= 0.f) ||
4564 (fbud.state == BUD_FLOWER_CLOSED && plant_instance.dd_to_flower_opening < 0.f && plant_instance.dd_to_fruit_set >= 0.f)) {
4566 if (fbud.time_counter >= plant_instance.dd_to_fruit_set) {
4567 fbud.time_counter = 0;
4568 if (context_ptr->
randu() < shoot->shoot_parameters.fruit_set_probability.val()) {
4569 phytomer->setFloralBudState(BUD_FRUITING, fbud);
4571 phytomer->setFloralBudState(BUD_DEAD, fbud);
4573 if (shoot->shoot_parameters.determinate_shoot_growth) {
4574 shoot->terminateApicalBud();
4575 shoot->terminateAxillaryVegetativeBuds();
4587 for (
auto &phytomer: shoot->phytomers) {
4589 if (phytomer->current_internode_scale_factor < 1) {
4590 float dL_internode = dt_max_days * shoot->elongation_rate_instantaneous * phytomer->internode_length_max;
4591 float length_scale = fmin(1.f, (phytomer->getInternodeLength() + dL_internode) / phytomer->internode_length_max);
4592 phytomer->setInternodeLengthScaleFraction(length_scale,
false);
4596 if (shoot->shoot_parameters.girth_area_factor.val() > 0.f) {
4597 if (carbon_model_enabled) {
4598 incrementPhytomerInternodeGirth_carb(plantID, shoot->ID, node_index, dt_max_days,
false);
4600 incrementPhytomerInternodeGirth(plantID, shoot->ID, node_index, dt_max_days,
false);
4608 for (
auto &phytomer: shoot->phytomers) {
4610 if (phytomer->hasLeaf()) {
4611 for (
uint petiole_index = 0; petiole_index < phytomer->current_leaf_scale_factor.size(); petiole_index++) {
4612 if (phytomer->current_leaf_scale_factor.at(petiole_index) >= 1) {
4616 float tip_ind = ceil(
float(phytomer->leaf_size_max.at(petiole_index).size() - 1) / 2.f);
4617 float leaf_length = phytomer->current_leaf_scale_factor.at(petiole_index) * phytomer->leaf_size_max.at(petiole_index).at(tip_ind);
4618 float dL_leaf = dt_max_days * shoot->elongation_rate_instantaneous * phytomer->leaf_size_max.at(petiole_index).at(tip_ind);
4619 float scale = fmin(1.f, (leaf_length + dL_leaf) / phytomer->phytomer_parameters.leaf.prototype_scale.val());
4620 phytomer->phytomer_parameters.leaf.prototype_scale.resample();
4621 phytomer->setLeafScaleFraction(petiole_index, scale);
4626 for (
auto &petiole: phytomer->floral_buds) {
4627 for (
auto &fbud: petiole) {
4629 if (fbud.state == BUD_FRUITING && fbud.time_counter > 0) {
4630 float scale = fmin(1, 0.25f + 0.75f * fbud.time_counter / plant_instance.dd_to_fruit_maturity);
4631 phytomer->setInflorescenceScaleFraction(fbud, scale);
4637 uint parent_petiole_index = 0;
4638 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
4639 for (
auto &vbud: petiole) {
4640 if (vbud.state == BUD_ACTIVE && phytomer->age + dt_max_days > shoot->shoot_parameters.vegetative_bud_break_time.val()) {
4641 ShootParameters *new_shoot_parameters = &shoot_types.at(vbud.shoot_type_label);
4642 int parent_node_count = shoot->current_node_number;
4649 float insertion_angle_adjustment = fmin(shoot->shoot_parameters.insertion_angle_tip.val() + shoot->shoot_parameters.insertion_angle_decay_rate.val() *
float(parent_node_count - phytomer->shoot_index.x - 1), 90.f);
4651 new_shoot_parameters->
base_yaw.resample();
4652 if (shoot->shoot_parameters.insertion_angle_decay_rate.val() == 0) {
4653 shoot->shoot_parameters.insertion_angle_tip.resample();
4657 float internode_length_max;
4665 float internode_radius = phytomer->internode_radius_initial;
4667 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);
4669 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
4670 vbud.shoot_ID = childID;
4671 shoot_tree->at(childID)->isdormant =
false;
4674 parent_petiole_index++;
4677 if (output_object_data.at(
"age")) {
4678 if (shoot->build_context_geometry_internode) {
4681 context_ptr->
setObjectData(shoot->internode_tube_objID,
"age", phytomer->age);
4684 if (phytomer->build_context_geometry_petiole) {
4685 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"age", phytomer->age);
4687 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"age", phytomer->age);
4694 if (shoot->current_node_number >= shoot->shoot_parameters.max_nodes.val()) {
4695 shoot->terminateApicalBud();
4699 if (!shoot->meristem_is_alive) {
4704 shoot->phyllochron_counter += dt_max_days;
4705 if (shoot->phyllochron_counter >= shoot->phyllochron_instantaneous && !shoot->phytomers.back()->isdormant) {
4706 float internode_radius = shoot->shoot_parameters.phytomer_parameters.internode.radius_initial.val();
4707 shoot->shoot_parameters.phytomer_parameters.internode.radius_initial.resample();
4708 float internode_length_max = shoot->internode_length_max_shoot_initial;
4709 appendPhytomerToShoot(plantID, shoot->ID, shoot_types.at(shoot->shoot_type_label).phytomer_parameters, internode_radius, internode_length_max, 0.01,
4711 shoot->phyllochron_counter = shoot->phyllochron_counter - shoot->phyllochron_instantaneous;
4715 std::string epicormic_shoot_label = plant_instance.epicormic_shoot_probability_perlength_per_day.first;
4716 if (!epicormic_shoot_label.empty()) {
4717 std::vector<float> epicormic_fraction;
4718 uint Nepicormic = shoot->sampleEpicormicShoot(time_step_days, epicormic_fraction);
4719 for (
int s = 0; s < Nepicormic; s++) {
4720 float internode_radius = shoot_types.at(epicormic_shoot_label).phytomer_parameters.internode.radius_initial.val();
4721 shoot_types.at(epicormic_shoot_label).phytomer_parameters.internode.radius_initial.resample();
4722 float internode_length_max = shoot_types.at(epicormic_shoot_label).internode_length_max.val();
4723 shoot_types.at(epicormic_shoot_label).internode_length_max.resample();
4724 addEpicormicShoot(plantID, shoot->ID, epicormic_fraction.at(s), 1, 0, internode_radius, internode_length_max, 0.01, 0.01, 0, epicormic_shoot_label);
4727 if (carbon_model_enabled) {
4728 if (output_object_data.find(
"carbohydrate_concentration") != output_object_data.end() && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
4729 float shoot_volume = shoot->calculateShootInternodeVolume();
4730 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", shoot->carbohydrate_pool_molC / shoot_volume);
4737 bool should_update_context = collision_detection_enabled && (geometry_update_counter >= geometry_update_frequency);
4740 bool force_update = collision_avoidance_applied && force_update_on_collision;
4742 if (should_update_context || force_update) {
4743 shoot_tree->front()->updateShootNodes(
true);
4747 shoot_tree->front()->updateShootNodes(
false);
4751 collision_avoidance_applied =
false;
4754 if (ground_clipping_height != -99999) {
4755 pruneGroundCollisions(plantID);
4759 for (
auto &shoot: *shoot_tree) {
4760 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
4762 shoot->old_shoot_volume = shoot_volume;
4767 if (carbon_model_enabled) {
4768 subtractShootMaintenanceCarbon(dt_max_days);
4769 subtractShootGrowthCarbon();
4770 checkCarbonPool_transferCarbon(dt_max_days);
4771 checkCarbonPool_adjustPhyllochron(dt_max_days);
4772 checkCarbonPool_abortOrgans(dt_max_days);
4776 if (geometry_update_counter >= geometry_update_frequency) {
4777 geometry_update_counter = 0;
4779 geometry_update_counter++;
4790 if (solid_obstacle_pruning_enabled) {
4791 pruneSolidBoundaryCollisions();
4796 if (!collision_detection_enabled) {
4797 for (
uint plantID: plantIDs) {
4798 if (plant_instances.find(plantID) != plant_instances.end()) {
4799 plant_instances.at(plantID).shoot_tree.front()->updateShootNodes(
true);
4809 if (!solid_obstacle_avoidance_enabled || solid_obstacle_UUIDs.empty() || !solid_obstacle_fruit_adjustment_enabled) {
4813 if (collision_detection_ptr ==
nullptr) {
4818 int debug_failures_shown = 0;
4819 const int max_debug_failures = 0;
4822 helios::ProgressBar progress_bar(plant_instances.size(), 50, plant_instances.size() > 1 && printmessages,
"Adjusting fruit collisions");
4825 for (
const auto &plant_instance: plant_instances) {
4826 uint plantID = plant_instance.first;
4831 if (fruit_objIDs.empty()) {
4836 for (
uint fruit_objID: fruit_objIDs) {
4840 if (fruit_UUIDs.empty()) {
4845 std::vector<uint> collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
4847 if (!collisions.empty()) {
4851 vec3 bbox_min, bbox_max;
4857 const Phytomer *fruit_phytomer =
nullptr;
4858 uint fruit_petiole_index = 0;
4859 uint fruit_bud_index = 0;
4860 bool found_base =
false;
4863 for (
const auto &shoot: plant_instance.second.shoot_tree) {
4864 for (
const auto &phytomer: shoot->phytomers) {
4865 uint petiole_idx = 0;
4866 for (
const auto &petiole: phytomer->floral_buds) {
4867 for (
const auto &fbud: petiole) {
4869 for (
size_t idx = 0; idx < fbud.inflorescence_objIDs.size(); idx++) {
4870 if (fbud.inflorescence_objIDs[idx] == fruit_objID && idx < fbud.inflorescence_bases.size()) {
4872 fruit_base = fbud.inflorescence_bases[idx];
4873 fruit_phytomer = phytomer.get();
4874 fruit_petiole_index = petiole_idx;
4875 fruit_bud_index = fbud.bud_index;
4879 peduncle_axis = phytomer->getPeduncleAxisVector(1.0f, petiole_idx, fbud.bud_index);
4880 }
catch (
const std::exception &e) {
4909 float fruit_radius = 0;
4910 fruit_radius = std::max(fruit_radius, (bbox_max - fruit_base).magnitude());
4911 fruit_radius = std::max(fruit_radius, (bbox_min - fruit_base).magnitude());
4912 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_min.
y, bbox_max.
z) - fruit_base).magnitude());
4913 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_max.
y, bbox_min.
z) - fruit_base).magnitude());
4914 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_min.
y, bbox_min.
z) - fruit_base).magnitude());
4915 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_max.
y, bbox_max.
z) - fruit_base).magnitude());
4916 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_min.
y, bbox_max.
z) - fruit_base).magnitude());
4917 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_max.
y, bbox_min.
z) - fruit_base).magnitude());
4921 float penetration_depth = std::max(0.0f, -bbox_min.
z);
4924 float initial_rotation = 0;
4925 if (fruit_radius > 0 && penetration_depth > 0) {
4927 float angle_estimate = std::asin(std::min(1.0f, penetration_depth / fruit_radius));
4929 initial_rotation = std::min(
deg2rad(35.0f), angle_estimate * 1.5f);
4932 initial_rotation =
deg2rad(10.0f);
4936 initial_rotation = std::max(initial_rotation,
deg2rad(8.0f));
4942 if (peduncle_axis.
magnitude() < 1e-6f) {
4950 vec3 bbox_center = 0.5f * (bbox_min + bbox_max);
4951 vec3 to_fruit_center = bbox_center - fruit_base;
4952 if (to_fruit_center.
magnitude() > 1e-6f) {
4956 to_fruit_center = peduncle_axis;
4961 rotation_axis =
cross(peduncle_axis, to_fruit_center);
4962 if (rotation_axis.
magnitude() < 1e-6f) {
4964 if (std::abs(peduncle_axis.
z) < 0.9f) {
4973 float rotation_step = initial_rotation;
4974 float total_rotation = 0;
4975 const float max_rotation =
deg2rad(120.0f);
4976 const int max_iterations = 25;
4979 bool debug_this_fruit = (debug_failures_shown < max_debug_failures);
4980 if (debug_this_fruit && printmessages) {
4981 std::cout <<
"\n=== DEBUG: Fruit " << fruit_objID <<
" collision adjustment ===" << std::endl;
4982 std::cout <<
"Fruit base: " << fruit_base << std::endl;
4983 std::cout <<
"Fruit bbox: " << bbox_min <<
" to " << bbox_max << std::endl;
4984 std::cout <<
"Fruit radius: " << fruit_radius << std::endl;
4985 std::cout <<
"Penetration depth: " << penetration_depth << std::endl;
4986 std::cout <<
"Peduncle axis: " << peduncle_axis << std::endl;
4987 std::cout <<
"Rotation axis: " << rotation_axis << std::endl;
4988 std::cout <<
"Initial rotation: " <<
rad2deg(initial_rotation) <<
" degrees" << std::endl;
4989 std::cout <<
"Initial collisions: " << collisions.size() << std::endl;
4992 for (
int iter = 0; iter < max_iterations && total_rotation < max_rotation; iter++) {
4995 context_ptr->
rotateObject(fruit_objID, -rotation_step, fruit_base, rotation_axis);
4996 total_rotation += rotation_step;
5000 collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5002 if (debug_this_fruit && printmessages) {
5003 std::cout <<
"Iter " << iter <<
": rotated " <<
rad2deg(rotation_step) <<
" deg (total " <<
rad2deg(total_rotation) <<
"), collisions: " << collisions.size() << std::endl;
5006 if (collisions.empty()) {
5009 float fine_tune_step =
deg2rad(3.0f);
5010 float fine_tune_attempts = 5;
5011 float original_total = total_rotation;
5013 if (debug_this_fruit && printmessages) {
5014 std::cout <<
"Fine-tuning: trying to rotate back down from " <<
rad2deg(total_rotation) <<
" degrees" << std::endl;
5017 for (
int fine_iter = 0; fine_iter < fine_tune_attempts; fine_iter++) {
5019 context_ptr->
rotateObject(fruit_objID, fine_tune_step, fruit_base, rotation_axis);
5023 std::vector<uint> test_collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5025 if (!test_collisions.empty()) {
5027 context_ptr->
rotateObject(fruit_objID, -fine_tune_step, fruit_base, rotation_axis);
5031 total_rotation -= fine_tune_step;
5040 rotation_step *= 0.7f;
5044 if (!collisions.empty()) {
5045 if (debug_this_fruit && printmessages) {
5046 std::cout <<
"FAILED: Fruit " << fruit_objID <<
" still colliding after " <<
rad2deg(total_rotation) <<
" degrees rotation (" << max_iterations <<
" iterations)" << std::endl;
5049 vec3 final_bbox_min, final_bbox_max;
5051 std::cout <<
"Final bbox: " << final_bbox_min <<
" to " << final_bbox_max << std::endl;
5052 std::cout <<
"Lowest point: " << final_bbox_min.
z << std::endl;
5054 debug_failures_shown++;
5068void PlantArchitecture::pruneSolidBoundaryCollisions() {
5069 if (!solid_obstacle_avoidance_enabled || solid_obstacle_UUIDs.empty()) {
5073 if (collision_detection_ptr ==
nullptr) {
5077 if (printmessages) {
5078 std::cout <<
"Performing solid boundary collision detection..." << std::endl;
5083 std::vector<uint> all_plant_primitives;
5087 std::vector<uint> intersecting_primitives = collision_detection_ptr->
findCollisions(solid_obstacle_UUIDs, {}, all_plant_primitives, {},
false);
5092 if (intersecting_primitives.empty()) {
5093 if (printmessages) {
5094 std::cout <<
"No collisions detected - this is unexpected given visible fruit penetration" << std::endl;
5099 if (printmessages) {
5100 std::cout <<
"Intersecting primitives found: " << intersecting_primitives.size() << std::endl;
5104 std::unordered_set<uint> collision_set(intersecting_objIDs.begin(), intersecting_objIDs.end());
5107 for (
auto &[plantID, plant]: plant_instances) {
5108 for (
uint shootID = 0; shootID < plant.shoot_tree.size(); shootID++) {
5109 auto &shoot = plant.shoot_tree.at(shootID);
5110 bool shoot_was_deleted =
false;
5114 if (collision_set.count(shoot->internode_tube_objID)) {
5116 if (shoot->rank != 0) {
5119 shoot_was_deleted =
true;
5125 if (shoot_was_deleted) {
5129 for (
uint node = 0; node < shoot->current_node_number; node++) {
5130 auto &phytomer = shoot->phytomers.at(node);
5133 for (
uint petiole = 0; petiole < phytomer->leaf_objIDs.size(); petiole++) {
5134 for (
uint leaflet = 0; leaflet < phytomer->leaf_objIDs.at(petiole).size(); leaflet++) {
5135 uint leaf_objID = phytomer->leaf_objIDs.at(petiole).at(leaflet);
5136 if (collision_set.count(leaf_objID)) {
5137 phytomer->removeLeaf();
5144 for (
uint petiole = 0; petiole < phytomer->petiole_objIDs.size(); petiole++) {
5145 for (
uint segment = 0; segment < phytomer->petiole_objIDs.at(petiole).size(); segment++) {
5146 uint petiole_objID = phytomer->petiole_objIDs.at(petiole).at(segment);
5147 if (collision_set.count(petiole_objID)) {
5148 phytomer->removeLeaf();
5155 for (
auto &petiole: phytomer->floral_buds) {
5156 for (
auto &fbud: petiole) {
5158 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
5159 uint objID = fbud.inflorescence_objIDs.at(p);
5160 if (collision_set.count(objID)) {
5162 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
5163 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
5167 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
5168 uint objID = fbud.peduncle_objIDs.at(p);
5169 if (collision_set.count(objID)) {
5173 fbud.peduncle_objIDs.clear();
5174 fbud.inflorescence_objIDs.clear();
5175 fbud.inflorescence_bases.clear();
5183 if (shoot_was_deleted) {
5189 if (printmessages) {
5190 std::cout <<
"Solid boundary collision pruning completed" << std::endl;
5194std::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) {
5195 uint Nverts = vertices.size();
5197 if (radii.size() != Nverts || colors.size() != Nverts) {
5198 helios_runtime_error(
"ERROR (makeTubeFromCones): Length of vertex vectors is not consistent.");
5201 std::vector<uint> objIDs;
5202 objIDs.reserve(Nverts - 1);
5204 for (
uint v = 0; v < Nverts - 1; v++) {
5205 if ((vertices.at(v + 1) - vertices.at(v)).magnitude() < 1e-6f) {
5208 float r0 = std::max(radii.at(v), 1e-5f);
5209 float r1 = std::max(radii.at(v + 1), 1e-5f);
5210 objIDs.push_back(context_ptr->
addConeObject(radial_subdivisions, vertices.at(v), vertices.at(v + 1), r0, r1, colors.at(v)));
5216bool PlantArchitecture::detectGroundCollision(
uint objID) {
5217 std::vector<uint> objIDs = {objID};
5218 return detectGroundCollision(objIDs);
5221bool PlantArchitecture::detectGroundCollision(
const std::vector<uint> &objID)
const {
5222 for (
uint ID: objID) {
5225 for (
uint UUID: UUIDs) {
5227 for (
const vec3 &v: vertices) {
5228 if (v.
z < ground_clipping_height) {
5239 if (output_object_data.find(object_data_label) == output_object_data.end()) {
5240 std::cerr <<
"WARNING (PlantArchitecture::optionalOutputObjectData): Output object data of '" << object_data_label <<
"' is not a valid option." << std::endl;
5243 output_object_data.at(object_data_label) =
true;
5247 for (
auto &label: object_data_labels) {
5248 if (output_object_data.find(label) == output_object_data.end()) {
5249 std::cerr <<
"WARNING (PlantArchitecture::optionalOutputObjectData): Output object data of '" << label <<
"' is not a valid option." << std::endl;
5252 output_object_data.at(label) =
true;
5258 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
5259 delete collision_detection_ptr;
5260 collision_detection_ptr =
nullptr;
5261 owns_collision_detection =
false;
5268 owns_collision_detection =
true;
5269 collision_detection_enabled =
true;
5270 collision_target_UUIDs = target_object_UUIDs;
5271 collision_target_object_IDs = target_object_IDs;
5274 petiole_collision_detection_enabled = enable_petiole_collision;
5275 fruit_collision_detection_enabled = enable_fruit_collision;
5284 std::vector<uint> static_obstacles;
5285 static_obstacles.insert(static_obstacles.end(), target_object_UUIDs.begin(), target_object_UUIDs.end());
5286 static_obstacles.insert(static_obstacles.end(), target_object_IDs.begin(), target_object_IDs.end());
5289 rebuildBVHForTimestep();
5292 if (solid_obstacle_avoidance_enabled) {
5293 static_obstacles.insert(static_obstacles.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
5301 for (
uint plant_id: plant_ids) {
5303 if (!plant_primitives.empty()) {
5304 collision_detection_ptr->
registerTree(plant_id, plant_primitives);
5310 }
catch (
const std::exception &e) {
5311 helios_runtime_error(
"ERROR (PlantArchitecture::enableSoftCollisionAvoidance): Failed to create CollisionDetection instance: " + std::string(e.what()));
5316 collision_detection_enabled =
false;
5319 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
5320 delete collision_detection_ptr;
5321 owns_collision_detection =
false;
5324 collision_detection_ptr =
nullptr;
5325 collision_target_UUIDs.clear();
5326 collision_target_object_IDs.clear();
5328 if (printmessages) {
5329 std::cout <<
"Collision detection disabled for plant growth and internal instance cleaned up" << std::endl;
5334 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
5335 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): cone_half_angle_deg must be between 0 and 180 degrees.");
5337 if (look_ahead_distance <= 0.0f) {
5338 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): sample_count must be positive.");
5340 if (inertia_weight < 0.0f || inertia_weight > 1.0f) {
5341 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): inertia_weight must be between 0.0 and 1.0.");
5344 collision_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5345 collision_cone_height = look_ahead_distance;
5346 collision_sample_count = sample_count;
5347 collision_inertia_weight = inertia_weight;
5351 if (collision_detection_ptr ==
nullptr) {
5352 helios_runtime_error(
"ERROR (PlantArchitecture::setStaticObstacles): Collision detection must be enabled before setting static obstacles.");
5357 if (printmessages) {
5358 std::cout <<
"Marked " << target_UUIDs.size() <<
" primitives as static obstacles for collision detection" << std::endl;
5363 return collision_detection_ptr;
5367 collision_include_internodes = include_internodes;
5368 collision_include_leaves = include_leaves;
5369 collision_include_petioles = include_petioles;
5370 collision_include_flowers = include_flowers;
5371 collision_include_fruit = include_fruit;
5376 if (printmessages) {
5377 std::cout <<
"Set collision-relevant organs: internodes=" << (include_internodes ?
"yes" :
"no") <<
", leaves=" << (include_leaves ?
"yes" :
"no") <<
", petioles=" << (include_petioles ?
"yes" :
"no")
5378 <<
", flowers=" << (include_flowers ?
"yes" :
"no") <<
", fruit=" << (include_fruit ?
"yes" :
"no") << std::endl;
5384 solid_obstacle_avoidance_enabled =
true;
5385 solid_obstacle_UUIDs = obstacle_UUIDs;
5386 solid_obstacle_avoidance_distance = avoidance_distance;
5387 solid_obstacle_fruit_adjustment_enabled = enable_fruit_adjustment;
5388 solid_obstacle_pruning_enabled = enable_obstacle_pruning;
5391 if (collision_detection_ptr ==
nullptr) {
5395 owns_collision_detection =
true;
5396 collision_detection_enabled =
true;
5404 rebuildBVHForTimestep();
5405 }
catch (std::exception &e) {
5406 helios_runtime_error(
"ERROR (PlantArchitecture::enableSolidObstacleAvoidance): Failed to create CollisionDetection instance: " + std::string(e.what()));
5411 if (collision_detection_enabled && collision_detection_ptr !=
nullptr && collision_detection_ptr->
isTreeBasedBVHEnabled()) {
5412 std::vector<uint> static_obstacles;
5413 static_obstacles.insert(static_obstacles.end(), collision_target_UUIDs.begin(), collision_target_UUIDs.end());
5414 static_obstacles.insert(static_obstacles.end(), collision_target_object_IDs.begin(), collision_target_object_IDs.end());
5415 static_obstacles.insert(static_obstacles.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
5421void PlantArchitecture::clearBVHCache()
const {
5422 bvh_cached_for_current_growth =
false;
5423 cached_target_geometry.clear();
5424 cached_filtered_geometry.clear();
5428void PlantArchitecture::rebuildBVHForTimestep() {
5429 if (!collision_detection_enabled || collision_detection_ptr ==
nullptr) {
5435 std::vector<uint> target_geometry;
5438 if (solid_obstacle_avoidance_enabled && !solid_obstacle_UUIDs.empty()) {
5439 target_geometry.insert(target_geometry.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
5442 if (!collision_target_UUIDs.empty()) {
5444 std::vector<uint> valid_targets;
5445 for (
uint uuid: collision_target_UUIDs) {
5447 valid_targets.push_back(uuid);
5451 target_geometry.insert(target_geometry.end(), valid_targets.begin(), valid_targets.end());
5452 }
else if (!collision_target_object_IDs.empty()) {
5454 for (
uint objID: collision_target_object_IDs) {
5457 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
5463 std::vector<uint> preserved_solid_obstacles = target_geometry;
5464 target_geometry.clear();
5468 if (collision_include_internodes) {
5470 target_geometry.insert(target_geometry.end(), internode_uuids.begin(), internode_uuids.end());
5472 if (collision_include_leaves) {
5474 target_geometry.insert(target_geometry.end(), leaf_uuids.begin(), leaf_uuids.end());
5476 if (collision_include_petioles) {
5478 target_geometry.insert(target_geometry.end(), petiole_uuids.begin(), petiole_uuids.end());
5480 if (collision_include_flowers) {
5482 target_geometry.insert(target_geometry.end(), flower_uuids.begin(), flower_uuids.end());
5484 if (collision_include_fruit) {
5486 target_geometry.insert(target_geometry.end(), fruit_uuids.begin(), fruit_uuids.end());
5488 }
catch (
const std::exception &e) {
5489 if (printmessages) {
5490 std::cout <<
"Warning: Exception in organ filtering, falling back to all geometry: " << e.what() << std::endl;
5496 target_geometry.insert(target_geometry.end(), preserved_solid_obstacles.begin(), preserved_solid_obstacles.end());
5499 std::vector<uint> all_context_geometry = context_ptr->
getAllUUIDs();
5500 std::set<uint> all_plant_geometry_set;
5503 all_plant_geometry_set.insert(all_plant.begin(), all_plant.end());
5504 }
catch (
const std::exception &e) {
5505 if (printmessages) {
5506 std::cout <<
"Warning: Could not get plant geometry for external obstacle filtering: " << e.what() << std::endl;
5510 for (
uint uuid: all_context_geometry) {
5511 if (all_plant_geometry_set.find(uuid) == all_plant_geometry_set.end()) {
5512 target_geometry.push_back(uuid);
5517 if (!target_geometry.empty()) {
5519 std::vector<uint> plant_geometry;
5522 }
catch (
const std::exception &e) {
5523 if (printmessages) {
5524 std::cout <<
"Warning: Could not get plant geometry for hierarchical BVH: " << e.what() << std::endl;
5526 plant_geometry.clear();
5528 std::set<uint> plant_set(plant_geometry.begin(), plant_geometry.end());
5530 std::vector<uint> static_obstacles;
5531 for (
uint uuid: target_geometry) {
5532 if (plant_set.find(uuid) == plant_set.end()) {
5533 static_obstacles.push_back(uuid);
5540 collision_detection_ptr->
updateBVH(target_geometry,
true);
5544 cached_target_geometry = target_geometry;
5545 cached_filtered_geometry = target_geometry;
5546 bvh_cached_for_current_growth =
true;
5551 if (update_frequency < 1) {
5552 helios_runtime_error(
"ERROR (PlantArchitecture::setGeometryUpdateScheduling): update_frequency must be at least 1.");
5555 geometry_update_frequency = update_frequency;
5556 geometry_update_counter = 0;
5562 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
5563 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
5565 if (look_ahead_distance <= 0.0f) {
5566 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): look_ahead_distance must be positive.");
5568 if (attraction_weight_input < 0.0f || attraction_weight_input > 1.0f) {
5569 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): attraction_weight must be between 0.0 and 1.0.");
5573 attraction_points_enabled =
true;
5574 attraction_points = attraction_points_input;
5575 attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5576 attraction_cone_height = look_ahead_distance;
5577 attraction_weight = attraction_weight_input;
5580 for (
auto &[plantID, plant]: plant_instances) {
5581 plant.attraction_points_enabled =
true;
5582 plant.attraction_points = attraction_points_input;
5583 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5584 plant.attraction_cone_height = look_ahead_distance;
5585 plant.attraction_weight = attraction_weight_input;
5591 attraction_points_enabled =
false;
5592 attraction_points.clear();
5595 for (
auto &[plantID, plant]: plant_instances) {
5596 plant.attraction_points_enabled =
false;
5597 plant.attraction_points.clear();
5602 if (!attraction_points_enabled) {
5603 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Attraction points must be enabled before updating positions.");
5605 if (attraction_points_input.empty()) {
5606 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): attraction_points cannot be empty.");
5610 attraction_points = attraction_points_input;
5613 for (
auto &[plantID, plant]: plant_instances) {
5614 if (plant.attraction_points_enabled) {
5615 plant.attraction_points = attraction_points_input;
5621 if (!attraction_points_enabled) {
5622 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Attraction points must be enabled before updating positions.");
5624 if (attraction_points_input.empty()) {
5625 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): attraction_points cannot be empty.");
5629 attraction_points.insert(attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
5632 for (
auto &[plantID, plant]: plant_instances) {
5633 if (plant.attraction_points_enabled) {
5634 plant.attraction_points.insert(plant.attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
5640 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
5641 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): view_half_angle_deg must be between 0 and 180 degrees.");
5643 if (look_ahead_distance <= 0.0f) {
5644 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): look_ahead_distance must be positive.");
5646 if (attraction_weight_input < 0.0f || attraction_weight_input > 1.0f) {
5647 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): attraction_weight must be between 0.0 and 1.0.");
5649 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
5650 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): obstacle_reduction_factor must be between 0.0 and 1.0.");
5654 attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5655 attraction_cone_height = look_ahead_distance;
5656 attraction_weight = attraction_weight_input;
5657 attraction_obstacle_reduction_factor = obstacle_reduction_factor;
5660 for (
auto &[plantID, plant]: plant_instances) {
5661 if (plant.attraction_points_enabled) {
5662 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5663 plant.attraction_cone_height = look_ahead_distance;
5664 plant.attraction_weight = attraction_weight_input;
5665 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
5669 if (printmessages) {
5670 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;
5671 if (!plant_instances.empty()) {
5672 std::cout <<
"Applied to " << plant_instances.size() <<
" existing plants with attraction points enabled" << std::endl;
5680 if (plant_instances.find(plantID) == plant_instances.end()) {
5681 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
5684 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
5685 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
5687 if (look_ahead_distance <= 0.0f) {
5688 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): look_ahead_distance must be greater than 0.");
5690 if (attraction_points_input.empty()) {
5691 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): attraction_points cannot be empty.");
5694 auto &plant = plant_instances.at(plantID);
5695 plant.attraction_points_enabled =
true;
5696 plant.attraction_points = attraction_points_input;
5697 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5698 plant.attraction_cone_height = look_ahead_distance;
5699 plant.attraction_weight = attraction_weight_input;
5701 if (printmessages) {
5702 std::cout <<
"Enabled attraction points for plant " << plantID <<
" with " << attraction_points_input.size() <<
" target positions" << std::endl;
5703 std::cout <<
"Plant " << plantID <<
" attraction parameters: cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input << std::endl;
5708 if (plant_instances.find(plantID) == plant_instances.end()) {
5709 helios_runtime_error(
"ERROR (PlantArchitecture::disableAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
5712 auto &plant = plant_instances.at(plantID);
5713 plant.attraction_points_enabled =
false;
5714 plant.attraction_points.clear();
5716 if (printmessages) {
5717 std::cout <<
"Disabled attraction points for plant " << plantID <<
" - will use natural growth patterns" << std::endl;
5722 if (plant_instances.find(plantID) == plant_instances.end()) {
5723 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
5726 auto &plant = plant_instances.at(plantID);
5727 if (!plant.attraction_points_enabled) {
5728 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Attraction points must be enabled for plant " + std::to_string(plantID) +
" before updating positions.");
5730 if (attraction_points_input.empty()) {
5731 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): attraction_points cannot be empty.");
5734 plant.attraction_points = attraction_points_input;
5738 if (plant_instances.find(plantID) == plant_instances.end()) {
5739 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
5742 auto &plant = plant_instances.at(plantID);
5743 if (!plant.attraction_points_enabled) {
5744 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Attraction points must be enabled for plant " + std::to_string(plantID) +
" before updating positions.");
5746 if (attraction_points_input.empty()) {
5747 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): attraction_points cannot be empty.");
5750 plant.attraction_points.insert(plant.attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
5754 if (plant_instances.find(plantID) == plant_instances.end()) {
5755 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): Plant with ID " + std::to_string(plantID) +
" does not exist.");
5758 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
5759 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): view_half_angle_deg must be between 0 and 180 degrees.");
5761 if (look_ahead_distance <= 0.0f) {
5762 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): look_ahead_distance must be greater than 0.");
5764 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
5765 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): obstacle_reduction_factor must be between 0 and 1.");
5768 auto &plant = plant_instances.at(plantID);
5769 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5770 plant.attraction_cone_height = look_ahead_distance;
5771 plant.attraction_weight = attraction_weight_input;
5772 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
5774 if (printmessages) {
5775 std::cout <<
"Updated attraction parameters for plant " << plantID <<
": cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input
5776 <<
", obstacle_reduction=" << obstacle_reduction_factor << std::endl;
5780void 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) {
5781 if (plant_instances.find(plantID) == plant_instances.end()) {
5782 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
5785 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
5786 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
5788 if (look_ahead_distance <= 0.0f) {
5789 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): look_ahead_distance must be greater than 0.");
5791 if (attraction_points_input.empty()) {
5792 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): attraction_points cannot be empty.");
5794 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
5795 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): obstacle_reduction_factor must be between 0 and 1.");
5798 auto &plant = plant_instances.at(plantID);
5799 plant.attraction_points_enabled =
true;
5800 plant.attraction_points = attraction_points_input;
5801 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
5802 plant.attraction_cone_height = look_ahead_distance;
5803 plant.attraction_weight = attraction_weight_input;
5804 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
5806 if (printmessages) {
5807 std::cout <<
"Set attraction points for plant " << plantID <<
" with " << attraction_points_input.size() <<
" target positions (internal library call)" << std::endl;
5812 printmessages =
false;
5813 if (collision_detection_ptr !=
nullptr) {
5819 printmessages =
true;
5820 if (collision_detection_ptr !=
nullptr) {