19#include <unordered_set>
22using namespace helios;
25static const float MIN_TUBE_RADIUS_FOR_GEOMETRY = 1e-5f;
26static const float MIN_TUBE_LENGTH_FOR_GEOMETRY = 1e-4f;
28static void renameAutoMaterial(
helios::Context *context_ptr,
uint objID,
const std::string &desired_base_name) {
30 if (UUIDs.empty())
return;
33 if (current_label.substr(0, 7) !=
"__auto_")
return;
40 if (existing_id == current_id)
return;
43 std::string candidate;
45 candidate = desired_base_name +
"_" + std::to_string(suffix++);
51static void renameAutoMaterial(
helios::Context *context_ptr,
const std::vector<uint> &objIDs,
const std::string &desired_base_name) {
52 for (
uint objID : objIDs) {
53 renameAutoMaterial(context_ptr, objID, desired_base_name);
57static float clampOffset(
int count_per_axis,
float offset) {
58 if (count_per_axis > 2) {
59 float denom = 0.5f * float(count_per_axis) - 1.f;
60 if (offset * denom > 1.f) {
67float PlantArchitecture::interpolateTube(
const std::vector<float> &P,
const float frac) {
68 assert(frac >= 0 && frac <= 1);
71 float dl = 1.f / float(P.size() - 1);
74 for (
int i = 0; i < P.size() - 1; i++) {
81 if (frac >= f && (frac <= fplus || std::abs(frac - fplus) < 0.0001)) {
82 float V = P.at(i) + (frac - f) / (fplus - f) * (P.at(i + 1) - P.at(i));
93vec3 PlantArchitecture::interpolateTube(
const std::vector<vec3> &P,
const float frac) {
94 assert(frac >= 0 && frac <= 1);
98 for (
int i = 0; i < P.size() - 1; i++) {
99 dl += (P.at(i + 1) - P.at(i)).magnitude();
103 for (
int i = 0; i < P.size() - 1; i++) {
104 float dseg = (P.at(i + 1) - P.at(i)).magnitude();
106 float fplus = f + dseg / dl;
112 if (frac >= f && (frac <= fplus || fabs(frac - fplus) < 0.0001)) {
113 vec3 V = P.at(i) + (frac - f) / (fplus - f) * (P.at(i + 1) - P.at(i));
129 initializePlantModelRegistrations();
131 output_object_data[
"age"] =
false;
132 output_object_data[
"rank"] =
false;
133 output_object_data[
"plantID"] =
false;
134 output_object_data[
"plant_name"] =
false;
135 output_object_data[
"plant_height"] =
false;
136 output_object_data[
"plant_type"] =
false;
137 output_object_data[
"phenology_stage"] =
false;
138 output_object_data[
"leafID"] =
false;
139 output_object_data[
"peduncleID"] =
false;
140 output_object_data[
"closedflowerID"] =
false;
141 output_object_data[
"openflowerID"] =
false;
142 output_object_data[
"fruitID"] =
false;
143 output_object_data[
"carbohydrate_concentration"] =
false;
147 progress_callback = std::move(callback);
156 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
157 delete collision_detection_ptr;
158 collision_detection_ptr =
nullptr;
159 owns_collision_detection =
false;
165 if (texture_file.empty()) {
169 std::filesystem::path filepath(texture_file);
172 if (filepath.is_absolute() && std::filesystem::exists(filepath)) {
178 if (!resolved_path.empty()) {
179 return resolved_path.string();
184 if (!resolved_path.empty()) {
185 return resolved_path.string();
189 if (texture_file.find(
"assets/") != 0) {
190 std::string filename = std::filesystem::path(texture_file).filename().string();
191 std::string extension = std::filesystem::path(texture_file).extension().string();
194 std::string subdirectory = (extension ==
".obj" || extension ==
".mtl") ?
"assets/obj/" :
"assets/textures/";
195 std::string assets_path = subdirectory + filename;
198 if (!resolved_path.empty()) {
199 return resolved_path.string();
204 helios_runtime_error(
"ERROR (PlantArchitecture): Could not resolve asset file: " + texture_file +
". Tried: direct path, plugin asset path, and assets/ subdirectory prefix.");
209 leaf_aspect_ratio.initialize(1.f, generator);
223 if (generator !=
nullptr) {
233 internode.pitch.initialize(20, generator);
234 internode.phyllotactic_angle.initialize(137.5, generator);
235 internode.radius_initial.initialize(0.001, generator);
241 petiole.petioles_per_internode = 1;
242 petiole.pitch.initialize(90, generator);
243 petiole.radius.initialize(0.001, generator);
244 petiole.length.initialize(0.05, generator);
245 petiole.curvature.initialize(0, generator);
246 petiole.taper.initialize(0, generator);
247 petiole.color = RGB::forestgreen;
249 petiole.radial_subdivisions = 7;
252 leaf.leaves_per_petiole.initialize(1, generator);
253 leaf.pitch.initialize(0, generator);
254 leaf.yaw.initialize(0, generator);
255 leaf.roll.initialize(0, generator);
256 leaf.leaflet_offset.initialize(0, generator);
257 leaf.leaflet_scale = 1;
258 leaf.prototype_scale.initialize(0.05, generator);
262 peduncle.length.initialize(0.05, generator);
263 peduncle.radius.initialize(0.001, generator);
264 peduncle.pitch.initialize(0, generator);
265 peduncle.roll.initialize(0, generator);
266 peduncle.curvature.initialize(0, generator);
267 petiole.color = RGB::forestgreen;
276 inflorescence.flower_prototype_scale.initialize(0.0075, generator);
277 inflorescence.fruit_prototype_scale.initialize(0.0075, generator);
278 inflorescence.fruit_gravity_factor_fraction.initialize(0, generator);
327 if (a_child_shoot_type_labels.size() != a_child_shoot_type_probabilities.size()) {
328 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Child shoot type labels and probabilities must be the same size.");
329 }
else if (a_child_shoot_type_labels.empty()) {
330 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Input argument vectors were empty.");
331 }
else if (
sum(a_child_shoot_type_probabilities) != 1.f) {
332 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Child shoot type probabilities must sum to 1.");
335 this->child_shoot_type_labels = a_child_shoot_type_labels;
336 this->child_shoot_type_probabilities = a_child_shoot_type_probabilities;
340 const std::map<std::string, float> &build_parameters) {
341 if (plant_count_xy.
x <= 0 || plant_count_xy.
y <= 0) {
342 helios_runtime_error(
"ERROR (PlantArchitecture::buildPlantCanopyFromLibrary): Plant count must be greater than zero.");
345 vec2 canopy_extent(plant_spacing_xy.
x *
float(plant_count_xy.
x - 1), plant_spacing_xy.
y *
float(plant_count_xy.
y - 1));
347 std::vector<uint> plantIDs;
348 plantIDs.reserve(plant_count_xy.
x * plant_count_xy.
y);
349 for (
int j = 0; j < plant_count_xy.
y; j++) {
352 if (cancel_flag !=
nullptr && *cancel_flag != 0) {
355 for (
int i = 0; i < plant_count_xy.
x; i++) {
356 if (context_ptr->
randu() < germination_rate) {
357 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));
370 std::vector<uint> plantIDs;
371 plantIDs.reserve(plant_count);
372 for (
int i = 0; i < plant_count; i++) {
374 if (cancel_flag !=
nullptr && *cancel_flag != 0) {
377 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);
386 if (this->shoot_types.find(shoot_type_label) != this->shoot_types.end()) {
388 this->shoot_types.at(shoot_type_label) = shoot_params;
390 this->shoot_types.emplace(shoot_type_label, shoot_params);
395 std::vector<vec3> nodes = parent_shoot_ptr->shoot_internode_vertices.at(
shoot_index.
x);
398 int s_minus = parent_shoot_ptr->shoot_internode_vertices.at(p_minus).size() - 1;
399 nodes.insert(nodes.begin(), parent_shoot_ptr->shoot_internode_vertices.at(p_minus).at(s_minus));
405 std::vector<float> node_radii = parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x);
408 int s_minus = parent_shoot_ptr->shoot_internode_radii.at(p_minus).size() - 1;
409 node_radii.insert(node_radii.begin(), parent_shoot_ptr->shoot_internode_radii.at(p_minus).at(s_minus));
426 if (petiole_index >= this->peduncle_vertices.size()) {
429 if (bud_index >= this->peduncle_vertices.at(petiole_index).size()) {
430 helios_runtime_error(
"ERROR (Phytomer::getPeduncleAxisVector): Floral bud index out of range.");
432 return getAxisVector(stem_fraction, this->peduncle_vertices.at(petiole_index).at(bud_index));
436 assert(stem_fraction >= 0 && stem_fraction <= 1);
439 float frac_plus, frac_minus;
440 if (stem_fraction + df <= 1) {
441 frac_minus = stem_fraction;
442 frac_plus = stem_fraction + df;
444 frac_minus = stem_fraction - df;
445 frac_plus = stem_fraction;
448 const vec3 node_minus = PlantArchitecture::interpolateTube(axis_vertices, frac_minus);
449 const vec3 node_plus = PlantArchitecture::interpolateTube(axis_vertices, frac_plus);
451 vec3 norm = node_plus - node_minus;
458 return parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x).front();
464 for (
int i = 0; i < node_vertices.size() - 1; i++) {
465 length += (node_vertices.at(i + 1) - node_vertices.at(i)).magnitude();
476 return PlantArchitecture::interpolateTube(parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x), stem_fraction);
482 for (
auto &petiole: leaf_objIDs) {
483 for (
auto &leaf_objID: petiole) {
486 float scale_factor = current_leaf_scale_factor.at(p);
487 float scaled_area = obj_area /
powi(scale_factor, 2);
488 leaf_area += scaled_area;
498 if (petiole_index >= leaf_bases.size()) {
500 }
else if (leaf_index >= leaf_bases.at(petiole_index).size()) {
504 return leaf_bases.at(petiole_index).at(leaf_index);
508 for (
auto &petiole: axillary_vegetative_buds) {
509 for (
auto &bud: petiole) {
516 if (petiole_index >= axillary_vegetative_buds.size()) {
519 if (bud_index >= axillary_vegetative_buds.at(petiole_index).size()) {
530 for (
auto &petiole: floral_buds) {
531 for (
auto &fbud: petiole) {
532 if (!fbud.isterminal) {
540 if (petiole_index >= floral_buds.size()) {
543 if (bud_index >= floral_buds.at(petiole_index).size()) {
551 if (fbud.state == state) {
553 }
else if (state == BUD_DORMANT || state == BUD_ACTIVE) {
559 if (plantarchitecture_ptr->carbon_model_enabled) {
560 if (state == BUD_FLOWER_CLOSED || (fbud.state == BUD_ACTIVE && state == BUD_FLOWER_OPEN)) {
562 float flower_cost = calculateFlowerConstructionCosts(fbud);
563 plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->sugar_pool_molC -= flower_cost;
564 }
else if (state == BUD_FRUITING) {
566 float fruit_cost = calculateFruitConstructionCosts(fbud);
567 fbud.previous_fruit_scale_factor = fbud.current_fruit_scale_factor;
568 if (plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->sugar_pool_molC > fruit_cost) {
569 plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->sugar_pool_molC -= fruit_cost;
578 fbud.inflorescence_objIDs.resize(0);
579 fbud.inflorescence_bases.resize(0);
580 fbud.inflorescence_rotation.resize(0);
581 fbud.inflorescence_base_scales.resize(0);
583 if (plantarchitecture_ptr->build_context_geometry_peduncle) {
585 fbud.peduncle_objIDs.resize(0);
590 if (state != BUD_DEAD) {
593 updateInflorescence(fbud);
594 fbud.time_counter = 0;
595 if (fbud.state == BUD_FRUITING) {
601helios::vec3 Phytomer::calculateCollisionAvoidanceDirection(
const helios::vec3 &internode_base_origin,
const helios::vec3 &internode_axis,
bool &collision_detection_active)
const {
602 vec3 collision_optimal_direction;
603 collision_detection_active =
false;
605 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
608 if (!plantarchitecture_ptr->bvh_cached_for_current_growth) {
609 if (plantarchitecture_ptr->printmessages) {
610 std::cout <<
"WARNING: BVH not cached - this indicates rebuildBVHForTimestep() was not called" << std::endl;
612 return collision_optimal_direction;
616 std::vector<uint> filtered_geometry;
620 float look_ahead_distance = plantarchitecture_ptr->collision_cone_height;
624 float max_relevant_distance = look_ahead_distance * 1.1f;
628 filtered_geometry = plantarchitecture_ptr->collision_detection_ptr->
filterGeometryByDistance(internode_base_origin, max_relevant_distance, plantarchitecture_ptr->cached_target_geometry);
632 plantarchitecture_ptr->cached_filtered_geometry = filtered_geometry;
634 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
636 vec3 apex = internode_base_origin;
637 vec3 central_axis = internode_axis;
639 float height = plantarchitecture_ptr->collision_cone_height;
640 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
641 int samples = plantarchitecture_ptr->collision_sample_count;
644 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
647 if (optimal_result.confidence > 0.0f) {
648 collision_optimal_direction = optimal_result.direction;
650 collision_detection_active =
true;
654 return collision_optimal_direction;
657helios::vec3 Phytomer::calculatePetioleCollisionAvoidanceDirection(
const helios::vec3 &petiole_base_origin,
const helios::vec3 &proposed_petiole_axis,
bool &collision_detection_active)
const {
658 vec3 collision_optimal_direction;
659 collision_detection_active =
false;
661 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
663 std::vector<uint> target_geometry;
664 if (!plantarchitecture_ptr->collision_target_UUIDs.empty()) {
665 target_geometry = plantarchitecture_ptr->collision_target_UUIDs;
666 }
else if (!plantarchitecture_ptr->collision_target_object_IDs.empty()) {
667 for (
uint objID: plantarchitecture_ptr->collision_target_object_IDs) {
669 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
677 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
679 vec3 apex = petiole_base_origin;
680 vec3 central_axis = proposed_petiole_axis;
682 float height = plantarchitecture_ptr->collision_cone_height;
683 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
684 int samples = plantarchitecture_ptr->collision_sample_count;
687 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
690 if (optimal_result.confidence > 0.0f) {
691 collision_optimal_direction = optimal_result.direction;
693 collision_detection_active =
true;
697 return collision_optimal_direction;
700helios::vec3 Phytomer::calculateFruitCollisionAvoidanceDirection(
const helios::vec3 &fruit_base_origin,
const helios::vec3 &proposed_fruit_axis,
bool &collision_detection_active)
const {
701 vec3 collision_optimal_direction;
702 collision_detection_active =
false;
705 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
707 std::vector<uint> target_geometry;
708 if (!plantarchitecture_ptr->collision_target_UUIDs.empty()) {
709 target_geometry = plantarchitecture_ptr->collision_target_UUIDs;
710 }
else if (!plantarchitecture_ptr->collision_target_object_IDs.empty()) {
711 for (
uint objID: plantarchitecture_ptr->collision_target_object_IDs) {
713 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
721 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
723 vec3 apex = fruit_base_origin;
724 vec3 central_axis = proposed_fruit_axis;
726 float height = plantarchitecture_ptr->collision_cone_height;
727 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
728 int samples = plantarchitecture_ptr->collision_sample_count;
731 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
734 if (optimal_result.confidence > 0.0f) {
735 collision_optimal_direction = optimal_result.direction;
737 collision_detection_active =
true;
741 static int no_collision_count = 0;
742 if (optimal_result.confidence <= 0.0f) {
743 no_collision_count++;
746 static int no_bvh_count = 0;
750 return collision_optimal_direction;
753bool Phytomer::applySolidObstacleAvoidance(
const helios::vec3 ¤t_position,
helios::vec3 &internode_axis)
const {
754 if (!plantarchitecture_ptr->solid_obstacle_avoidance_enabled || plantarchitecture_ptr->solid_obstacle_UUIDs.empty()) {
764 vec3 growth_direction = internode_axis;
768 float nearest_obstacle_distance;
769 vec3 nearest_obstacle_direction;
772 float hard_detection_cone_angle =
deg2rad(20.0f);
773 float detection_distance = plantarchitecture_ptr->solid_obstacle_avoidance_distance;
775 if (plantarchitecture_ptr->collision_detection_ptr !=
nullptr && plantarchitecture_ptr->collision_detection_ptr->
findNearestSolidObstacleInCone(current_position, growth_direction, hard_detection_cone_angle, detection_distance,
776 plantarchitecture_ptr->solid_obstacle_UUIDs, nearest_obstacle_distance, nearest_obstacle_direction)) {
779 float buffer_distance = detection_distance * 0.05f;
782 float normalized_distance = nearest_obstacle_distance / detection_distance;
783 float buffer_threshold = buffer_distance / detection_distance;
785 vec3 avoidance_direction;
786 float rotation_fraction;
788 if (nearest_obstacle_distance <= buffer_distance) {
791 avoidance_direction = current_position - (current_position + nearest_obstacle_direction * nearest_obstacle_distance);
792 if (avoidance_direction.
magnitude() < 0.001f) {
794 avoidance_direction =
cross(growth_direction, nearest_obstacle_direction);
795 if (avoidance_direction.
magnitude() < 0.001f) {
796 avoidance_direction =
make_vec3(0, 0, 1);
802 rotation_fraction = 1.0f;
805 float buffer_blend_factor = 0.8f;
806 internode_axis = (1.0f - buffer_blend_factor) * growth_direction + buffer_blend_factor * avoidance_direction;
813 float dot_with_obstacle = normalize(growth_direction) * normalize(nearest_obstacle_direction);
814 float angle_deficit =
asin_safe(fabs(dot_with_obstacle));
817 vec3 rotation_axis =
cross(growth_direction, -nearest_obstacle_direction);
819 if (rotation_axis.
magnitude() > 0.001f) {
825 if (rotation_axis.
magnitude() > 0.001f) {
829 float surface_threshold_fraction = 0.2f;
831 if (normalized_distance <= surface_threshold_fraction) {
833 rotation_fraction = 1.0f;
836 float remaining_distance = normalized_distance - surface_threshold_fraction;
837 float max_remaining_distance = 1.0f - surface_threshold_fraction;
840 float distance_factor = remaining_distance / max_remaining_distance;
841 float min_rotation_fraction = 0.05f;
844 rotation_fraction = min_rotation_fraction + (1.0f - min_rotation_fraction) * exp(-3.0f * distance_factor);
848 float rotation_this_step = angle_deficit * rotation_fraction;
851 internode_axis =
rotatePointAboutLine(internode_axis, nullorigin, rotation_axis, rotation_this_step);
863 vec3 attraction_direction;
864 attraction_active =
false;
867 if (plantarchitecture_ptr->plant_instances.find(plantID) != plantarchitecture_ptr->plant_instances.end()) {
868 const auto &plant = plantarchitecture_ptr->plant_instances.at(plantID);
869 if (plant.attraction_points_enabled && !plant.attraction_points.empty()) {
871 vec3 look_direction = internode_axis;
873 float half_angle_degrees =
rad2deg(plant.attraction_cone_half_angle_rad);
874 float look_ahead_distance = plant.attraction_cone_height;
876 vec3 direction_to_closest;
877 if (plantarchitecture_ptr->
detectAttractionPointsInCone(plant.attraction_points, internode_base_origin, look_direction, look_ahead_distance, half_angle_degrees, direction_to_closest)) {
878 attraction_direction = direction_to_closest;
880 attraction_active =
true;
882 return attraction_direction;
887 if (!plantarchitecture_ptr->attraction_points_enabled || plantarchitecture_ptr->attraction_points.empty()) {
888 return attraction_direction;
892 vec3 look_direction = internode_axis;
894 float half_angle_degrees =
rad2deg(plantarchitecture_ptr->attraction_cone_half_angle_rad);
895 float look_ahead_distance = plantarchitecture_ptr->attraction_cone_height;
897 vec3 direction_to_closest;
898 if (plantarchitecture_ptr->
detectAttractionPointsInCone(plantarchitecture_ptr->attraction_points, internode_base_origin, look_direction, look_ahead_distance, half_angle_degrees, direction_to_closest)) {
899 attraction_direction = direction_to_closest;
901 attraction_active =
true;
904 return attraction_direction;
910 if (attraction_points.empty()) {
914 if (look_ahead_distance <= 0.0f) {
920 if (half_angle_degrees <= 0.0f || half_angle_degrees >= 180.0f) {
927 float half_angle_rad = half_angle_degrees *
M_PI / 180.0f;
930 vec3 axis = look_direction;
934 bool found_any =
false;
935 float min_angular_distance = std::numeric_limits<float>::max();
939 for (
const vec3 &point: attraction_points) {
941 vec3 to_point = point - vertex;
942 float distance_to_point = to_point.
magnitude();
945 if (distance_to_point < 1e-6f || distance_to_point > look_ahead_distance) {
950 vec3 direction_to_point = to_point;
954 float cos_angle = axis * direction_to_point;
957 cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
959 float angle = std::acos(cos_angle);
962 if (angle <= half_angle_rad) {
966 if (angle < min_angular_distance) {
967 min_angular_distance = angle;
968 closest_point = point;
975 direction_to_closest = closest_point - vertex;
987 if (attraction_points_input.empty()) {
991 if (look_ahead_distance <= 0.0f) {
997 if (half_angle_degrees <= 0.0f || half_angle_degrees >= 180.0f) {
1004 float half_angle_rad = half_angle_degrees *
M_PI / 180.0f;
1007 vec3 axis = look_direction;
1011 bool found_any =
false;
1012 float min_angular_distance = std::numeric_limits<float>::max();
1016 for (
const vec3 &point: attraction_points_input) {
1018 vec3 to_point = point - vertex;
1019 float distance_to_point = to_point.
magnitude();
1022 if (distance_to_point <= 1e-6 || distance_to_point > look_ahead_distance) {
1027 vec3 direction_to_point = to_point;
1031 float cos_angle = axis * direction_to_point;
1034 cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
1036 float angle = std::acos(cos_angle);
1039 if (angle <= half_angle_rad) {
1043 if (angle < min_angular_distance) {
1044 min_angular_distance = angle;
1045 closest_point = point;
1052 direction_to_closest = closest_point - vertex;
1061 auto shoot_tree_ptr = &plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree;
1064 vec3 parent_internode_axis;
1065 vec3 parent_petiole_axis;
1066 vec3 internode_base_position;
1067 if (phytomers.empty()) {
1069 if (parent_shoot_ID == -1) {
1071 parent_internode_axis =
make_vec3(0, 0, 1);
1072 parent_petiole_axis =
make_vec3(0, -1, 0);
1075 assert(parent_shoot_ID < shoot_tree_ptr->size() && parent_node_index < shoot_tree_ptr->at(parent_shoot_ID)->phytomers.size());
1076 parent_internode_axis = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
1078 if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
1079 parent_petiole_axis =
cross(parent_internode_axis,
make_vec3(0, 0, 1));
1080 if (parent_petiole_axis.
magnitude() < 0.01f) {
1082 parent_petiole_axis =
make_vec3(0, 1, 0);
1086 float phyllotactic_angle = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->internode_phyllotactic_angle;
1087 float cumulative_rotation = float(parent_node_index) * phyllotactic_angle;
1090 parent_petiole_axis = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->getPetioleAxisVector(0.f, parent_petiole_index);
1093 internode_base_position = base_position;
1096 parent_internode_axis = phytomers.back()->getInternodeAxisVector(1.f);
1098 if (phytomers.back()->petiole_vertices.empty()) {
1099 parent_petiole_axis =
cross(parent_internode_axis,
make_vec3(0, 0, 1));
1100 if (parent_petiole_axis.
magnitude() < 0.01f) {
1102 parent_petiole_axis =
make_vec3(0, 1, 0);
1106 uint prev_phytomer_index = phytomers.size() - 1;
1107 float phyllotactic_angle = phytomers.back()->internode_phyllotactic_angle;
1108 float cumulative_rotation = float(prev_phytomer_index) * phyllotactic_angle;
1111 parent_petiole_axis = phytomers.back()->getPetioleAxisVector(0.f, 0);
1113 internode_base_position = shoot_internode_vertices.back().back();
1116 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,
1117 internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, rank, plantarchitecture_ptr, context_ptr);
1118 shoot_tree_ptr->at(ID)->phytomers.push_back(phytomer);
1119 phytomer = shoot_tree_ptr->at(ID)->phytomers.back();
1122 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1125 for (
auto &vbud: petiole) {
1126 phytomer->setVegetativeBudState(BUD_DORMANT, vbud);
1127 vbud.shoot_type_label = child_shoot_type_label;
1131 if (plantarchitecture_ptr->carbon_model_enabled) {
1132 if (sampleVegetativeBudBreak_carb(phytomer->shoot_index.x)) {
1134 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1136 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1141 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1143 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1151 uint petiole_index = 0;
1152 for (
auto &petiole: phytomer->floral_buds) {
1154 for (
auto &fbud: petiole) {
1156 phytomer->setFloralBudState(BUD_DORMANT, fbud);
1160 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1163 fbud.parent_index = petiole_index;
1164 fbud.bud_index = bud_index;
1176 if (plantarchitecture_ptr->build_context_geometry_internode && context_ptr->
doesObjectExist(internode_tube_objID)) {
1178 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1179 context_ptr->
setObjectData(internode_tube_objID,
"age", phytomer->age);
1181 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1182 context_ptr->
setObjectData(internode_tube_objID,
"rank", rank);
1184 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1185 context_ptr->
setObjectData(internode_tube_objID,
"plantID", (
int) plantID);
1187 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1188 context_ptr->
setObjectData(internode_tube_objID,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1191 if (plantarchitecture_ptr->build_context_geometry_petiole) {
1192 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1193 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"age", phytomer->age);
1195 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1196 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"rank", phytomer->rank);
1198 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1199 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"plantID", (
int) plantID);
1201 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1202 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1205 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1206 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"age", phytomer->age);
1208 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1209 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"rank", phytomer->rank);
1211 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1212 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"plantID", (
int) plantID);
1214 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1215 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1218 if (plantarchitecture_ptr->output_object_data.at(
"leafID")) {
1219 for (
auto &petiole: phytomer->leaf_objIDs) {
1220 for (
uint objID: petiole) {
1227 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);
1231 return (
int) phytomers.size() - 1;
1234void Shoot::breakDormancy() {
1237 int phytomer_ind = 0;
1238 for (
auto &phytomer: phytomers) {
1239 for (
auto &petiole: phytomer->floral_buds) {
1240 for (
auto &fbud: petiole) {
1241 if (fbud.state != BUD_DEAD) {
1242 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1244 if (meristem_is_alive && fbud.isterminal) {
1245 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1247 fbud.time_counter = 0;
1250 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1251 for (
auto &vbud: petiole) {
1252 if (vbud.state != BUD_DEAD) {
1253 if (plantarchitecture_ptr->carbon_model_enabled) {
1254 if (sampleVegetativeBudBreak_carb(phytomer_ind)) {
1256 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1258 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1263 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1265 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1272 phytomer->isdormant =
false;
1277void Shoot::makeDormant() {
1280 nodes_this_season = 0;
1282 for (
auto &phytomer: phytomers) {
1283 for (
auto &petiole: phytomer->floral_buds) {
1285 for (
auto &fbud: petiole) {
1286 if (fbud.state != BUD_DORMANT) {
1287 phytomer->setFloralBudState(BUD_DEAD, fbud);
1291 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1292 for (
auto &vbud: petiole) {
1293 if (vbud.state != BUD_DORMANT) {
1294 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1298 if (!plantarchitecture_ptr->plant_instances.at(plantID).is_evergreen) {
1299 phytomer->removeLeaf();
1301 phytomer->isdormant =
true;
1310 this->meristem_is_alive =
false;
1311 this->phyllochron_counter = 0;
1315 for (
auto &phytomer: phytomers) {
1316 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1317 for (
auto &vbud: petiole) {
1318 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1326 for (
int bud = 0; bud < Nbuds; bud++) {
1328 bud_new.isterminal =
true;
1329 bud_new.parent_index = 0;
1330 bud_new.bud_index = bud;
1331 bud_new.base_position = shoot_internode_vertices.back().back();
1332 float pitch_adjustment = 0;
1334 pitch_adjustment =
deg2rad(30);
1336 float yaw_adjustment =
static_cast<float>(bud_new.bud_index) * 2.f *
PI_F /
float(Nbuds);
1338 bud_new.base_rotation = make_AxisRotation(pitch_adjustment, yaw_adjustment, 0);
1339 bud_new.bending_axis =
make_vec3(1, 0, 0);
1341 phytomers.back()->floral_buds.push_back({bud_new});
1348 float shoot_volume = 0;
1350 for (
int p = 0; p < phytomers.size(); p++) {
1351 float phytomer_volume = phytomers.at(p)->calculatePhytomerVolume(p);
1352 shoot_volume += phytomer_volume;
1354 return shoot_volume;
1358 float shoot_length = 0;
1359 for (
const auto &phytomer: phytomers) {
1360 shoot_length += phytomer->getInternodeLength();
1362 return shoot_length;
1372 if (phytomers.empty()) {
1377 if (parent_shoot_ID >= 0) {
1380 auto parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
1382 const vec3 current_origin = shoot_internode_vertices.front().front();
1383 const vec3 updated_origin = parent_shoot->shoot_internode_vertices.at(this->parent_node_index).back();
1384 vec3 shift = updated_origin - current_origin;
1390 for (
auto &phytomer: shoot_internode_vertices) {
1391 for (
vec3 &node: phytomer) {
1398 if (update_context_geometry && plantarchitecture_ptr->build_context_geometry_internode && context_ptr->
doesObjectExist(internode_tube_objID)) {
1404 for (
int p = 0; p < phytomers.size(); p++) {
1405 vec3 petiole_base = shoot_internode_vertices.at(p).back();
1406 if (parent_shoot_ID >= 0) {
1408 auto parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
1411 phytomers.at(p)->setPetioleBase(petiole_base);
1415 for (
const auto &node: childIDs) {
1416 for (
int child_shoot_ID: node.second) {
1417 plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->updateShootNodes(update_context_geometry);
1423 uint phytomer_count = this->phytomers.size();
1425 uint phytomer_index = 0;
1426 if (shoot_fraction > 0) {
1427 phytomer_index = std::ceil(shoot_fraction *
float(phytomer_count)) - 1;
1430 assert(phytomer_index < phytomer_count);
1432 return this->phytomers.at(phytomer_index)->getInternodeAxisVector(0.5);
1436 for (
int i = node_index; i >= 0; i--) {
1437 shoot->phytomers.at(i)->downstream_leaf_area += leaf_area;
1438 shoot->phytomers.at(i)->downstream_leaf_area = std::max(0.f, shoot->phytomers.at(i)->downstream_leaf_area);
1441 if (shoot->parent_shoot_ID >= 0) {
1442 Shoot *parent_shoot = plantarchitecture_ptr->plant_instances.at(shoot->plantID).shoot_tree.at(shoot->parent_shoot_ID).get();
1449 if (start_node_index >= phytomers.size()) {
1455 for (
uint p = start_node_index; p < phytomers.size(); p++) {
1457 auto phytomer = phytomers.at(p);
1458 for (
auto &petiole: phytomer->leaf_objIDs) {
1459 for (
uint objID: petiole) {
1467 if (childIDs.find(p) != childIDs.end()) {
1468 for (
int child_shoot_ID: childIDs.at(p)) {
1469 area += plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->sumShootLeafArea(0);
1479 if (start_node_index >= phytomers.size()) {
1485 for (
uint p = start_node_index; p < phytomers.size(); p++) {
1487 if (childIDs.find(p) != childIDs.end()) {
1488 for (
int child_shoot_ID: childIDs.at(p)) {
1489 volume += plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
1498 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,
1499 helios::Context *context_ptr) : rank(rank), context_ptr(context_ptr), plantarchitecture_ptr(plantarchitecture_ptr) {
1500 this->phytomer_parameters = params;
1503 ShootParameters parent_shoot_parameters = parent_shoot->shoot_parameters;
1505 this->internode_radius_initial = internode_radius;
1506 this->internode_length_max = internode_length_max;
1509 this->rank = parent_shoot->rank;
1510 this->plantID = parent_shoot->plantID;
1511 this->parent_shoot_ID = parent_shoot->ID;
1512 this->parent_shoot_ptr = parent_shoot;
1514 bool build_context_geometry_internode = plantarchitecture_ptr->build_context_geometry_internode;
1515 bool build_context_geometry_petiole = plantarchitecture_ptr->build_context_geometry_petiole;
1516 bool build_context_geometry_peduncle = plantarchitecture_ptr->build_context_geometry_peduncle;
1524 uint Ndiv_internode_length = std::max(
uint(1), phytomer_parameters.
internode.length_segments);
1525 uint Ndiv_internode_radius = std::max(
uint(3), phytomer_parameters.
internode.radial_subdivisions);
1526 uint Ndiv_petiole_length = std::max(
uint(1), phytomer_parameters.
petiole.length_segments);
1527 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
1530 if (phytomer_parameters.
internode.length_segments == 0 || phytomer_parameters.
internode.radial_subdivisions < 3) {
1531 build_context_geometry_internode =
false;
1533 if (phytomer_parameters.
petiole.length_segments == 0 || phytomer_parameters.
petiole.radial_subdivisions < 3) {
1534 build_context_geometry_petiole =
false;
1537 if (phytomer_parameters.
petiole.petioles_per_internode == 0) {
1539 build_context_geometry_petiole =
false;
1540 phytomer_parameters.
leaf.leaves_per_petiole = 0;
1543 if (phytomer_parameters.
petiole.petioles_per_internode < 0) {
1544 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Number of petioles per internode cannot be negative.");
1547 current_internode_scale_factor = internode_length_scale_factor_fraction;
1548 current_leaf_scale_factor.resize(phytomer_parameters.
petiole.petioles_per_internode);
1549 std::fill(current_leaf_scale_factor.begin(), current_leaf_scale_factor.end(), leaf_scale_factor_fraction);
1551 if (internode_radius == 0.f) {
1552 internode_radius = MIN_TUBE_RADIUS_FOR_GEOMETRY;
1556 float internode_length = internode_length_scale_factor_fraction * internode_length_max;
1557 float dr_internode = internode_length / float(phytomer_parameters.
internode.length_segments);
1558 float dr_internode_max = internode_length_max / float(phytomer_parameters.
internode.length_segments);
1559 std::vector<vec3> phytomer_internode_vertices;
1560 std::vector<float> phytomer_internode_radii;
1561 phytomer_internode_vertices.resize(Ndiv_internode_length + 1);
1562 phytomer_internode_vertices.at(0) = internode_base_origin;
1563 phytomer_internode_radii.resize(Ndiv_internode_length + 1);
1564 phytomer_internode_radii.at(0) = internode_radius;
1566 phytomer_parameters.
internode.pitch.resample();
1567 internode_phyllotactic_angle =
deg2rad(phytomer_parameters.
internode.phyllotactic_angle.val());
1568 phytomer_parameters.
internode.phyllotactic_angle.resample();
1571 petiole_length.resize(phytomer_parameters.
petiole.petioles_per_internode);
1573 petiole_radii.resize(phytomer_parameters.
petiole.petioles_per_internode);
1576 peduncle_vertices.resize(phytomer_parameters.
petiole.petioles_per_internode);
1577 peduncle_radii.resize(phytomer_parameters.
petiole.petioles_per_internode);
1578 peduncle_length.resize(phytomer_parameters.
petiole.petioles_per_internode);
1579 peduncle_radius.resize(phytomer_parameters.
petiole.petioles_per_internode);
1580 peduncle_pitch.resize(phytomer_parameters.
petiole.petioles_per_internode);
1581 peduncle_curvature.resize(phytomer_parameters.
petiole.petioles_per_internode);
1582 petiole_pitch.resize(phytomer_parameters.
petiole.petioles_per_internode);
1583 petiole_curvature.resize(phytomer_parameters.
petiole.petioles_per_internode);
1584 petiole_taper.resize(phytomer_parameters.
petiole.petioles_per_internode);
1585 petiole_axis_initial.resize(phytomer_parameters.
petiole.petioles_per_internode);
1586 petiole_rotation_axis.resize(phytomer_parameters.
petiole.petioles_per_internode);
1587 std::vector<float> dr_petiole(phytomer_parameters.
petiole.petioles_per_internode);
1588 std::vector<float> dr_petiole_max(phytomer_parameters.
petiole.petioles_per_internode);
1590 std::vector<bool> suppress_petiole_geometry(phytomer_parameters.
petiole.petioles_per_internode,
false);
1591 for (
int p = 0; p < phytomer_parameters.
petiole.petioles_per_internode; p++) {
1593 petiole_radii.at(p).resize(Ndiv_petiole_length + 1);
1595 suppress_petiole_geometry.at(p) = (phytomer_parameters.
petiole.radius.val() <= 0.f || phytomer_parameters.
petiole.length.val() <= 0.f);
1597 petiole_length.at(p) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.length.val();
1598 if (petiole_length.at(p) <= 0.f) {
1599 petiole_length.at(p) = MIN_TUBE_LENGTH_FOR_GEOMETRY;
1601 dr_petiole.at(p) = petiole_length.at(p) / float(phytomer_parameters.
petiole.length_segments);
1602 dr_petiole_max.at(p) = phytomer_parameters.
petiole.length.val() / float(phytomer_parameters.
petiole.length_segments);
1604 petiole_radii.at(p).at(0) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.radius.val();
1605 if (petiole_radii.at(p).at(0) <= 0.f) {
1606 petiole_radii.at(p).at(0) = MIN_TUBE_RADIUS_FOR_GEOMETRY;
1609 phytomer_parameters.
petiole.length.resample();
1611 petiole_objIDs.resize(phytomer_parameters.
petiole.petioles_per_internode);
1614 leaf_bases.resize(phytomer_parameters.
petiole.petioles_per_internode);
1615 leaf_objIDs.resize(phytomer_parameters.
petiole.petioles_per_internode);
1616 leaf_size_max.resize(phytomer_parameters.
petiole.petioles_per_internode);
1617 leaf_rotation.resize(phytomer_parameters.
petiole.petioles_per_internode);
1618 int leaves_per_petiole = phytomer_parameters.
leaf.leaves_per_petiole.val();
1619 float leaflet_offset_val = clampOffset(leaves_per_petiole, phytomer_parameters.
leaf.leaflet_offset.val());
1620 phytomer_parameters.
leaf.leaves_per_petiole.resample();
1621 for (
uint petiole = 0; petiole < phytomer_parameters.
petiole.petioles_per_internode; petiole++) {
1622 leaf_size_max.at(petiole).resize(leaves_per_petiole);
1623 leaf_rotation.at(petiole).resize(leaves_per_petiole);
1626 internode_colors.resize(Ndiv_internode_length + 1);
1627 internode_colors.at(0) = phytomer_parameters.
internode.color;
1628 petiole_colors.resize(Ndiv_petiole_length + 1);
1629 petiole_colors.at(0) = phytomer_parameters.
petiole.color;
1631 vec3 internode_axis = parent_internode_axis;
1633 vec3 petiole_rotation_axis =
cross(parent_internode_axis, parent_petiole_axis);
1634 if (petiole_rotation_axis ==
make_vec3(0, 0, 0)) {
1635 petiole_rotation_axis =
make_vec3(1, 0, 0);
1639 if (phytomer_index == 0) {
1642 if (phytomer_index == 0) {
1645 if (internode_pitch != 0.f) {
1646 if (phytomer_index == 0) {
1649 if (phytomer_index == 0) {
1653 float roll_nudge = 0.f;
1659 if (phytomer_index == 0) {
1662 if (shoot_base_rotation.roll != 0.f || roll_nudge != 0.f) {
1663 if (phytomer_index == 0) {
1668 if (phytomer_index == 0) {
1672 vec3 base_pitch_axis = -1 *
cross(parent_internode_axis, parent_petiole_axis);
1675 if (shoot_base_rotation.pitch != 0.f) {
1676 if (phytomer_index == 0) {
1680 if (phytomer_index == 0) {
1685 if (shoot_base_rotation.yaw != 0) {
1686 if (phytomer_index == 0) {
1690 if (phytomer_index == 0) {
1702 if (internode_pitch != 0) {
1710 if (internode_axis ==
make_vec3(0, 0, 1)) {
1711 shoot_bending_axis =
make_vec3(0, 1, 0);
1715 vec3 collision_optimal_direction;
1716 bool collision_detection_active =
false;
1717 vec3 attraction_direction;
1718 bool attraction_active =
false;
1719 bool obstacle_found =
false;
1722 collision_optimal_direction = calculateCollisionAvoidanceDirection(internode_base_origin, internode_axis, collision_detection_active);
1725 attraction_direction = calculateAttractionPointDirection(internode_base_origin, internode_axis, attraction_active);
1734 for (
int inode_segment = 1; inode_segment <= Ndiv_internode_length; inode_segment++) {
1736 if ((fabs(parent_shoot->gravitropic_curvature) > 0 || parent_shoot_parameters.
tortuosity.val() > 0) &&
shoot_index.
x > 0) {
1739 float current_curvature_fact = 0.5f - internode_axis.
z / 2.f;
1740 if (internode_axis.
z < 0) {
1741 current_curvature_fact *= 2.f;
1744 float dt = dr_internode_max / float(Ndiv_internode_length);
1746 parent_shoot->curvature_perturbation += -0.5f * parent_shoot->curvature_perturbation * dt + parent_shoot_parameters.
tortuosity.val() * context_ptr->
randn() * sqrt(dt);
1748 float curvature_angle =
deg2rad((parent_shoot->gravitropic_curvature * current_curvature_fact * dr_internode_max + parent_shoot->curvature_perturbation));
1751 parent_shoot->yaw_perturbation += -0.5f * parent_shoot->yaw_perturbation * dt + parent_shoot_parameters.
tortuosity.val() * context_ptr->
randn() * sqrt(dt);
1753 float yaw_angle =
deg2rad((parent_shoot->yaw_perturbation));
1758 vec3 current_position = phytomer_internode_vertices.at(inode_segment - 1);
1759 obstacle_found = applySolidObstacleAvoidance(current_position, internode_axis);
1764 vec3 final_direction = internode_axis;
1766 if (attraction_active) {
1768 float attraction_weight = plantarchitecture_ptr->attraction_weight;
1770 if (obstacle_found) {
1773 attraction_weight *= plantarchitecture_ptr->attraction_obstacle_reduction_factor;
1777 final_direction = (1.0f - attraction_weight) * final_direction + attraction_weight * attraction_direction;
1781 plantarchitecture_ptr->collision_avoidance_applied =
true;
1783 }
else if (collision_detection_active && !obstacle_found) {
1785 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
1788 final_direction = inertia_weight * final_direction + (1.0f - inertia_weight) * collision_optimal_direction;
1792 plantarchitecture_ptr->collision_avoidance_applied =
true;
1795 if (obstacle_found) {
1797 plantarchitecture_ptr->collision_avoidance_applied =
true;
1801 internode_axis = final_direction;
1817 phytomer_internode_vertices.at(inode_segment) = phytomer_internode_vertices.at(inode_segment - 1) + dr_internode * internode_axis;
1819 phytomer_internode_radii.at(inode_segment) = internode_radius;
1820 internode_colors.at(inode_segment) = phytomer_parameters.
internode.color;
1825 parent_shoot_ptr->shoot_internode_vertices.push_back(phytomer_internode_vertices);
1826 parent_shoot_ptr->shoot_internode_radii.push_back(phytomer_internode_radii);
1829 parent_shoot_ptr->shoot_internode_vertices.emplace_back(phytomer_internode_vertices.begin() + 1, phytomer_internode_vertices.end());
1830 parent_shoot_ptr->shoot_internode_radii.emplace_back(phytomer_internode_radii.begin() + 1, phytomer_internode_radii.end());
1834 if (build_context_geometry_internode) {
1836 float texture_repeat_length = 0.25f;
1838 for (
auto &phytomer: parent_shoot_ptr->phytomers) {
1839 length += phytomer->internode_length_max;
1841 std::vector<float> uv_y(phytomer_internode_vertices.size());
1842 float dy = internode_length_max / float(uv_y.size() - 1);
1843 for (
int j = 0; j < uv_y.size(); j++) {
1844 uv_y.at(j) = (length + j * dy) / texture_repeat_length - std::floor((length + j * dy) / texture_repeat_length);
1850 if (!context_ptr->
doesObjectExist(parent_shoot->internode_tube_objID)) {
1852 if (!resolved_internode_texture.empty()) {
1853 parent_shoot->internode_tube_objID = context_ptr->
addTubeObject(Ndiv_internode_radius, phytomer_internode_vertices, phytomer_internode_radii, resolved_internode_texture.c_str(), uv_y);
1855 parent_shoot->internode_tube_objID = context_ptr->
addTubeObject(Ndiv_internode_radius, phytomer_internode_vertices, phytomer_internode_radii, internode_colors);
1858 std::string stem_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot->shoot_type_label +
"_stem";
1859 renameAutoMaterial(context_ptr, parent_shoot->internode_tube_objID, stem_material_name);
1862 for (
int inode_segment = 1; inode_segment <= Ndiv_internode_length; inode_segment++) {
1863 if (!resolved_internode_texture.empty()) {
1864 context_ptr->
appendTubeSegment(parent_shoot->internode_tube_objID, phytomer_internode_vertices.at(inode_segment), phytomer_internode_radii.at(inode_segment), resolved_internode_texture.c_str(),
1865 {uv_y.at(inode_segment - 1), uv_y.at(inode_segment)});
1867 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));
1876 for (
int petiole = 0; petiole < phytomer_parameters.
petiole.petioles_per_internode; petiole++) {
1879 vec3 petiole_axis = internode_axis;
1886 petiole_pitch.at(petiole) =
deg2rad(5.f);
1889 petiole_pitch.at(petiole) =
deg2rad(phytomer_parameters.
petiole.pitch.val());
1890 phytomer_parameters.
petiole.pitch.resample();
1891 if (fabs(petiole_pitch.at(petiole)) <
deg2rad(5.f)) {
1892 petiole_pitch.at(petiole) =
deg2rad(5.f);
1898 if (phytomer_index != 0 && internode_phyllotactic_angle != 0) {
1905 petiole_curvature.at(petiole) = phytomer_parameters.
petiole.curvature.val();
1906 phytomer_parameters.
petiole.curvature.resample();
1908 vec3 petiole_rotation_axis_actual = petiole_rotation_axis;
1909 vec3 petiole_axis_actual = petiole_axis;
1912 float budrot = float(petiole) * 2.f *
PI_F / float(phytomer_parameters.
petiole.petioles_per_internode);
1918 this->petiole_axis_initial.at(petiole) = petiole_axis_actual;
1919 this->petiole_rotation_axis.at(petiole) = petiole_rotation_axis_actual;
1922 vec3 collision_optimal_petiole_direction;
1923 bool petiole_collision_active =
false;
1925 if (plantarchitecture_ptr->petiole_collision_detection_enabled) {
1926 collision_optimal_petiole_direction = calculatePetioleCollisionAvoidanceDirection(phytomer_internode_vertices.back(),
1927 petiole_axis_actual, petiole_collision_active);
1930 if (petiole_collision_active) {
1931 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
1932 vec3 natural_petiole_direction = petiole_axis_actual;
1937 petiole_axis_actual = inertia_weight * natural_petiole_direction + (1.0f - inertia_weight) * collision_optimal_petiole_direction;
1942 vec3 bending_direction = collision_optimal_petiole_direction - (collision_optimal_petiole_direction * natural_petiole_direction) * natural_petiole_direction;
1944 if (bending_direction.
magnitude() > 1e-6f) {
1949 vec3 curvature_axis =
cross(natural_petiole_direction, bending_direction);
1951 if (curvature_axis.
magnitude() > 1e-6f) {
1955 float angular_deviation = acosf(std::max(-1.0f, std::min(1.0f, collision_optimal_petiole_direction * natural_petiole_direction)));
1958 float desired_curvature_deg =
rad2deg(angular_deviation) * (1.0f - inertia_weight);
1961 float curvature_sign = (curvature_axis * petiole_rotation_axis_actual > 0) ? 1.0f : -1.0f;
1964 petiole_curvature.at(petiole) += curvature_sign * desired_curvature_deg * 0.5f;
1970 petiole_taper.at(petiole) = phytomer_parameters.
petiole.taper.val();
1971 phytomer_parameters.
petiole.taper.resample();
1975 for (
int j = 1; j <= Ndiv_petiole_length; j++) {
1976 if (fabs(petiole_curvature.at(petiole)) > 0) {
1982 petiole_radii.at(petiole).at(j) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.radius.val() * (1.f - petiole_taper.at(petiole) / float(Ndiv_petiole_length) * float(j));
1983 petiole_colors.at(j) = phytomer_parameters.
petiole.color;
1986 assert(!std::isnan(petiole_radii.at(petiole).at(j)) && std::isfinite(petiole_radii.at(petiole).at(j)));
1989 if (build_context_geometry_petiole && !suppress_petiole_geometry.at(petiole)) {
1991 if (!petiole_objIDs.at(petiole).empty()) {
1993 std::string petiole_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot->shoot_type_label +
"_petiole";
1994 renameAutoMaterial(context_ptr, petiole_objIDs.at(petiole), petiole_material_name);
2000 std::vector<VegetativeBud> vegetative_buds_new;
2001 vegetative_buds_new.resize(phytomer_parameters.
internode.max_vegetative_buds_per_petiole.val());
2002 phytomer_parameters.
internode.max_vegetative_buds_per_petiole.resample();
2004 axillary_vegetative_buds.push_back(vegetative_buds_new);
2006 std::vector<FloralBud> floral_buds_new;
2007 floral_buds_new.resize(phytomer_parameters.
internode.max_floral_buds_per_petiole.val());
2008 phytomer_parameters.
internode.max_floral_buds_per_petiole.resample();
2011 for (
auto &fbud: floral_buds_new) {
2012 fbud.bud_index = index;
2013 fbud.parent_index = petiole;
2014 float pitch_adjustment = fbud.bud_index * 0.1f *
PI_F / float(axillary_vegetative_buds.size());
2015 float yaw_adjustment = -0.25f *
PI_F + fbud.bud_index * 0.5f *
PI_F / float(axillary_vegetative_buds.size());
2016 fbud.base_rotation = make_AxisRotation(pitch_adjustment, yaw_adjustment, 0);
2017 fbud.base_position = phytomer_internode_vertices.back();
2018 fbud.bending_axis = shoot_bending_axis;
2022 floral_buds.push_back(floral_buds_new);
2026 if (phytomer_parameters.
leaf.prototype.prototype_function ==
nullptr) {
2027 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Leaf prototype function was not defined for shoot type " + parent_shoot->shoot_type_label +
".");
2033 assert(phytomer_parameters.
leaf.prototype.unique_prototype_identifier != 0);
2034 if (phytomer_parameters.
leaf.prototype.unique_prototypes > 0 &&
2035 plantarchitecture_ptr->unique_leaf_prototype_objIDs.find(phytomer_parameters.
leaf.prototype.unique_prototype_identifier) == plantarchitecture_ptr->unique_leaf_prototype_objIDs.end()) {
2036 plantarchitecture_ptr->unique_leaf_prototype_objIDs[phytomer_parameters.
leaf.prototype.unique_prototype_identifier].resize(phytomer_parameters.
leaf.prototype.unique_prototypes);
2037 for (
int prototype = 0; prototype < phytomer_parameters.
leaf.prototype.unique_prototypes; prototype++) {
2038 for (
int leaf = 0; leaf < leaves_per_petiole; leaf++) {
2039 float ind_from_tip = float(leaf) - float(leaves_per_petiole - 1) / 2.f;
2040 uint objID_leaf = phytomer_parameters.
leaf.prototype.prototype_function(context_ptr, &phytomer_parameters.
leaf.prototype, ind_from_tip);
2041 if (phytomer_parameters.
leaf.prototype.prototype_function == GenericLeafPrototype) {
2044 plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).push_back(objID_leaf);
2045 std::string material_base_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot->shoot_type_label +
"_leaf";
2046 renameAutoMaterial(context_ptr, objID_leaf, material_base_name);
2054 for (
int leaf = 0; leaf < leaves_per_petiole; leaf++) {
2055 float ind_from_tip = float(leaf) - float(leaves_per_petiole - 1) / 2.f;
2058 if (phytomer_parameters.
leaf.prototype.unique_prototypes > 0) {
2060 int prototype = context_ptr->
randu(0, phytomer_parameters.
leaf.prototype.unique_prototypes - 1);
2061 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.find(phytomer_parameters.
leaf.prototype.unique_prototype_identifier) != plantarchitecture_ptr->unique_leaf_prototype_objIDs.end());
2062 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).size() > prototype);
2063 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).size() > leaf);
2064 objID_leaf = context_ptr->
copyObject(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).at(leaf));
2067 objID_leaf = phytomer_parameters.
leaf.prototype.prototype_function(context_ptr, &phytomer_parameters.
leaf.prototype, ind_from_tip);
2068 std::string material_base_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot->shoot_type_label +
"_leaf";
2069 renameAutoMaterial(context_ptr, objID_leaf, material_base_name);
2074 if (leaves_per_petiole > 0 && phytomer_parameters.
leaf.leaflet_scale.val() != 1.f && ind_from_tip != 0) {
2075 leaf_size_max.at(petiole).at(leaf) = powf(phytomer_parameters.
leaf.leaflet_scale.val(), fabs(ind_from_tip)) * phytomer_parameters.
leaf.prototype_scale.val();
2077 leaf_size_max.at(petiole).at(leaf) = phytomer_parameters.
leaf.prototype_scale.val();
2079 vec3 leaf_scale = leaf_scale_factor_fraction * leaf_size_max.at(petiole).at(leaf) *
make_vec3(1, 1, 1);
2083 float compound_rotation = 0;
2084 if (leaves_per_petiole > 1) {
2085 if (leaflet_offset_val == 0) {
2086 float dphi =
PI_F / (floor(0.5 *
float(leaves_per_petiole - 1)) + 1);
2087 compound_rotation = -float(
PI_F) + dphi * (leaf + 0.5f);
2089 if (leaf ==
float(leaves_per_petiole - 1) / 2.f) {
2091 compound_rotation = 0;
2092 }
else if (leaf <
float(leaves_per_petiole - 1) / 2.f) {
2093 compound_rotation = -0.5 *
PI_F;
2095 compound_rotation = 0.5 *
PI_F;
2108 if (leaves_per_petiole == 1) {
2110 roll_rot = -
deg2rad(phytomer_parameters.
leaf.roll.val()) * sign;
2111 }
else if (ind_from_tip != 0) {
2112 roll_rot = (
asin_safe(petiole_tip_axis.
z) +
deg2rad(phytomer_parameters.
leaf.roll.val())) * compound_rotation / std::fabs(compound_rotation);
2114 leaf_rotation.at(petiole).at(leaf).roll =
deg2rad(phytomer_parameters.
leaf.roll.val());
2115 phytomer_parameters.
leaf.roll.resample();
2119 leaf_rotation.at(petiole).at(leaf).pitch =
deg2rad(phytomer_parameters.
leaf.pitch.val());
2120 float pitch_rot = leaf_rotation.at(petiole).at(leaf).pitch;
2121 phytomer_parameters.
leaf.pitch.resample();
2122 if (ind_from_tip == 0) {
2125 context_ptr->
rotateObject(objID_leaf, -pitch_rot,
"y");
2128 if (ind_from_tip != 0) {
2129 float sign = -compound_rotation / fabs(compound_rotation);
2130 leaf_rotation.at(petiole).at(leaf).yaw = sign *
deg2rad(phytomer_parameters.
leaf.yaw.val());
2131 float yaw_rot = leaf_rotation.at(petiole).at(leaf).yaw;
2132 phytomer_parameters.
leaf.yaw.resample();
2135 leaf_rotation.at(petiole).at(leaf).yaw = 0;
2139 context_ptr->
rotateObject(objID_leaf, -std::atan2(petiole_tip_axis.
y, petiole_tip_axis.
x) + compound_rotation,
"z");
2153 if (leaves_per_petiole == 1) {
2155 const float r_h = sqrtf(petiole_tip_axis.
x * petiole_tip_axis.
x + petiole_tip_axis.
y * petiole_tip_axis.
y);
2157 float blade_correction = std::atan2(petiole_tip_axis.
z * r_h, r_h * r_h);
2158 const float petiole_len = petiole_length.at(petiole);
2159 const float leaf_size_ref = std::max(leaf_size_max.at(petiole).at(leaf), 1e-6f);
2160 const float length_ratio = std::min(petiole_len / leaf_size_ref, 1.f);
2161 blade_correction *= length_ratio;
2162 const float max_correction = 0.5f *
PI_F -
deg2rad(1.f);
2163 if (blade_correction > max_correction) blade_correction = max_correction;
2164 if (blade_correction < -max_correction) blade_correction = -max_correction;
2165 context_ptr->
rotateObject(objID_leaf, blade_correction *
static_cast<float>(sign), petiole_tip_axis);
2173 if (leaves_per_petiole > 1 && leaflet_offset_val > 0) {
2174 if (ind_from_tip != 0) {
2175 float offset = (fabs(ind_from_tip) - 0.5f) * leaflet_offset_val * phytomer_parameters.
petiole.length.val();
2176 leaf_base = PlantArchitecture::interpolateTube(
petiole_vertices.at(petiole), 1.f - offset / phytomer_parameters.
petiole.length.val());
2182 leaf_objIDs.at(petiole).push_back(objID_leaf);
2183 leaf_bases.at(petiole).push_back(leaf_base);
2185 phytomer_parameters.
leaf.prototype_scale.resample();
2187 inflorescence_bending_axis =
cross(parent_internode_axis, petiole_axis_actual);
2188 if (inflorescence_bending_axis ==
make_vec3(0, 0, 0)) {
2189 inflorescence_bending_axis =
make_vec3(1, 0, 0);
2194 if (phytomer_parameters.
petiole.petioles_per_internode == 0) {
2195 std::vector<VegetativeBud> vegetative_buds_new;
2196 vegetative_buds_new.resize(phytomer_parameters.
internode.max_vegetative_buds_per_petiole.val());
2197 phytomer_parameters.
internode.max_vegetative_buds_per_petiole.resample();
2198 axillary_vegetative_buds.push_back(vegetative_buds_new);
2200 std::vector<FloralBud> floral_buds_new;
2201 floral_buds_new.resize(phytomer_parameters.
internode.max_floral_buds_per_petiole.val());
2202 phytomer_parameters.
internode.max_floral_buds_per_petiole.resample();
2203 floral_buds.push_back(floral_buds_new);
2209 const auto &segment = parent_shoot_ptr->shoot_internode_radii.at(node_number);
2212 float avg_radius = 0.0f;
2213 for (
float radius: segment) {
2214 avg_radius += radius;
2216 avg_radius /= scast<float>(segment.size());
2222 float volume =
PI_F * avg_radius * avg_radius * length;
2227void Phytomer::createInflorescenceGeometry(
FloralBud &fbud,
const helios::vec3 &fruit_base,
const helios::vec3 &peduncle_axis,
float pitch,
float roll,
float azimuth,
float yaw_compound,
float scale_factor,
bool is_open_flower) {
2231 if (fbud.state == BUD_FRUITING) {
2232 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2234 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2235 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_fruit_prototype_objIDs.at(phytomer_parameters.
inflorescence.fruit_prototype_function).at(prototype));
2238 objID_fruit = phytomer_parameters.
inflorescence.fruit_prototype_function(context_ptr, 1);
2239 std::string fruit_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_fruit";
2240 renameAutoMaterial(context_ptr, objID_fruit, fruit_material_name);
2244 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2246 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2247 if (is_open_flower) {
2248 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_open_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype));
2250 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_closed_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype));
2254 objID_fruit = phytomer_parameters.
inflorescence.flower_prototype_function(context_ptr, 1, is_open_flower);
2255 std::string flower_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name + (is_open_flower ?
"_flower_open" :
"_flower_closed");
2256 renameAutoMaterial(context_ptr, objID_fruit, flower_material_name);
2262 context_ptr->
scaleObject(objID_fruit, fruit_scale);
2265 if (std::abs(roll) > 1e-6) {
2268 if (std::abs(pitch) > 1e-6) {
2271 if (std::abs(azimuth) > 1e-6) {
2279 if (std::abs(yaw_compound) > 1e-6) {
2280 context_ptr->
rotateObject(objID_fruit, yaw_compound, fruit_base, peduncle_axis);
2284 fbud.inflorescence_objIDs.push_back(objID_fruit);
2285 fbud.inflorescence_bases.push_back(fruit_base);
2288 flower_rotation.pitch = pitch;
2289 flower_rotation.yaw = yaw_compound;
2290 flower_rotation.roll = roll;
2291 flower_rotation.azimuth = azimuth;
2292 flower_rotation.peduncle_axis = peduncle_axis;
2293 fbud.inflorescence_rotation.push_back(flower_rotation);
2295 fbud.inflorescence_base_scales.push_back(scale_factor);
2297 assert(fbud.inflorescence_objIDs.size() == fbud.inflorescence_bases.size());
2298 assert(fbud.inflorescence_bases.size() == fbud.inflorescence_rotation.size());
2299 assert(fbud.inflorescence_rotation.size() == fbud.inflorescence_base_scales.size());
2302void PlantArchitecture::ensureInflorescencePrototypesInitialized(
const PhytomerParameters ¶ms,
const std::string &plant_name) {
2303 if (params.inflorescence.unique_prototypes > 0) {
2305 if (params.inflorescence.flower_prototype_function !=
nullptr && unique_closed_flower_prototype_objIDs.find(params.inflorescence.flower_prototype_function) == unique_closed_flower_prototype_objIDs.end()) {
2306 unique_closed_flower_prototype_objIDs[params.inflorescence.flower_prototype_function].resize(params.inflorescence.unique_prototypes);
2307 for (
int prototype = 0; prototype < params.inflorescence.unique_prototypes; prototype++) {
2308 uint objID_flower = params.inflorescence.flower_prototype_function(context_ptr, 1,
false);
2309 unique_closed_flower_prototype_objIDs.at(params.inflorescence.flower_prototype_function).at(prototype) = objID_flower;
2310 renameAutoMaterial(context_ptr, objID_flower, plant_name +
"_flower_closed");
2315 if (params.inflorescence.flower_prototype_function !=
nullptr && unique_open_flower_prototype_objIDs.find(params.inflorescence.flower_prototype_function) == unique_open_flower_prototype_objIDs.end()) {
2316 unique_open_flower_prototype_objIDs[params.inflorescence.flower_prototype_function].resize(params.inflorescence.unique_prototypes);
2317 for (
int prototype = 0; prototype < params.inflorescence.unique_prototypes; prototype++) {
2318 uint objID_flower = params.inflorescence.flower_prototype_function(context_ptr, 1,
true);
2319 unique_open_flower_prototype_objIDs.at(params.inflorescence.flower_prototype_function).at(prototype) = objID_flower;
2320 renameAutoMaterial(context_ptr, objID_flower, plant_name +
"_flower_open");
2325 if (params.inflorescence.fruit_prototype_function !=
nullptr && unique_fruit_prototype_objIDs.find(params.inflorescence.fruit_prototype_function) == unique_fruit_prototype_objIDs.end()) {
2326 unique_fruit_prototype_objIDs[params.inflorescence.fruit_prototype_function].resize(params.inflorescence.unique_prototypes);
2327 for (
int prototype = 0; prototype < params.inflorescence.unique_prototypes; prototype++) {
2328 uint objID_fruit = params.inflorescence.fruit_prototype_function(context_ptr, 1);
2329 unique_fruit_prototype_objIDs.at(params.inflorescence.fruit_prototype_function).at(prototype) = objID_fruit;
2330 renameAutoMaterial(context_ptr, objID_fruit, plant_name +
"_fruit");
2337void Phytomer::updateInflorescence(
FloralBud &fbud) {
2338 bool build_context_geometry_peduncle = plantarchitecture_ptr->build_context_geometry_peduncle;
2340 uint Ndiv_peduncle_length = std::max(
uint(1), phytomer_parameters.
peduncle.length_segments);
2341 uint Ndiv_peduncle_radius = std::max(
uint(3), phytomer_parameters.
peduncle.radial_subdivisions);
2342 if (phytomer_parameters.
peduncle.length_segments == 0 || phytomer_parameters.
peduncle.radial_subdivisions < 3) {
2343 build_context_geometry_peduncle =
false;
2347 float peduncle_length = phytomer_parameters.
peduncle.length.val();
2348 float dr_peduncle = peduncle_length / float(Ndiv_peduncle_length);
2350 std::vector<vec3> peduncle_vertices(phytomer_parameters.
peduncle.length_segments + 1);
2351 peduncle_vertices.at(0) = fbud.base_position;
2352 std::vector<float> peduncle_radii(phytomer_parameters.
peduncle.length_segments + 1);
2353 peduncle_radii.at(0) = phytomer_parameters.
peduncle.radius.val();
2354 std::vector<RGBcolor> peduncle_colors(phytomer_parameters.
peduncle.length_segments + 1);
2355 peduncle_colors.at(0) = phytomer_parameters.
peduncle.color;
2360 vec3 inflorescence_bending_axis_actual = inflorescence_bending_axis;
2363 if (phytomer_parameters.
peduncle.pitch.val() != 0.f || fbud.base_rotation.pitch != 0.f) {
2364 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, inflorescence_bending_axis_actual,
deg2rad(phytomer_parameters.
peduncle.pitch.val()) + fbud.base_rotation.pitch);
2369 vec3 parent_petiole_base_axis;
2372 parent_petiole_base_axis = internode_axis;
2376 float parent_petiole_azimuth = -std::atan2(parent_petiole_base_axis.
y, parent_petiole_base_axis.
x);
2377 float current_peduncle_azimuth = -std::atan2(peduncle_axis.
y, peduncle_axis.
x);
2378 float azimuthal_rotation = current_peduncle_azimuth - parent_petiole_azimuth;
2379 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, internode_axis, azimuthal_rotation);
2381 inflorescence_bending_axis_actual =
rotatePointAboutLine(inflorescence_bending_axis_actual, nullorigin, internode_axis, azimuthal_rotation);
2384 float theta_base = fabs(
cart2sphere(peduncle_axis).zenith);
2387 vec3 collision_optimal_peduncle_direction;
2388 bool peduncle_collision_active =
false;
2390 if (plantarchitecture_ptr->fruit_collision_detection_enabled) {
2391 collision_optimal_peduncle_direction = calculateFruitCollisionAvoidanceDirection(fbud.base_position, peduncle_axis, peduncle_collision_active);
2394 if (peduncle_collision_active) {
2395 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
2396 vec3 natural_peduncle_direction = peduncle_axis;
2401 peduncle_axis = inertia_weight * natural_peduncle_direction + (1.0f - inertia_weight) * collision_optimal_peduncle_direction;
2406 float peduncle_curvature = phytomer_parameters.
peduncle.curvature.val();
2407 phytomer_parameters.
peduncle.curvature.resample();
2410 uint petiole_idx = fbud.parent_index;
2411 uint bud_idx = fbud.bud_index;
2412 if (petiole_idx < this->peduncle_length.size()) {
2413 if (this->peduncle_length.at(petiole_idx).size() <= bud_idx) {
2414 this->peduncle_length.at(petiole_idx).resize(bud_idx + 1);
2415 this->peduncle_radius.at(petiole_idx).resize(bud_idx + 1);
2416 this->peduncle_pitch.at(petiole_idx).resize(bud_idx + 1);
2417 this->peduncle_curvature.at(petiole_idx).resize(bud_idx + 1);
2419 this->peduncle_length.at(petiole_idx).at(bud_idx) = peduncle_length;
2420 this->peduncle_radius.at(petiole_idx).at(bud_idx) = phytomer_parameters.
peduncle.radius.val();
2421 this->peduncle_pitch.at(petiole_idx).at(bud_idx) = phytomer_parameters.
peduncle.pitch.val();
2422 this->peduncle_curvature.at(petiole_idx).at(bud_idx) = peduncle_curvature;
2425 for (
int i = 1; i <= phytomer_parameters.
peduncle.length_segments; i++) {
2426 if (peduncle_curvature != 0.f) {
2427 float curvature_value = peduncle_curvature;
2432 float axis_magnitude = horizontal_bending_axis.
magnitude();
2435 if (axis_magnitude > 0.001f) {
2436 horizontal_bending_axis = horizontal_bending_axis / axis_magnitude;
2439 float theta_curvature =
deg2rad(curvature_value * dr_peduncle);
2440 float theta_from_target;
2442 if (curvature_value > 0) {
2445 theta_from_target = std::acos(std::min(1.0f, std::max(-1.0f, peduncle_axis.
z)));
2449 theta_from_target = std::acos(std::min(1.0f, std::max(-1.0f, -peduncle_axis.
z)));
2453 if (fabs(theta_curvature) >= theta_from_target) {
2455 if (curvature_value > 0) {
2462 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, horizontal_bending_axis, theta_curvature);
2467 if (curvature_value > 0) {
2475 peduncle_vertices.at(i) = peduncle_vertices.at(i - 1) + dr_peduncle * peduncle_axis;
2477 peduncle_radii.at(i) = phytomer_parameters.
peduncle.radius.val();
2478 peduncle_colors.at(i) = phytomer_parameters.
peduncle.color;
2481 if (build_context_geometry_peduncle) {
2482 fbud.peduncle_objIDs.push_back(context_ptr->
addTubeObject(Ndiv_peduncle_radius, peduncle_vertices, peduncle_radii, peduncle_colors));
2484 std::string peduncle_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot_ptr->shoot_type_label +
"_peduncle";
2485 renameAutoMaterial(context_ptr, fbud.peduncle_objIDs.back(), peduncle_material_name);
2492 if (petiole_idx < this->peduncle_vertices.size()) {
2493 if (this->peduncle_vertices.at(petiole_idx).size() <= fbud.bud_index) {
2494 this->peduncle_vertices.at(petiole_idx).resize(fbud.bud_index + 1);
2496 this->peduncle_vertices.at(petiole_idx).at(fbud.bud_index) = peduncle_vertices;
2500 if (petiole_idx < this->peduncle_radii.size()) {
2501 if (this->peduncle_radii.at(petiole_idx).size() <= fbud.bud_index) {
2502 this->peduncle_radii.at(petiole_idx).resize(fbud.bud_index + 1);
2504 this->peduncle_radii.at(petiole_idx).at(fbud.bud_index) = peduncle_radii;
2508 phytomer_parameters.
peduncle.length.resample();
2509 phytomer_parameters.
peduncle.radius.resample();
2510 phytomer_parameters.
peduncle.pitch.resample();
2513 plantarchitecture_ptr->ensureInflorescencePrototypesInitialized(phytomer_parameters, plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
2515 int flowers_per_peduncle = phytomer_parameters.
inflorescence.flowers_per_peduncle.val();
2516 float flower_offset_val = clampOffset(flowers_per_peduncle, phytomer_parameters.
inflorescence.flower_offset.val());
2517 for (
int fruit = 0; fruit < flowers_per_peduncle; fruit++) {
2520 if (fbud.state == BUD_FRUITING) {
2521 scale_factor = phytomer_parameters.
inflorescence.fruit_prototype_scale.val();
2522 phytomer_parameters.
inflorescence.fruit_prototype_scale.resample();
2524 scale_factor = phytomer_parameters.
inflorescence.flower_prototype_scale.val();
2525 phytomer_parameters.
inflorescence.flower_prototype_scale.resample();
2528 float ind_from_tip = fabs(fruit -
float(flowers_per_peduncle - 1) /
float(phytomer_parameters.
petiole.petioles_per_internode));
2531 vec3 fruit_base = peduncle_vertices.back();
2533 if (flowers_per_peduncle > 1 && flower_offset_val > 0) {
2534 if (ind_from_tip != 0) {
2535 float offset = (ind_from_tip - 0.5f) * flower_offset_val * phytomer_parameters.
peduncle.length.val();
2536 if (phytomer_parameters.
peduncle.length.val() > 0) {
2537 frac = 1.f - offset / phytomer_parameters.
peduncle.length.val();
2539 fruit_base = PlantArchitecture::interpolateTube(peduncle_vertices, frac);
2544 float compound_rotation = 0;
2545 if (flowers_per_peduncle > 1) {
2546 if (flower_offset_val == 0) {
2548 float dphi =
PI_F / (floor(0.5 *
float(flowers_per_peduncle - 1)) + 1);
2549 compound_rotation = -float(
PI_F) + dphi * (fruit + 0.5f);
2551 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);
2552 phytomer_parameters.
internode.phyllotactic_angle.resample();
2566 float pitch_inflorescence = -
asin_safe(peduncle_axis.
z) + applied_pitch_param;
2567 if (fbud.state == BUD_FRUITING) {
2569 pitch_inflorescence = pitch_inflorescence + phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.val() * (0.5f *
PI_F - pitch_inflorescence);
2571 phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.resample();
2574 float azimuth = -std::atan2(peduncle_axis.
y, peduncle_axis.
x);
2577 float yaw_compound =
deg2rad(phytomer_parameters.
peduncle.roll.val()) + compound_rotation;
2580 bool is_open_flower = (fbud.state == BUD_FLOWER_OPEN);
2583 createInflorescenceGeometry(fbud, fruit_base, peduncle_axis, pitch_inflorescence, applied_roll, azimuth, yaw_compound, scale_factor, is_open_flower);
2585 phytomer_parameters.
inflorescence.flowers_per_peduncle.resample();
2586 phytomer_parameters.
peduncle.roll.resample();
2588 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
2589 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"age", fbud.age);
2590 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"age", fbud.age);
2593 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
2594 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"rank", rank);
2595 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"rank", rank);
2598 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
2599 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
2600 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
2603 if (plantarchitecture_ptr->output_object_data.at(
"peduncleID")) {
2604 for (
uint objID: fbud.peduncle_objIDs) {
2605 context_ptr->
setObjectData(objID,
"peduncleID", (
int) objID);
2608 for (
uint objID: fbud.inflorescence_objIDs) {
2609 if (fbud.state == BUD_FLOWER_CLOSED && plantarchitecture_ptr->output_object_data.at(
"closedflowerID")) {
2610 context_ptr->
setObjectData(objID,
"closedflowerID", (
int) objID);
2611 }
else if (fbud.state == BUD_FLOWER_OPEN && plantarchitecture_ptr->output_object_data.at(
"openflowerID")) {
2613 context_ptr->
setObjectData(objID,
"openflowerID", (
int) objID);
2614 }
else if (plantarchitecture_ptr->output_object_data.at(
"fruitID")) {
2627 vec3 shift = base_position - old_base;
2630 for (
auto &vertex: petiole_vertice) {
2635 if (build_context_geometry_petiole) {
2640 for (
auto &petiole: leaf_bases) {
2641 for (
auto &leaf_base: petiole) {
2646 for (
auto &petiole_peduncles: peduncle_vertices) {
2647 for (
auto &bud_peduncle_vertices: petiole_peduncles) {
2648 for (
auto &vertex: bud_peduncle_vertices) {
2654 for (
auto &floral_bud: floral_buds) {
2655 for (
auto &fbud: floral_bud) {
2658 for (
auto &base: fbud.inflorescence_bases) {
2661 if (build_context_geometry_peduncle) {
2669 if (petiole_index >= leaf_objIDs.size()) {
2671 }
else if (leaf_index >= leaf_objIDs.at(petiole_index).size()) {
2680 vec3 pitch_axis = -1 *
cross(internode_axis, petiole_axis);
2682 int leaves_per_petiole = leaf_rotation.at(petiole_index).size();
2685 float compound_rotation = 0;
2686 if (leaves_per_petiole > 1 && leaf_index ==
float(leaves_per_petiole - 1) / 2.f) {
2690 compound_rotation = 0;
2691 }
else if (leaves_per_petiole > 1 && leaf_index <
float(leaves_per_petiole - 1) / 2.f) {
2693 yaw = -rotation.yaw;
2694 roll = -rotation.roll;
2695 compound_rotation = -0.5 *
PI_F;
2698 yaw = -rotation.yaw;
2699 roll = rotation.roll;
2700 compound_rotation = 0;
2706 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), roll, leaf_bases.at(petiole_index).at(leaf_index), roll_axis);
2707 leaf_rotation.at(petiole_index).at(leaf_index).roll += roll;
2711 if (rotation.pitch != 0) {
2713 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), rotation.pitch, leaf_bases.at(petiole_index).at(leaf_index), pitch_axis);
2714 leaf_rotation.at(petiole_index).at(leaf_index).pitch += rotation.pitch;
2719 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), yaw, leaf_bases.at(petiole_index).at(leaf_index), {0, 0, 1});
2720 leaf_rotation.at(petiole_index).at(leaf_index).yaw += yaw;
2726 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer::rotatePetiole): Invalid petiole index.");
2731 if (rotation.pitch == 0.f && rotation.yaw == 0.f && rotation.roll == 0.f) {
2738 auto applyRotation = [
this, petiole_index, &base](
float angle,
const vec3 &axis) {
2742 if (!petiole_objIDs.at(petiole_index).empty()) {
2743 context_ptr->
rotateObject(petiole_objIDs.at(petiole_index), angle, base, axis);
2745 if (petiole_index < leaf_objIDs.size() && !leaf_objIDs.at(petiole_index).empty()) {
2746 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index), angle, base, axis);
2751 if (petiole_index < leaf_bases.size()) {
2752 for (
auto &leaf_base: leaf_bases.at(petiole_index)) {
2756 if (petiole_index < petiole_axis_initial.size()) {
2767 if (rotation.pitch != 0.f && petiole_index < petiole_rotation_axis.size()) {
2768 vec3 pitch_axis = petiole_rotation_axis.at(petiole_index);
2771 applyRotation(std::abs(rotation.pitch), pitch_axis);
2772 petiole_pitch.at(petiole_index) += std::abs(rotation.pitch);
2777 if (rotation.yaw != 0.f) {
2778 vec3 yaw_axis = internode_axis;
2781 applyRotation(rotation.yaw, yaw_axis);
2786 if (rotation.roll != 0.f) {
2790 applyRotation(rotation.roll, roll_axis);
2796 assert(internode_scale_factor_fraction >= 0 && internode_scale_factor_fraction <= 1);
2798 if (internode_scale_factor_fraction == current_internode_scale_factor) {
2802 float delta_scale = internode_scale_factor_fraction / current_internode_scale_factor;
2804 current_internode_scale_factor = internode_scale_factor_fraction;
2807 int s_start = (p == 0) ? 1 : 0;
2809 for (
int s = s_start; s < parent_shoot_ptr->shoot_internode_vertices.at(p).size(); s++) {
2813 int s_minus = s - 1;
2816 s_minus =
static_cast<int>(parent_shoot_ptr->shoot_internode_vertices.at(p_minus).size() - 1);
2819 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));
2820 float current_length = central_axis.
magnitude();
2821 central_axis = central_axis / current_length;
2822 vec3 dL = central_axis * current_length * (delta_scale - 1);
2825 for (
int p_downstream = p; p_downstream < parent_shoot_ptr->shoot_internode_vertices.size(); p_downstream++) {
2826 int sd_start = (p_downstream == p) ? s : 0;
2827 for (
int s_downstream = sd_start; s_downstream < parent_shoot_ptr->shoot_internode_vertices.at(p_downstream).size(); s_downstream++) {
2828 parent_shoot_ptr->shoot_internode_vertices.at(p_downstream).at(s_downstream) += dL;
2837 this->internode_length_max *= scale_factor;
2839 current_internode_scale_factor = current_internode_scale_factor / scale_factor;
2841 if (current_internode_scale_factor >= 1.f) {
2843 current_internode_scale_factor = 1.f;
2848 float scale_factor = internode_length_max_new / this->internode_length_max;
2853 this->internode_radius_max = internode_radius_max_new;
2858 assert(leaf_scale_factor_fraction >= 0 && leaf_scale_factor_fraction <= 1);
2860 if (current_leaf_scale_factor.size() <= petiole_index) {
2861 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf scale factor.");
2865 if (leaf_scale_factor_fraction == current_leaf_scale_factor.at(petiole_index) || (leaf_objIDs.at(petiole_index).empty() && petiole_objIDs.at(petiole_index).empty())) {
2869 float delta_scale = leaf_scale_factor_fraction / current_leaf_scale_factor.at(petiole_index);
2871 petiole_length.at(petiole_index) *= delta_scale;
2873 current_leaf_scale_factor.at(petiole_index) = leaf_scale_factor_fraction;
2875 assert(leaf_objIDs.size() == leaf_bases.size());
2879 if (!petiole_objIDs.at(petiole_index).empty()) {
2882 for (
uint objID: petiole_objIDs.at(petiole_index)) {
2886 petiole_radii.at(petiole_index).at(node) *= delta_scale;
2897 }
else if (build_context_geometry_petiole) {
2900 for (
uint node = 0; node < petiole_radii.at(petiole_index).size(); node++) {
2901 petiole_radii.at(petiole_index).at(node) *= delta_scale;
2909 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
2910 petiole_objIDs.at(petiole_index) =
makeTubeFromCones(Ndiv_petiole_radius,
petiole_vertices.at(petiole_index), petiole_radii.at(petiole_index), petiole_colors, context_ptr);
2911 if (!petiole_objIDs.at(petiole_index).empty()) {
2913 std::string petiole_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot_ptr->shoot_type_label +
"_petiole";
2914 renameAutoMaterial(context_ptr, petiole_objIDs.at(petiole_index), petiole_material_name);
2919 assert(leaf_objIDs.at(petiole_index).size() == leaf_bases.at(petiole_index).size());
2920 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2921 float ind_from_tip = float(leaf) - float(leaf_objIDs.at(petiole_index).size() - 1) / 2.f;
2923 float leaflet_offset_val = clampOffset(
int(leaf_objIDs.at(petiole_index).size()), phytomer_parameters.
leaf.leaflet_offset.val());
2925 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), -1 * leaf_bases.at(petiole_index).at(leaf));
2926 context_ptr->
scaleObject(leaf_objIDs.at(petiole_index).at(leaf), delta_scale *
make_vec3(1, 1, 1));
2927 if (ind_from_tip == 0) {
2929 leaf_bases.at(petiole_index).at(leaf) =
petiole_vertices.at(petiole_index).back();
2931 float offset = (fabs(ind_from_tip) - 0.5f) * leaflet_offset_val * phytomer_parameters.
petiole.length.val();
2932 vec3 leaf_base = PlantArchitecture::interpolateTube(
petiole_vertices.at(petiole_index), 1.f - offset / phytomer_parameters.
petiole.length.val());
2933 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), leaf_base);
2934 leaf_bases.at(petiole_index).at(leaf) = leaf_base;
2940 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2946 if (leaf_objIDs.size() <= petiole_index) {
2947 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf prototype scale.");
2949 if (leaf_prototype_scale < 0.f) {
2950 leaf_prototype_scale = 0;
2953 float tip_ind = ceil(scast<float>(leaf_size_max.at(petiole_index).size() - 1) / 2.f);
2954 float scale_factor = leaf_prototype_scale / leaf_size_max.at(petiole_index).at(tip_ind);
2955 current_leaf_scale_factor.at(petiole_index) *= scale_factor;
2957 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2958 leaf_size_max.at(petiole_index).at(leaf) *= scale_factor;
2959 context_ptr->
scaleObjectAboutPoint(leaf_objIDs.at(petiole_index).at(leaf), scale_factor *
make_vec3(1, 1, 1), leaf_bases.at(petiole_index).at(leaf));
2963 this->petiole_curvature.at(petiole_index) /= scale_factor;
2965 if (current_leaf_scale_factor.at(petiole_index) >= 1.f) {
2967 current_leaf_scale_factor.at(petiole_index) = 1.f;
2972 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2978 if (leaf_objIDs.size() <= petiole_index) {
2979 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf prototype scale.");
2981 if (scale_factor < 0.f) {
2985 current_leaf_scale_factor.at(petiole_index) /= scale_factor;
2987 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2988 leaf_size_max.at(petiole_index).at(leaf) *= scale_factor;
2989 context_ptr->
scaleObjectAboutPoint(leaf_objIDs.at(petiole_index).at(leaf), scale_factor *
make_vec3(1, 1, 1), leaf_bases.at(petiole_index).at(leaf));
2993 this->petiole_curvature.at(petiole_index) /= scale_factor;
2995 if (current_leaf_scale_factor.at(petiole_index) >= 1.f) {
2997 current_leaf_scale_factor.at(petiole_index) = 1.f;
3002 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
3008 if (petiole_index >= petiole_length.size()) {
3009 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer::scalePetioleGeometry): Invalid petiole index " + std::to_string(petiole_index) +
".");
3011 if (target_length <= 0.f || target_base_radius <= 0.f) {
3012 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer::scalePetioleGeometry): Target length and radius must be positive.");
3016 float current_length = petiole_length.at(petiole_index);
3017 float current_base_radius = petiole_radii.at(petiole_index).at(0);
3019 if (current_length <= 0.f || current_base_radius <= 0.f) {
3021 petiole_length.at(petiole_index) = target_length;
3022 if (!petiole_radii.at(petiole_index).empty()) {
3023 petiole_radii.at(petiole_index).at(0) = target_base_radius;
3028 float length_scale = target_length / current_length;
3029 float radius_scale = target_base_radius / current_base_radius;
3037 petiole_vertices.at(petiole_index).at(j) = petiole_base + offset * length_scale;
3041 for (
size_t j = 0; j < petiole_radii.at(petiole_index).size(); j++) {
3042 petiole_radii.at(petiole_index).at(j) *= radius_scale;
3046 petiole_length.at(petiole_index) = target_length;
3049 if (!petiole_objIDs.at(petiole_index).empty()) {
3051 context_ptr->
deleteObject(petiole_objIDs.at(petiole_index));
3054 std::vector<RGBcolor> petiole_colors(petiole_radii.at(petiole_index).size(), phytomer_parameters.
petiole.color);
3055 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
3057 petiole_objIDs.at(petiole_index) =
makeTubeFromCones(Ndiv_petiole_radius,
petiole_vertices.at(petiole_index), petiole_radii.at(petiole_index), petiole_colors, context_ptr);
3060 if (!petiole_objIDs.at(petiole_index).empty()) {
3062 std::string petiole_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot_ptr->shoot_type_label +
"_petiole";
3063 renameAutoMaterial(context_ptr, petiole_objIDs.at(petiole_index), petiole_material_name);
3068 if (petiole_index < leaf_bases.size()) {
3069 for (
size_t leaf = 0; leaf < leaf_bases.at(petiole_index).size(); leaf++) {
3070 vec3 offset = leaf_bases.at(petiole_index).at(leaf) - petiole_base;
3071 leaf_bases.at(petiole_index).at(leaf) = petiole_base + offset * length_scale;
3074 if (petiole_index < leaf_objIDs.size() && leaf < leaf_objIDs.at(petiole_index).size()) {
3075 vec3 translation = offset * length_scale - offset;
3076 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), translation);
3082 if (petiole_index < floral_buds.size()) {
3083 for (
auto &fbud: floral_buds.at(petiole_index)) {
3084 vec3 offset = fbud.base_position - petiole_base;
3085 vec3 translation = offset * length_scale - offset;
3086 fbud.base_position = petiole_base + offset * length_scale;
3089 for (
size_t i = 0; i < fbud.inflorescence_bases.size(); i++) {
3090 fbud.inflorescence_bases.at(i) += translation;
3094 for (
size_t i = 0; i < fbud.inflorescence_objIDs.size(); i++) {
3095 context_ptr->
translateObject(fbud.inflorescence_objIDs.at(i), translation);
3097 for (
size_t i = 0; i < fbud.peduncle_objIDs.size(); i++) {
3098 context_ptr->
translateObject(fbud.peduncle_objIDs.at(i), translation);
3105 assert(inflorescence_scale_factor_fraction >= 0 && inflorescence_scale_factor_fraction <= 1);
3107 if (inflorescence_scale_factor_fraction == fbud.current_fruit_scale_factor) {
3111 float delta_scale = inflorescence_scale_factor_fraction / fbud.current_fruit_scale_factor;
3113 fbud.current_fruit_scale_factor = inflorescence_scale_factor_fraction;
3116 for (
int inflorescence = 0; inflorescence < fbud.inflorescence_objIDs.size(); inflorescence++) {
3117 context_ptr->
scaleObjectAboutPoint(fbud.inflorescence_objIDs.at(inflorescence), delta_scale *
make_vec3(1, 1, 1), fbud.inflorescence_bases.at(inflorescence));
3124 this->petiole_radii.resize(0);
3126 this->petiole_colors.resize(0);
3127 this->petiole_length.resize(0);
3128 this->leaf_size_max.resize(0);
3129 this->leaf_rotation.resize(0);
3130 this->leaf_bases.resize(0);
3133 leaf_objIDs.clear();
3136 if (build_context_geometry_petiole) {
3138 petiole_objIDs.resize(0);
3144 if (context_ptr->
doesObjectExist(parent_shoot_ptr->internode_tube_objID)) {
3147 uint tube_prune_index;
3149 tube_prune_index = 0;
3151 tube_prune_index = this->
shoot_index.
x * tube_segments + 1;
3153 if (tube_prune_index < tube_nodes) {
3154 context_ptr->
pruneTubeNodes(parent_shoot_ptr->internode_tube_objID, tube_prune_index);
3160 auto &phytomer = parent_shoot_ptr->phytomers.at(node);
3163 phytomer->removeLeaf();
3166 for (
auto &petiole: phytomer->floral_buds) {
3167 for (
auto &fbud: petiole) {
3168 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
3169 uint objID = fbud.inflorescence_objIDs.at(p);
3171 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
3172 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
3174 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
3177 fbud.peduncle_objIDs.clear();
3178 fbud.inflorescence_objIDs.clear();
3179 fbud.inflorescence_bases.clear();
3186 if (parent_shoot_ptr->childIDs.find(node) != parent_shoot_ptr->childIDs.end()) {
3187 for (
auto childID: parent_shoot_ptr->childIDs.at(node)) {
3188 auto child_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(childID);
3189 if (!child_shoot->phytomers.empty()) {
3190 child_shoot->phytomers.front()->deletePhytomer();
3197 parent_shoot_ptr->shoot_internode_radii.resize(this->
shoot_index.
x);
3198 parent_shoot_ptr->shoot_internode_vertices.resize(this->
shoot_index.
x);
3199 parent_shoot_ptr->phytomers.resize(this->
shoot_index.
x);
3202 for (
const auto &phytomer: parent_shoot_ptr->phytomers) {
3203 phytomer->shoot_index.y = scast<int>(parent_shoot_ptr->phytomers.size());
3205 parent_shoot_ptr->current_node_number = scast<int>(parent_shoot_ptr->phytomers.size());
3209 return (!leaf_bases.empty() && !leaf_bases.front().empty());
3218 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),
3219 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) {
3220 sugar_pool_molC = 0;
3221 phyllochron_counter = 0;
3224 context_ptr = plant_architecture_ptr->context_ptr;
3228 if (parent_shoot_ID >= 0) {
3229 plant_architecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->childIDs[(int) parent_node_index].push_back(shoot_ID);
3233void Shoot::buildShootPhytomers(
float internode_radius,
float internode_length,
float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper) {
3234 for (
int i = 0; i < current_node_number; i++) {
3238 if (current_node_number > 1) {
3239 taper = 1.f - radius_taper * float(i) / float(current_node_number - 1);
3243 appendPhytomer(internode_radius * taper, internode_length, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, shoot_parameters.
phytomer_parameters);
3248 auto shoot_ptr =
this;
3250 assert(shoot_ptr->shoot_parameters.child_shoot_type_labels.size() == shoot_ptr->shoot_parameters.child_shoot_type_probabilities.size());
3252 std::string child_shoot_type_label;
3254 if (shoot_ptr->shoot_parameters.child_shoot_type_labels.empty()) {
3256 child_shoot_type_label = shoot_ptr->shoot_type_label;
3257 }
else if (shoot_ptr->shoot_parameters.child_shoot_type_labels.size() == 1) {
3259 child_shoot_type_label = shoot_ptr->shoot_parameters.child_shoot_type_labels.at(0);
3261 float randf = context_ptr->
randu();
3262 int shoot_type_index = -1;
3263 float cumulative_probability = 0;
3264 for (
int s = 0; s < shoot_ptr->shoot_parameters.child_shoot_type_labels.size(); s++) {
3265 cumulative_probability += shoot_ptr->shoot_parameters.child_shoot_type_probabilities.at(s);
3266 if (randf < cumulative_probability) {
3267 shoot_type_index = s;
3271 if (shoot_type_index < 0) {
3272 shoot_type_index = shoot_ptr->shoot_parameters.child_shoot_type_labels.size() - 1;
3274 child_shoot_type_label = shoot_ptr->shoot_type_label;
3275 if (shoot_type_index >= 0) {
3276 child_shoot_type_label = shoot_ptr->shoot_parameters.child_shoot_type_labels.at(shoot_type_index);
3280 return child_shoot_type_label;
3284 if (node_index >= phytomers.size()) {
3285 helios_runtime_error(
"ERROR (PlantArchitecture::sampleVegetativeBudBreak): Invalid node index. Node index must be less than the number of phytomers on the shoot.");
3288 float probability_min = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_min.val();
3289 float probability_max = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_max.val();
3290 float probability_decay = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_decay_rate.val();
3292 float bud_break_probability;
3294 bud_break_probability = probability_min;
3295 }
else if (probability_decay > 0) {
3297 bud_break_probability = std::fmax(probability_min, probability_max - probability_decay *
float(this->current_node_number - node_index - 1));
3298 }
else if (probability_decay < 0) {
3300 bud_break_probability = std::fmax(probability_min, probability_max - fabs(probability_decay) *
float(node_index));
3302 if (probability_decay == 0.f) {
3303 bud_break_probability = probability_min;
3305 bud_break_probability = probability_max;
3309 bool bud_break =
true;
3310 if (context_ptr->
randu() > bud_break_probability) {
3318 std::string epicormic_shoot_label = plantarchitecture_ptr->plant_instances.at(this->plantID).epicormic_shoot_probability_perlength_per_day.first;
3320 if (epicormic_shoot_label.empty()) {
3324 float epicormic_probability = plantarchitecture_ptr->plant_instances.at(this->plantID).epicormic_shoot_probability_perlength_per_day.second;
3326 if (epicormic_probability == 0) {
3332 epicormic_positions_fraction.clear();
3338 float dta = std::min(time, 1.f);
3340 float shoot_fraction = context_ptr->
randu();
3344 bool new_shoot =
uint((epicormic_probability * shoot_length * dta * elevation > context_ptr->
randu()));
3346 Nshoots +=
uint(new_shoot);
3349 epicormic_positions_fraction.push_back(shoot_fraction);
3355 assert(epicormic_positions_fraction.size() == Nshoots);
3361 float radius_taper,
const std::string &shoot_type_label) {
3362 if (plant_instances.find(plantID) == plant_instances.end()) {
3363 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3364 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3365 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3368 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3370 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3371 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3373 if (current_node_number > shoot_parameters.max_nodes.val()) {
3374 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()) +
".");
3377 uint shootID = shoot_tree_ptr->size();
3378 vec3 base_position = plant_instances.at(plantID).base_position;
3381 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));
3382 shoot_tree_ptr->emplace_back(shoot_new);
3385 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3391 float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label) {
3392 if (plant_instances.find(plantID) == plant_instances.end()) {
3393 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3394 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3395 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3398 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3400 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3401 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3403 if (shoot_tree_ptr->empty()) {
3404 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Cannot append shoot to empty shoot. You must call addBaseStemShoot() first for each plant.");
3405 }
else if (parent_shoot_ID >=
int(shoot_tree_ptr->size())) {
3406 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Parent with ID of " + std::to_string(parent_shoot_ID) +
" does not exist.");
3407 }
else if (current_node_number > shoot_parameters.max_nodes.val()) {
3408 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()) +
".");
3409 }
else if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.empty()) {
3413 shoot_tree_ptr->at(parent_shoot_ID)->shoot_parameters.max_nodes = shoot_tree_ptr->at(parent_shoot_ID)->current_node_number;
3414 shoot_tree_ptr->at(parent_shoot_ID)->terminateApicalBud();
3417 int appended_shootID = int(shoot_tree_ptr->size());
3418 uint parent_node = shoot_tree_ptr->at(parent_shoot_ID)->current_node_number - 1;
3419 uint rank = shoot_tree_ptr->at(parent_shoot_ID)->rank;
3420 vec3 base_position = interpolateTube(shoot_tree_ptr->at(parent_shoot_ID)->phytomers.back()->getInternodeNodePositions(), 0.9f);
3423 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));
3424 shoot_tree_ptr->emplace_back(shoot_new);
3427 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3429 return appended_shootID;
3433 float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label,
uint petiole_index) {
3434 if (plant_instances.find(plantID) == plant_instances.end()) {
3435 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3436 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3437 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3440 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3442 if (parent_shoot_ID <= -1 || parent_shoot_ID >= shoot_tree_ptr->size()) {
3443 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Parent with ID of " + std::to_string(parent_shoot_ID) +
" does not exist.");
3444 }
else if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.size() <= parent_node_index) {
3445 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Parent shoot does not have a node " + std::to_string(parent_node_index) +
".");
3449 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3450 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3451 uint parent_rank = (int) shoot_tree_ptr->at(parent_shoot_ID)->rank;
3452 int childID = int(shoot_tree_ptr->size());
3455 const auto parent_shoot_ptr = shoot_tree_ptr->at(parent_shoot_ID);
3457 vec3 shoot_base_position = parent_shoot_ptr->shoot_internode_vertices.at(parent_node_index).back();
3461 if (parent_shoot_ptr->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
3463 axis_vector = parent_shoot_ptr->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
3465 axis_vector = parent_shoot_ptr->phytomers.at(parent_node_index)->getPetioleAxisVector(0, petiole_index);
3467 shoot_base_position += 0.9f * axis_vector * parent_shoot_ptr->phytomers.at(parent_node_index)->getInternodeRadius(1.f);
3470 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));
3471 shoot_tree_ptr->emplace_back(shoot_new);
3474 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3480 float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label) {
3481 if (plant_instances.find(plantID) == plant_instances.end()) {
3482 helios_runtime_error(
"ERROR (PlantArchitecture::addEpicormicShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3483 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3484 helios_runtime_error(
"ERROR (PlantArchitecture::addEpicormicShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3487 auto &parent_shoot = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
3489 uint parent_node_index = 0;
3490 if (parent_position_fraction > 0) {
3491 parent_node_index = std::ceil(parent_position_fraction *
float(parent_shoot->phytomers.size())) - 1;
3495 if (plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
3497 axis_vector = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
3499 axis_vector = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->getPetioleAxisVector(0, 0);
3505 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);
3508void PlantArchitecture::validateShootTypes(
ShootParameters &shoot_parameters,
const std::map<std::string, ShootParameters> &shoot_types_ref)
const {
3509 assert(shoot_parameters.child_shoot_type_probabilities.size() == shoot_parameters.child_shoot_type_labels.size());
3511 for (
int ind = shoot_parameters.child_shoot_type_labels.size() - 1; ind >= 0; ind--) {
3512 if (shoot_types_ref.find(shoot_parameters.child_shoot_type_labels.at(ind)) == shoot_types_ref.end()) {
3513 shoot_parameters.child_shoot_type_labels.erase(shoot_parameters.child_shoot_type_labels.begin() + ind);
3514 shoot_parameters.child_shoot_type_probabilities.erase(shoot_parameters.child_shoot_type_probabilities.begin() + ind);
3520 float leaf_scale_factor_fraction) {
3521 if (plant_instances.find(plantID) == plant_instances.end()) {
3522 helios_runtime_error(
"ERROR (PlantArchitecture::appendPhytomerToShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3525 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3527 if (shootID >= shoot_tree_ptr->size()) {
3528 helios_runtime_error(
"ERROR (PlantArchitecture::appendPhytomerToShoot): Parent with ID of " + std::to_string(shootID) +
" does not exist.");
3531 auto current_shoot_ptr = plant_instances.at(plantID).shoot_tree.at(shootID);
3533 int pID = current_shoot_ptr->appendPhytomer(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, phytomer_parameters);
3535 current_shoot_ptr->current_node_number++;
3536 current_shoot_ptr->nodes_this_season++;
3538 for (
auto &phytomers: current_shoot_ptr->phytomers) {
3539 phytomers->shoot_index.y = current_shoot_ptr->current_node_number;
3543 if (current_shoot_ptr->current_node_number == current_shoot_ptr->shoot_parameters.max_nodes.val()) {
3544 if (!current_shoot_ptr->shoot_parameters.flowers_require_dormancy && current_shoot_ptr->shoot_parameters.max_terminal_floral_buds.val() > 0) {
3545 current_shoot_ptr->addTerminalFloralBud();
3547 if (current_shoot_ptr->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function !=
nullptr) {
3548 state = BUD_FLOWER_CLOSED;
3549 }
else if (current_shoot_ptr->shoot_parameters.phytomer_parameters.inflorescence.fruit_prototype_function !=
nullptr) {
3550 state = BUD_FRUITING;
3554 for (
auto &fbuds: current_shoot_ptr->phytomers.back()->floral_buds) {
3555 for (
auto &fbud: fbuds) {
3556 if (fbud.isterminal) {
3558 current_shoot_ptr->phytomers.back()->updateInflorescence(fbud);
3559 if (state == BUD_FRUITING) {
3564 current_shoot_ptr->phytomers.back()->setInflorescenceScaleFraction(fbud, 0.25f);
3573 else if (current_shoot_ptr->nodes_this_season >= current_shoot_ptr->shoot_parameters.max_nodes_per_season.val()) {
3574 if (!current_shoot_ptr->shoot_parameters.flowers_require_dormancy && current_shoot_ptr->shoot_parameters.max_terminal_floral_buds.val() > 0) {
3575 current_shoot_ptr->addTerminalFloralBud();
3576 for (
auto &fbuds: current_shoot_ptr->phytomers.back()->floral_buds) {
3577 for (
auto &fbud: fbuds) {
3578 if (fbud.isterminal) {
3579 fbud.state = BUD_DORMANT;
3580 current_shoot_ptr->phytomers.back()->updateInflorescence(fbud);
3585 current_shoot_ptr->phytomers.at(pID)->isdormant =
true;
3592 if (plant_instances.find(plantID) == plant_instances.end()) {
3593 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3594 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(epicormic_shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3595 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Shoot type with label of " + epicormic_shoot_type_label +
" does not exist.");
3596 }
else if (epicormic_probability_perlength_perday < 0) {
3597 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Epicormic probability must be greater than or equal to zero.");
3600 plant_instances.at(plantID).epicormic_shoot_probability_perlength_per_day = std::make_pair(epicormic_shoot_type_label, epicormic_probability_perlength_perday);
3604 build_context_geometry_internode =
false;
3608 build_context_geometry_petiole =
false;
3612 build_context_geometry_peduncle =
false;
3616 ground_clipping_height = ground_height;
3619void PlantArchitecture::incrementPhytomerInternodeGirth(
uint plantID,
uint shootID,
uint node_number,
float dt,
bool update_context_geometry) {
3620 if (plant_instances.find(plantID) == plant_instances.end()) {
3621 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3624 auto shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3626 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3627 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3628 }
else if (node_number >= shoot->current_node_number) {
3629 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.");
3632 auto phytomer = shoot->phytomers.at(node_number);
3634 float leaf_area = phytomer->downstream_leaf_area;
3637 context_ptr->
setObjectData(shoot->internode_tube_objID,
"leaf_area", leaf_area);
3639 float phytomer_age = phytomer->age;
3640 float girth_area_factor = shoot->shoot_parameters.girth_area_factor.val();
3641 if (phytomer_age > 365) {
3642 girth_area_factor = shoot->shoot_parameters.girth_area_factor.val() * 365 / phytomer_age;
3645 float internode_area = girth_area_factor * leaf_area * 1e-4;
3646 float phytomer_radius = sqrtf(internode_area /
PI_F);
3648 auto &segment = shoot->shoot_internode_radii.at(node_number);
3649 for (
float &radius: segment) {
3650 if (phytomer_radius > radius) {
3652 radius = radius + 0.5 * (phytomer_radius - radius);
3656 if (update_context_geometry && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
3657 context_ptr->
setTubeRadii(shoot->internode_tube_objID,
flatten(shoot->shoot_internode_radii));
3661void PlantArchitecture::pruneGroundCollisions(
uint plantID) {
3662 if (plant_instances.find(plantID) == plant_instances.end()) {
3663 helios_runtime_error(
"ERROR (PlantArchitecture::pruneGroundCollisions): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3666 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3667 for (
auto &phytomer: shoot->phytomers) {
3669 if ((phytomer->shoot_index.x == 0 && phytomer->rank > 0) && context_ptr->
doesObjectExist(shoot->internode_tube_objID) && detectGroundCollision(shoot->internode_tube_objID)) {
3670 context_ptr->
deleteObject(shoot->internode_tube_objID);
3671 shoot->terminateApicalBud();
3675 for (
uint petiole = 0; petiole < phytomer->leaf_objIDs.size(); petiole++) {
3676 if (detectGroundCollision(phytomer->leaf_objIDs.at(petiole))) {
3677 phytomer->removeLeaf();
3682 for (
auto &petiole: phytomer->floral_buds) {
3683 for (
auto &fbud: petiole) {
3684 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
3685 uint objID = fbud.inflorescence_objIDs.at(p);
3686 if (detectGroundCollision(objID)) {
3688 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
3689 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
3692 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
3693 uint objID = fbud.peduncle_objIDs.at(p);
3694 if (detectGroundCollision(objID)) {
3697 fbud.peduncle_objIDs.clear();
3698 fbud.inflorescence_objIDs.clear();
3699 fbud.inflorescence_bases.clear();
3724 if (plant_instances.find(plantID) == plant_instances.end()) {
3725 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3728 auto parent_shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3730 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3731 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3732 }
else if (node_number >= parent_shoot->current_node_number) {
3733 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.");
3735 if (leaf_scale_factor_fraction < 0 || leaf_scale_factor_fraction > 1) {
3739 parent_shoot->phytomers.at(node_number)->setLeafScaleFraction(leaf_scale_factor_fraction);
3743 if (plant_instances.find(plantID) == plant_instances.end()) {
3744 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3747 plant_instances.at(plantID).base_position = base_position;
3750 if (!plant_instances.at(plantID).shoot_tree.empty()) {
3755 if (Beta_mu_inclination <= 0.f) {
3756 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3757 }
else if (Beta_nu_inclination <= 0.f) {
3758 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3761 setPlantLeafAngleDistribution_private({plantID}, Beta_mu_inclination, Beta_nu_inclination, 0.f, 0.f,
true,
false);
3765 if (Beta_mu_inclination <= 0.f) {
3766 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3767 }
else if (Beta_nu_inclination <= 0.f) {
3768 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3771 setPlantLeafAngleDistribution_private(plantIDs, Beta_mu_inclination, Beta_nu_inclination, 0.f, 0.f,
true,
false);
3775 if (eccentricity < 0.f || eccentricity > 1.f) {
3776 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAzimuthAngleDistribution): Eccentricity must be between 0 and 1.");
3779 setPlantLeafAngleDistribution_private({plantID}, 0.f, 0.f, eccentricity, ellipse_rotation_degrees,
false,
true);
3783 if (eccentricity < 0.f || eccentricity > 1.f) {
3784 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAzimuthAngleDistribution): Eccentricity must be between 0 and 1.");
3787 setPlantLeafAngleDistribution_private(plantIDs, 0.f, 0.f, eccentricity, ellipse_rotation_degrees,
false,
true);
3791 if (Beta_mu_inclination <= 0.f) {
3792 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3793 }
else if (Beta_nu_inclination <= 0.f) {
3794 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3795 }
else if (eccentricity < 0.f || eccentricity > 1.f) {
3796 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Eccentricity must be between 0 and 1.");
3799 setPlantLeafAngleDistribution_private({plantID}, Beta_mu_inclination, Beta_nu_inclination, eccentricity, ellipse_rotation_degrees,
true,
true);
3803 if (Beta_mu_inclination <= 0.f) {
3804 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3805 }
else if (Beta_nu_inclination <= 0.f) {
3806 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3807 }
else if (eccentricity < 0.f || eccentricity > 1.f) {
3808 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Eccentricity must be between 0 and 1.");
3811 setPlantLeafAngleDistribution_private(plantIDs, Beta_mu_inclination, Beta_nu_inclination, eccentricity, ellipse_rotation_degrees,
true,
true);
3816 if (plant_instances.find(plantID) == plant_instances.end()) {
3817 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3818 }
else if (plant_instances.at(plantID).shoot_tree.empty()) {
3819 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" has no shoots, so could not get a base position.");
3821 return plant_instances.at(plantID).base_position;
3825 std::vector<vec3> positions;
3826 positions.reserve(plantIDs.size());
3827 for (
uint plantID: plantIDs) {
3834 if (plant_instances.find(plantID) == plant_instances.end()) {
3835 helios_runtime_error(
"ERROR (PlantArchitecture::sumPlantLeafArea): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3841 for (
uint objID: leaf_objIDs) {
3849 if (plant_instances.find(plantID) == plant_instances.end()) {
3850 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantStemHeight): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3853 auto base_shoot_ptr = plant_instances.at(plantID).shoot_tree.front();
3855 std::vector<uint> stem_objID{base_shoot_ptr->internode_tube_objID};
3858 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantStemHeight): The plant does not contain any geometry.");
3862 if (base_shoot_ptr->childIDs.find(base_shoot_ptr->current_node_number - 1) != base_shoot_ptr->childIDs.end()) {
3863 auto terminal_children = base_shoot_ptr->childIDs.at(base_shoot_ptr->current_node_number - 1);
3864 for (
uint childID: terminal_children) {
3865 auto child_shoot_ptr = plant_instances.at(plantID).shoot_tree.at(childID);
3866 if (child_shoot_ptr->rank == base_shoot_ptr->rank) {
3867 if (context_ptr->
doesObjectExist(child_shoot_ptr->internode_tube_objID)) {
3868 stem_objID.push_back(child_shoot_ptr->internode_tube_objID);
3878 return max_box.
z - min_box.
z;
3883 if (plant_instances.find(plantID) == plant_instances.end()) {
3884 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantHeight): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3891 return max_box.
z - min_box.
z;
3895 if (plant_instances.find(plantID) == plant_instances.end()) {
3896 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafInclinationAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3902 std::vector<float> leaf_inclination_angles(Nbins, 0.f);
3903 const float dtheta = 0.5f *
PI_F / float(Nbins);
3904 for (
const uint UUID: leaf_UUIDs) {
3906 const float theta =
acos_safe(fabs(normal.z));
3908 uint bin =
static_cast<uint>(std::floor(theta / dtheta));
3912 if (!std::isnan(area)) {
3913 leaf_inclination_angles.at(bin) += area;
3920 for (
float &angle: leaf_inclination_angles) {
3926 return leaf_inclination_angles;
3930 std::vector<float> leaf_inclination_angles(Nbins, 0.f);
3931 for (
const uint plantID: plantIDs) {
3938 for (
float &angle: leaf_inclination_angles) {
3944 return leaf_inclination_angles;
3948 if (plant_instances.find(plantID) == plant_instances.end()) {
3949 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafAzimuthAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3955 std::vector<float> leaf_azimuth_angles(Nbins, 0.f);
3956 const float dtheta = 2.f *
PI_F /
static_cast<float>(Nbins);
3957 for (
const uint UUID: leaf_UUIDs) {
3961 uint bin =
static_cast<uint>(std::floor(phi / dtheta));
3965 if (!std::isnan(area)) {
3966 leaf_azimuth_angles.at(bin) += area;
3973 for (
float &angle: leaf_azimuth_angles) {
3979 return leaf_azimuth_angles;
3983 std::vector<float> leaf_azimuth_angles(Nbins, 0.f);
3984 for (
const uint plantID: plantIDs) {
3991 for (
float &angle: leaf_azimuth_angles) {
3997 return leaf_azimuth_angles;
4002 if (plant_instances.find(plantID) == plant_instances.end()) {
4003 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafCount): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4010 if (plant_instances.find(plantID) == plant_instances.end()) {
4011 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafBases): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4014 std::vector<vec3> leaf_bases;
4017 size_t total_size = 0;
4018 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
4019 for (
const auto &phytomer: shoot->phytomers) {
4020 total_size += phytomer->leaf_bases.size() * phytomer->leaf_bases.front().size();
4023 leaf_bases.reserve(total_size);
4026 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
4027 for (
const auto &phytomer: shoot->phytomers) {
4028 std::vector<vec3> bases_flat =
flatten(phytomer->leaf_bases);
4029 leaf_bases.insert(leaf_bases.end(), bases_flat.begin(), bases_flat.end());
4037 std::vector<helios::vec3> leaf_bases;
4038 for (
const uint plantID: plantIDs) {
4040 leaf_bases.insert(leaf_bases.end(), bases.begin(), bases.end());
4046 if (plant_instances.find(plantID) == plant_instances.end()) {
4047 helios_runtime_error(
"ERROR (PlantArchitecture::isPlantDormant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4050 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
4051 if (!shoot->isdormant) {
4060 if (plant_instances.find(plantID) == plant_instances.end()) {
4061 helios_runtime_error(
"ERROR (PlantArchitecture::determinePhenologyStage): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4072 if (!flowers.empty() || !fruits.empty()) {
4073 return "reproductive";
4077 const auto &plant_instance = plant_instances.at(plantID);
4078 if (plant_instance.dd_to_dormancy > 0) {
4079 float senescence_threshold = plant_instance.dd_to_dormancy_break + plant_instance.dd_to_dormancy * 0.9f;
4080 if (plant_instance.time_since_dormancy > senescence_threshold) {
4086 return "vegetative";
4090 if (plant_instances.find(plantID) == plant_instances.end()) {
4091 helios_runtime_error(
"ERROR (PlantArchitecture::writePlantMeshVertices): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4097 file.open(filename);
4099 if (!file.is_open()) {
4100 helios_runtime_error(
"ERROR (PlantArchitecture::writePlantMeshVertices): Could not open file " + filename +
" for writing.");
4103 for (
uint UUID: plant_UUIDs) {
4105 for (
vec3 &v: vertex) {
4106 file << v.x <<
" " << v.y <<
" " << v.z << std::endl;
4119 if (plant_instances.find(plantID) == plant_instances.end()) {
4120 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantName): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4122 return plant_instances.at(plantID).plant_name;
4126 if (plant_instances.find(plantID) == plant_instances.end()) {
4127 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAge): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4128 }
else if (plant_instances.at(plantID).shoot_tree.empty()) {
4129 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAge): Plant with ID of " + std::to_string(plantID) +
" has no shoots, so could not get a base position.");
4131 return plant_instances.at(plantID).current_age;
4136 if (plant_instances.find(plantID) == plant_instances.end()) {
4137 helios_runtime_error(
"ERROR (PlantArchitecture::listShootTypeLabels): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4141 const auto &shoot_types_snap = plant_instances.at(plantID).shoot_types_snapshot;
4144 std::vector<std::string> labels;
4145 labels.reserve(shoot_types_snap.size());
4146 for (
const auto &pair: shoot_types_snap) {
4147 labels.push_back(pair.first);
4154 if (plant_instances.find(plantID) == plant_instances.end()) {
4155 helios_runtime_error(
"ERROR (PlantArchitecture::harvestPlant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4158 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4159 for (
auto &phytomer: shoot->phytomers) {
4160 for (
auto &petiole: phytomer->floral_buds) {
4161 for (
auto &fbud: petiole) {
4162 if (fbud.state != BUD_DORMANT) {
4163 phytomer->setFloralBudState(BUD_DEAD, fbud);
4172 if (plant_instances.find(plantID) == plant_instances.end()) {
4173 helios_runtime_error(
"ERROR (PlantArchitecture::removePlantLeaves): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4176 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4177 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootLeaves): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
4180 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4182 for (
auto &phytomer: shoot->phytomers) {
4183 phytomer->removeLeaf();
4188 if (plant_instances.find(plantID) == plant_instances.end()) {
4189 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootVegetativeBuds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4192 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4193 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootVegetativeBuds): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
4196 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4198 for (
auto &phytomer: shoot->phytomers) {
4199 phytomer->setVegetativeBudState(BUD_DEAD);
4204 if (plant_instances.find(plantID) == plant_instances.end()) {
4205 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootFloralBuds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4208 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4209 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootFloralBuds): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
4212 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4214 for (
auto &phytomer: shoot->phytomers) {
4215 phytomer->setFloralBudState(BUD_DEAD);
4220 if (plant_instances.find(plantID) == plant_instances.end()) {
4221 helios_runtime_error(
"ERROR (PlantArchitecture::removePlantLeaves): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4224 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4225 for (
auto &phytomer: shoot->phytomers) {
4226 phytomer->removeLeaf();
4232 if (plant_instances.find(plantID) == plant_instances.end()) {
4233 helios_runtime_error(
"ERROR (PlantArchitecture::makePlantDormant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4236 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4237 shoot->makeDormant();
4239 plant_instances.at(plantID).time_since_dormancy = 0;
4243 if (plant_instances.find(plantID) == plant_instances.end()) {
4244 helios_runtime_error(
"ERROR (PlantArchitecture::breakPlantDormancy): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4247 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4248 shoot->breakDormancy();
4249 if (carbon_model_enabled)
4251 shoot->mobilizeStarch();
4257 if (plant_instances.find(plantID) == plant_instances.end()) {
4258 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4259 }
else if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4260 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Shoot with ID of " + std::to_string(shootID) +
" does not exist on plant " + std::to_string(plantID) +
".");
4261 }
else if (node_index >= plant_instances.at(plantID).shoot_tree.at(shootID)->current_node_number) {
4262 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Node index " + std::to_string(node_index) +
" is out of range for shoot " + std::to_string(shootID) +
".");
4265 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4267 shoot->phytomers.at(node_index)->deletePhytomer();
4269 if (plant_instances.at(plantID).shoot_tree.empty()) {
4270 std::cout <<
"WARNING (PlantArchitecture::pruneBranch): Plant " << plantID <<
" base shoot was pruned." << std::endl;
4275static vec3 orthonormal_axis(
const vec3 &v) {
4284static vec3 rodrigues(
const vec3 &v,
const vec3 &k,
float a) {
4285 float c = std::cos(a);
4286 float s = std::sin(a);
4289 return v * c +
cross(k, v) * s + k * (kv * (1.f - c));
4292void 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,
4293 bool set_azimuth)
const {
4294 for (
uint plantID: plantIDs) {
4295 if (plant_instances.find(plantID) == plant_instances.end()) {
4296 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4303 size_t N = objIDs.size();
4304 assert(bases.size() == N);
4305 if (N == 0 || (!set_elevation && !set_azimuth))
4309 std::vector<float> theta(N), phi(N), theta_t(N), phi_t(N);
4310 for (
size_t i = 0; i < N; ++i) {
4313 if (!std::isfinite(n0.
x) || !std::isfinite(n0.
y) || !std::isfinite(n0.
z) || n0.
magnitude() < 1e-6f) {
4314 n0 =
vec3(0.f, 0.f, 1.f);
4324 if (set_elevation && !set_azimuth) {
4327 }
else if (!set_elevation && set_azimuth) {
4328 theta_t[i] = theta[i];
4338 if (set_elevation && !set_azimuth) {
4340 for (
size_t i = 0; i < N; ++i) {
4341 float elev =
PI_F * 0.5f - theta_t[i];
4347 if (!set_elevation && set_azimuth) {
4349 for (
size_t i = 0; i < N; ++i) {
4350 float elev =
PI_F * 0.5f - theta[i];
4358 std::vector<vec3> V0(N), V1(N);
4359 for (
size_t i = 0; i < N; ++i) {
4360 float e0 =
PI_F * 0.5f - theta[i];
4361 float e1 =
PI_F * 0.5f - theta_t[i];
4367 std::vector<int> assignment(N);
4370 std::vector<std::vector<double>> C(N, std::vector<double>(N));
4371 for (
size_t i = 0; i < N; ++i) {
4372 for (
size_t j = 0; j < N; ++j) {
4373 double d = (V0[i] - V1[j]).magnitude();
4374 C[i][j] = std::isfinite(d) ? d : ((std::numeric_limits<double>::max)() * 0.5);
4377 hung.Solve(C, assignment);
4381 for (
size_t i = 0; i < N; ++i) {
4382 int j = assignment[i];
4385 vec3 u = (j >= 0 && j < (int) N ? V1[j] : V0[i]);
4388 v = (v.
magnitude() < 1e-6f ?
vec3(0, 0, 1) : v.normalize());
4389 u = (u.
magnitude() < 1e-6f ?
vec3(0, 0, 1) : u.normalize());
4392 float dot = std::clamp(v * u, -1.f, 1.f);
4397 if (!set_elevation && set_azimuth) {
4399 axis =
vec3(0.f, 0.f, 1.f);
4402 axis = orthonormal_axis(v);
4408 vec3 r = rodrigues(v, axis, ang);
4409 if (!std::isfinite(r.
x) || !std::isfinite(r.
y) || !std::isfinite(r.
z) || r.
magnitude() < 1e-6f) {
4529 if (plant_instances.find(plantID) == plant_instances.end()) {
4530 helios_runtime_error(
"ERROR (PlantArchitecture::getShootNodeCount): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4531 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
4532 helios_runtime_error(
"ERROR (PlantArchitecture::getShootNodeCount): Shoot ID is out of range.");
4534 return plant_instances.at(plantID).shoot_tree.at(shootID)->current_node_number;
4538 if (plant_instances.find(plantID) == plant_instances.end()) {
4539 helios_runtime_error(
"ERROR (PlantArchitecture::getAllShootIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4542 std::vector<uint> shootIDs;
4543 shootIDs.reserve(plant_instances.at(plantID).shoot_tree.size());
4544 for (
uint shootID = 0; shootID < plant_instances.at(plantID).shoot_tree.size(); shootID++) {
4545 shootIDs.push_back(shootID);
4551 if (plant_instances.find(plantID) == plant_instances.end()) {
4552 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4553 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
4556 return plant_instances.at(plantID).shoot_tree.at(shootID);
4560 if (plant_instances.find(plantID) == plant_instances.end()) {
4561 helios_runtime_error(
"ERROR (PlantArchitecture::getShootTaper): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4562 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
4566 float r0 = plant_instances.at(plantID).shoot_tree.at(shootID)->shoot_internode_radii.front().front();
4567 float r1 = plant_instances.at(plantID).shoot_tree.at(shootID)->shoot_internode_radii.back().back();
4569 float taper = (r0 - r1) / r0;
4572 }
else if (taper > 1) {
4580 std::vector<uint> objIDs;
4581 objIDs.reserve(plant_instances.size());
4583 for (
const auto &plant: plant_instances) {
4584 objIDs.push_back(plant.first);
4591 if (plant_instances.find(plantID) == plant_instances.end()) {
4592 helios_runtime_error(
"ERROR (PlantArchitecture::getAllPlantObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4595 std::vector<uint> objIDs;
4597 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
4599 objIDs.push_back(shoot->internode_tube_objID);
4601 for (
const auto &phytomer: shoot->phytomers) {
4602 std::vector<uint> petiole_objIDs_flat =
flatten(phytomer->petiole_objIDs);
4603 objIDs.insert(objIDs.end(), petiole_objIDs_flat.begin(), petiole_objIDs_flat.end());
4604 std::vector<uint> leaf_objIDs_flat =
flatten(phytomer->leaf_objIDs);
4605 objIDs.insert(objIDs.end(), leaf_objIDs_flat.begin(), leaf_objIDs_flat.end());
4606 for (
auto &petiole: phytomer->floral_buds) {
4607 for (
auto &fbud: petiole) {
4608 std::vector<uint> inflorescence_objIDs_flat = fbud.inflorescence_objIDs;
4609 objIDs.insert(objIDs.end(), inflorescence_objIDs_flat.begin(), inflorescence_objIDs_flat.end());
4610 std::vector<uint> peduncle_objIDs_flat = fbud.peduncle_objIDs;
4611 objIDs.insert(objIDs.end(), peduncle_objIDs_flat.begin(), peduncle_objIDs_flat.end());
4620std::vector<uint> PlantArchitecture::getAllPrototypeObjectIDs()
const {
4621 std::vector<uint> objIDs;
4622 for (
const auto &[key, prototype_vec] : unique_leaf_prototype_objIDs) {
4623 for (
const auto &leaflet_vec : prototype_vec) {
4624 for (
uint objID : leaflet_vec) {
4626 objIDs.push_back(objID);
4631 for (
const auto &[key, prototype_vec] : unique_closed_flower_prototype_objIDs) {
4632 for (
uint objID : prototype_vec) {
4634 objIDs.push_back(objID);
4638 for (
const auto &[key, prototype_vec] : unique_open_flower_prototype_objIDs) {
4639 for (
uint objID : prototype_vec) {
4641 objIDs.push_back(objID);
4645 for (
const auto &[key, prototype_vec] : unique_fruit_prototype_objIDs) {
4646 for (
uint objID : prototype_vec) {
4648 objIDs.push_back(objID);
4655void PlantArchitecture::deleteAllPrototypes() {
4656 std::vector<uint> prototype_objIDs = getAllPrototypeObjectIDs();
4657 for (
uint objID : prototype_objIDs) {
4660 unique_leaf_prototype_objIDs.clear();
4661 unique_open_flower_prototype_objIDs.clear();
4662 unique_closed_flower_prototype_objIDs.clear();
4663 unique_fruit_prototype_objIDs.clear();
4668 if (include_hidden) {
4669 std::vector<uint> prototype_objIDs = getAllPrototypeObjectIDs();
4670 objIDs.insert(objIDs.end(), prototype_objIDs.begin(), prototype_objIDs.end());
4676 if (plant_instances.find(plantID) == plant_instances.end()) {
4677 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4680 std::vector<uint> objIDs;
4682 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4684 for (
auto &shoot: shoot_tree) {
4686 objIDs.push_back(shoot->internode_tube_objID);
4694 if (plant_instances.find(plantID) == plant_instances.end()) {
4695 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4698 std::vector<uint> objIDs;
4700 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4702 bool shoot_type_found =
false;
4703 for (
auto &shoot: shoot_tree) {
4704 if (shoot->shoot_type_label == shoot_type_label) {
4705 shoot_type_found =
true;
4707 objIDs.push_back(shoot->internode_tube_objID);
4712 if (!shoot_type_found) {
4713 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): No shoots with shoot type label '" + shoot_type_label +
"' exist for plant with ID " + std::to_string(plantID) +
".");
4720 if (plant_instances.find(plantID) == plant_instances.end()) {
4721 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantPetioleObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4724 std::vector<uint> objIDs;
4726 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4728 for (
auto &shoot: shoot_tree) {
4729 for (
auto &phytomer: shoot->phytomers) {
4730 for (
auto &petiole: phytomer->petiole_objIDs) {
4731 objIDs.insert(objIDs.end(), petiole.begin(), petiole.end());
4740 if (plant_instances.find(plantID) == plant_instances.end()) {
4741 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4744 std::vector<uint> objIDs;
4746 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4748 for (
auto &shoot: shoot_tree) {
4749 for (
auto &phytomer: shoot->phytomers) {
4750 for (
auto &leaf_objID: phytomer->leaf_objIDs) {
4751 objIDs.insert(objIDs.end(), leaf_objID.begin(), leaf_objID.end());
4760 std::vector<uint> objIDs;
4761 objIDs.reserve(50 * plantIDs.size());
4762 for (
const uint plantID: plantIDs) {
4764 objIDs.insert(objIDs.end(), leaf_objIDs.begin(), leaf_objIDs.end());
4770 if (plant_instances.find(plantID) == plant_instances.end()) {
4771 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantPeduncleObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4774 std::vector<uint> objIDs;
4776 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4778 for (
auto &shoot: shoot_tree) {
4779 for (
auto &phytomer: shoot->phytomers) {
4780 for (
auto &petiole: phytomer->floral_buds) {
4781 for (
auto &fbud: petiole) {
4782 objIDs.insert(objIDs.end(), fbud.peduncle_objIDs.begin(), fbud.peduncle_objIDs.end());
4792 if (plant_instances.find(plantID) == plant_instances.end()) {
4793 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4796 std::vector<uint> objIDs;
4798 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4800 for (
auto &shoot: shoot_tree) {
4801 for (
auto &phytomer: shoot->phytomers) {
4802 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4803 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4804 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FLOWER_OPEN || phytomer->floral_buds.at(petiole).at(bud).state == BUD_FLOWER_CLOSED) {
4805 objIDs.insert(objIDs.end(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.begin(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.end());
4816 if (plant_instances.find(plantID) == plant_instances.end()) {
4817 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4820 std::vector<uint> objIDs;
4822 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4824 for (
auto &shoot: shoot_tree) {
4825 for (
auto &phytomer: shoot->phytomers) {
4826 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4827 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4828 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FRUITING) {
4829 objIDs.insert(objIDs.end(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.begin(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.end());
4841 if (plant_instances.find(plantID) == plant_instances.end()) {
4842 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4845 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4847 for (
const auto& shoot : shoot_tree) {
4849 int fruit_count = 0;
4851 for (
const auto& phytomer : shoot->phytomers) {
4852 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4853 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4854 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FRUITING) {
4862 context_ptr->
setObjectData(shoot->internode_tube_objID,
"fruit_count", fruit_count);
4870 if (plant_instances.find(plantID) == plant_instances.end()) {
4871 helios_runtime_error(
"ERROR (PlantArchitecture::getShootInternodeObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4874 std::vector<uint> objIDs;
4876 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4878 for (
auto &shoot: shoot_tree) {
4883 objIDs.push_back(shoot->internode_tube_objID);
4893 if (plant_instances.find(plantID) == plant_instances.end()) {
4894 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantCollisionRelevantObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4897 std::vector<uint> collision_relevant_objects;
4902 if (collision_include_internodes) {
4904 collision_relevant_objects.insert(collision_relevant_objects.end(), internodes.begin(), internodes.end());
4908 if (collision_include_leaves) {
4910 collision_relevant_objects.insert(collision_relevant_objects.end(), leaves.begin(), leaves.end());
4914 if (collision_include_petioles) {
4916 collision_relevant_objects.insert(collision_relevant_objects.end(), petioles.begin(), petioles.end());
4920 if (collision_include_flowers) {
4922 collision_relevant_objects.insert(collision_relevant_objects.end(), flowers.begin(), flowers.end());
4926 if (collision_include_fruit) {
4928 collision_relevant_objects.insert(collision_relevant_objects.end(), fruit.begin(), fruit.end());
4931 return collision_relevant_objects;
4935 std::vector<uint> UUIDs_all;
4936 for (
const auto &instance: plant_instances) {
4938 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4944 std::vector<uint> UUIDs_all;
4945 for (
const auto &instance: plant_instances) {
4948 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4954 std::vector<uint> UUIDs_all;
4955 for (
const auto &instance: plant_instances) {
4958 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4964 std::vector<uint> UUIDs_all;
4965 for (
const auto &instance: plant_instances) {
4968 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4974 std::vector<uint> UUIDs_all;
4975 for (
const auto &instance: plant_instances) {
4978 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4984 std::vector<uint> UUIDs_all;
4985 for (
const auto &instance: plant_instances) {
4988 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4994 std::vector<uint> UUIDs_all;
4995 for (
const auto &instance: plant_instances) {
4998 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
5004 std::vector<uint> objIDs_all;
5005 for (
const auto &instance: plant_instances) {
5007 objIDs_all.insert(objIDs_all.end(), objIDs.begin(), objIDs.end());
5013 carbon_model_enabled =
true;
5017 carbon_model_enabled =
false;
5021 if (current_age < 0) {
5022 helios_runtime_error(
"ERROR (PlantArchitecture::addPlantInstance): Current age must be greater than or equal to zero.");
5025 PlantInstance instance(base_position, current_age,
"custom", context_ptr);
5027 plant_instances.emplace(plant_count, instance);
5030 plant_instances.at(plant_count).shoot_types_snapshot = shoot_types;
5034 return plant_count - 1;
5038 if (plant_instances.find(plantID) == plant_instances.end()) {
5039 helios_runtime_error(
"ERROR (PlantArchitecture::duplicatePlantInstance): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
5042 auto plant_shoot_tree = &plant_instances.at(plantID).shoot_tree;
5047 plant_instances.at(plantID_new).shoot_types_snapshot = plant_instances.at(plantID).shoot_types_snapshot;
5049 if (plant_shoot_tree->empty()) {
5053 if (plant_shoot_tree->front()->phytomers.empty()) {
5058 for (
const auto &shoot: *plant_shoot_tree) {
5059 uint shootID_new = 0;
5060 for (
int node = 0; node < shoot->current_node_number; node++) {
5061 auto phytomer = shoot->phytomers.at(node);
5062 float internode_radius = phytomer->internode_radius_initial;
5063 float internode_length_max = phytomer->internode_length_max;
5064 float internode_scale_factor_fraction = phytomer->current_internode_scale_factor;
5065 float leaf_scale_factor_fraction = 1.f;
5069 AxisRotation original_base_rotation = shoot->base_rotation;
5070 if (shoot->parent_shoot_ID == -1) {
5072 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);
5075 uint parent_node = plant_shoot_tree->at(shoot->parent_shoot_ID)->parent_node_index;
5076 uint parent_petiole_index = 0;
5077 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
5078 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,
5079 shoot->shoot_type_label, parent_petiole_index);
5080 parent_petiole_index++;
5085 appendPhytomerToShoot(plantID_new, shootID_new, plant_instances.at(plantID).shoot_types_snapshot.at(shoot->shoot_type_label).phytomer_parameters, internode_radius, internode_length_max, internode_scale_factor_fraction,
5086 leaf_scale_factor_fraction);
5088 auto phytomer_new = plant_instances.at(plantID_new).shoot_tree.at(shootID_new)->phytomers.back();
5089 for (
uint petiole_index = 0; petiole_index < phytomer->petiole_objIDs.size(); petiole_index++) {
5090 phytomer_new->setLeafScaleFraction(petiole_index, phytomer->current_leaf_scale_factor.at(petiole_index));
5099 if (plant_instances.find(plantID) == plant_instances.end()) {
5105 plant_instances.erase(plantID);
5107 if (plant_instances.empty()) {
5108 deleteAllPrototypes();
5113 for (
uint ID: plantIDs) {
5119 float max_leaf_lifespan,
bool is_evergreen) {
5120 if (plant_instances.find(plantID) == plant_instances.end()) {
5121 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantPhenologicalThresholds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
5124 plant_instances.at(plantID).dd_to_dormancy_break = time_to_dormancy_break;
5125 plant_instances.at(plantID).dd_to_flower_initiation = time_to_flower_initiation;
5126 plant_instances.at(plantID).dd_to_flower_opening = time_to_flower_opening;
5127 plant_instances.at(plantID).dd_to_fruit_set = time_to_fruit_set;
5128 plant_instances.at(plantID).dd_to_fruit_maturity = time_to_fruit_maturity;
5129 plant_instances.at(plantID).dd_to_dormancy = time_to_dormancy;
5130 if (max_leaf_lifespan == 0) {
5131 plant_instances.at(plantID).max_leaf_lifespan = 1e6;
5133 plant_instances.at(plantID).max_leaf_lifespan = max_leaf_lifespan;
5135 plant_instances.at(plantID).is_evergreen = is_evergreen;
5139 if (plant_instances.find(plantID) == plant_instances.end()) {
5140 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantCarbohydrateModelParameters): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
5143 plant_instances.at(plantID).carb_parameters = carb_parameters;
5147 for (
uint plantID: plantIDs) {
5153 plant_instances.at(plantID).dd_to_dormancy_break = 0;
5154 plant_instances.at(plantID).dd_to_flower_initiation = -1;
5155 plant_instances.at(plantID).dd_to_flower_opening = -1;
5156 plant_instances.at(plantID).dd_to_fruit_set = -1;
5157 plant_instances.at(plantID).dd_to_fruit_maturity = -1;
5158 plant_instances.at(plantID).dd_to_dormancy = 1e6;
5170 std::vector<uint> plantIDs = {plantID};
5175 for (
uint plantID: plantIDs) {
5176 if (plant_instances.find(plantID) == plant_instances.end()) {
5177 helios_runtime_error(
"ERROR (PlantArchitecture::advanceTime): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
5185 if (collision_detection_enabled && collision_detection_ptr !=
nullptr) {
5186 rebuildBVHForTimestep();
5190 float phyllochron_min = 9999;
5191 for (
uint plantID: plantIDs) {
5192 PlantInstance &plant_instance = plant_instances.at(plantID);
5193 auto shoot_tree = &plant_instance.shoot_tree;
5194 if (shoot_tree->empty()) {
5197 float phyllochron_min_shoot = shoot_tree->front()->shoot_parameters.phyllochron_min.val();
5198 if (phyllochron_min_shoot < phyllochron_min) {
5199 phyllochron_min = phyllochron_min_shoot;
5201 for (
int i = 1; i < shoot_tree->size(); i++) {
5202 if (shoot_tree->at(i)->shoot_parameters.phyllochron_min.val() < phyllochron_min) {
5203 phyllochron_min_shoot = shoot_tree->at(i)->shoot_parameters.phyllochron_min.val();
5204 if (phyllochron_min_shoot < phyllochron_min) {
5205 phyllochron_min = phyllochron_min_shoot;
5210 if (phyllochron_min == 9999) {
5215 if (carbon_model_enabled) {
5216 accumulateShootPhotosynthesis();
5222 if (time_step_days <= phyllochron_min) {
5223 Nsteps = time_step_days;
5226 Nsteps = std::floor(time_step_days / phyllochron_min);
5227 dt_max_days = phyllochron_min;
5230 float remainder_time = time_step_days - dt_max_days * float(Nsteps);
5231 if (remainder_time > 0.f) {
5236 helios::ProgressBar progress_bar(Nsteps, 50, Nsteps > 1 && printmessages,
"Advancing time");
5237 if (progress_callback) {
5241 for (
int timestep = 0; timestep < Nsteps; timestep++) {
5246 if (cancel_flag !=
nullptr && *cancel_flag != 0) {
5251 bool should_rebuild_bvh =
false;
5252 if (collision_detection_enabled && collision_detection_ptr !=
nullptr) {
5256 should_rebuild_bvh = (timestep % 25 == 0);
5258 should_rebuild_bvh = (timestep % 10 == 0);
5262 if (should_rebuild_bvh) {
5263 rebuildBVHForTimestep();
5267 for (
uint plantID: plantIDs) {
5269 if (!plant_primitives.empty()) {
5270 collision_detection_ptr->
registerTree(plantID, plant_primitives);
5276 if (timestep == Nsteps - 1 && remainder_time != 0.f) {
5277 dt_max_days = remainder_time;
5280 for (
uint plantID: plantIDs) {
5281 PlantInstance &plant_instance = plant_instances.at(plantID);
5283 auto shoot_tree = &plant_instance.shoot_tree;
5285 if (shoot_tree->empty()) {
5289 if (plant_instance.current_age <= plant_instance.max_age && plant_instance.current_age + dt_max_days > plant_instance.max_age) {
5290 }
else if (plant_instance.current_age >= plant_instance.max_age) {
5296 shoot_tree->front()->updateShootNodes(
true);
5302 plant_instance.current_age += dt_max_days;
5303 plant_instance.time_since_dormancy += dt_max_days;
5305 if (plant_instance.time_since_dormancy > plant_instance.dd_to_dormancy_break + plant_instance.dd_to_dormancy) {
5306 plant_instance.time_since_dormancy = 0;
5307 for (
const auto &shoot: *shoot_tree) {
5308 shoot->makeDormant();
5309 shoot->phyllochron_counter = 0;
5315 size_t shoot_count = shoot_tree->size();
5316 for (
int i = 0; i < shoot_count; i++) {
5317 auto shoot = shoot_tree->at(i);
5319 for (
auto &phytomer: shoot->phytomers) {
5320 phytomer->age += dt_max_days;
5322 if (phytomer->phytomer_parameters.phytomer_callback_function !=
nullptr) {
5323 phytomer->phytomer_parameters.phytomer_callback_function(phytomer);
5330 if (shoot->isdormant && plant_instance.time_since_dormancy >= plant_instance.dd_to_dormancy_break) {
5331 shoot->phyllochron_counter = 0;
5332 shoot->breakDormancy();
5333 if (carbon_model_enabled)
5335 shoot->mobilizeStarch();
5339 if (shoot->isdormant) {
5344 for (
auto &phytomer: shoot->phytomers) {
5345 if (phytomer->age > plant_instance.max_leaf_lifespan) {
5347 phytomer->removeLeaf();
5350 if (phytomer->floral_buds.empty()) {
5355 for (
auto &petiole: phytomer->floral_buds) {
5356 for (
auto &fbud: petiole) {
5357 if (fbud.state != BUD_DORMANT && fbud.state != BUD_DEAD) {
5358 fbud.time_counter += dt_max_days;
5360 if (fbud.state != BUD_ACTIVE) {
5361 fbud.age += dt_max_days;
5366 if (shoot->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function !=
nullptr) {
5369 if (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation >= 0.f) {
5371 if ((!shoot->shoot_parameters.flowers_require_dormancy && fbud.time_counter >= plant_instance.dd_to_flower_initiation) ||
5372 (shoot->shoot_parameters.flowers_require_dormancy && fbud.time_counter >= plant_instance.dd_to_flower_initiation)) {
5373 fbud.time_counter = 0;
5374 if (context_ptr->
randu() < shoot->shoot_parameters.flower_bud_break_probability.val()) {
5375 phytomer->setFloralBudState(BUD_FLOWER_CLOSED, fbud);
5377 phytomer->setFloralBudState(BUD_DEAD, fbud);
5379 if (shoot->shoot_parameters.determinate_shoot_growth) {
5380 shoot->terminateApicalBud();
5381 shoot->terminateAxillaryVegetativeBuds();
5386 }
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)) {
5387 if (fbud.time_counter >= plant_instance.dd_to_flower_opening) {
5388 fbud.time_counter = 0;
5389 if (fbud.state == BUD_FLOWER_CLOSED) {
5390 phytomer->setFloralBudState(BUD_FLOWER_OPEN, fbud);
5392 if (context_ptr->
randu() < shoot->shoot_parameters.flower_bud_break_probability.val()) {
5393 phytomer->setFloralBudState(BUD_FLOWER_OPEN, fbud);
5395 phytomer->setFloralBudState(BUD_DEAD, fbud);
5398 if (shoot->shoot_parameters.determinate_shoot_growth) {
5399 shoot->terminateApicalBud();
5400 shoot->terminateAxillaryVegetativeBuds();
5408 if (shoot->shoot_parameters.phytomer_parameters.inflorescence.fruit_prototype_function !=
nullptr) {
5409 if ((fbud.state == BUD_FLOWER_OPEN && plant_instance.dd_to_fruit_set >= 0.f) ||
5411 (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation < 0.f &&
5412 (plant_instance.dd_to_flower_opening < 0.f || shoot->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function ==
nullptr) && plant_instance.dd_to_fruit_set >= 0.f) ||
5414 (fbud.state == BUD_FLOWER_CLOSED && plant_instance.dd_to_flower_opening < 0.f && plant_instance.dd_to_fruit_set >= 0.f)) {
5416 if (fbud.time_counter >= plant_instance.dd_to_fruit_set) {
5417 fbud.time_counter = 0;
5419 float fruit_set_prob = shoot->shoot_parameters.fruit_set_probability.val();
5420 if (fbud.state == BUD_ACTIVE) {
5422 fruit_set_prob *= shoot->shoot_parameters.flower_bud_break_probability.val();
5424 if (context_ptr->
randu() < fruit_set_prob) {
5425 phytomer->setFloralBudState(BUD_FRUITING, fbud);
5427 phytomer->setFloralBudState(BUD_DEAD, fbud);
5429 if (shoot->shoot_parameters.determinate_shoot_growth) {
5430 shoot->terminateApicalBud();
5431 shoot->terminateAxillaryVegetativeBuds();
5443 for (
auto &phytomer: shoot->phytomers) {
5445 if (phytomer->current_internode_scale_factor < 1) {
5446 float dL_internode = dt_max_days * shoot->elongation_rate_instantaneous * phytomer->internode_length_max;
5447 float length_scale = fmin(1.f, (phytomer->getInternodeLength() + dL_internode) / phytomer->internode_length_max);
5448 phytomer->setInternodeLengthScaleFraction(length_scale,
false);
5452 if (shoot->shoot_parameters.girth_area_factor.val() > 0.f) {
5453 if (carbon_model_enabled) {
5454 incrementPhytomerInternodeGirth_carb(plantID, shoot->ID, node_index, dt_max_days,
false);
5456 incrementPhytomerInternodeGirth(plantID, shoot->ID, node_index, dt_max_days,
false);
5464 for (
auto &phytomer: shoot->phytomers) {
5466 if (phytomer->hasLeaf()) {
5467 for (
uint petiole_index = 0; petiole_index < phytomer->current_leaf_scale_factor.size(); petiole_index++) {
5468 if (phytomer->current_leaf_scale_factor.at(petiole_index) >= 1) {
5479 float tip_ind = ceil(
float(phytomer->leaf_size_max.at(petiole_index).size() - 1) / 2.f);
5480 float leaf_length = phytomer->current_leaf_scale_factor.at(petiole_index) * phytomer->leaf_size_max.at(petiole_index).at(tip_ind);
5481 float dL_leaf = dt_max_days * shoot->elongation_rate_instantaneous * phytomer->leaf_size_max.at(petiole_index).at(tip_ind);
5482 float leaf_scale = fmin(1.f, (leaf_length + dL_leaf) / phytomer->phytomer_parameters.leaf.prototype_scale.val());
5486 float scale = fmin(1.f, (leaf_length + dL_leaf) / phytomer->phytomer_parameters.leaf.prototype_scale.val());
5487 phytomer->phytomer_parameters.leaf.prototype_scale.resample();
5488 phytomer->setLeafScaleFraction(petiole_index, scale);
5493 for (
auto &petiole: phytomer->floral_buds) {
5494 for (
auto &fbud: petiole) {
5496 if (fbud.state == BUD_FRUITING && fbud.time_counter > 0) {
5498 fbud.previous_fruit_scale_factor = fbud.current_fruit_scale_factor;
5499 float scale = fmin(1, 0.25f + 0.75f * fbud.time_counter / plant_instance.dd_to_fruit_maturity);
5500 phytomer->setInflorescenceScaleFraction(fbud, scale);
5506 uint parent_petiole_index = 0;
5507 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
5508 for (
auto &vbud: petiole) {
5509 if (vbud.state == BUD_ACTIVE && phytomer->age + dt_max_days > shoot->shoot_parameters.vegetative_bud_break_time.val()) {
5511 int parent_node_count = shoot->current_node_number;
5518 new_shoot_parameters->
base_yaw.resample();
5524 float internode_length_max;
5532 float internode_radius = phytomer->internode_radius_initial;
5534 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);
5536 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
5537 vbud.shoot_ID = childID;
5538 shoot_tree->at(childID)->isdormant =
false;
5541 parent_petiole_index++;
5548 if (shoot->current_node_number >= shoot->shoot_parameters.max_nodes.val()) {
5549 shoot->terminateApicalBud();
5553 if (!shoot->meristem_is_alive) {
5558 shoot->phyllochron_counter += dt_max_days;
5559 if (shoot->phyllochron_counter >= shoot->phyllochron_instantaneous && !shoot->phytomers.back()->isdormant) {
5560 float internode_radius = shoot->shoot_parameters.phytomer_parameters.internode.radius_initial.val();
5561 shoot->shoot_parameters.phytomer_parameters.internode.radius_initial.resample();
5562 float internode_length_max = shoot->internode_length_max_shoot_initial;
5565 shoot->phyllochron_counter = shoot->phyllochron_counter - shoot->phyllochron_instantaneous;
5569 std::string epicormic_shoot_label = plant_instance.epicormic_shoot_probability_perlength_per_day.first;
5570 if (!epicormic_shoot_label.empty()) {
5571 std::vector<float> epicormic_fraction;
5572 uint Nepicormic = shoot->sampleEpicormicShoot(time_step_days, epicormic_fraction);
5573 for (
int s = 0; s < Nepicormic; s++) {
5574 float internode_radius = plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).phytomer_parameters.internode.radius_initial.val();
5575 plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).phytomer_parameters.internode.radius_initial.resample();
5576 float internode_length_max = plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).internode_length_max.val();
5578 addEpicormicShoot(plantID, shoot->ID, epicormic_fraction.at(s), 1, 0, internode_radius, internode_length_max, 0.01, 0.01, 0, epicormic_shoot_label);
5581 if (carbon_model_enabled) {
5582 if (output_object_data.find(
"carbohydrate_concentration") != output_object_data.end() && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
5583 float shoot_volume = shoot->calculateShootInternodeVolume();
5584 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", shoot->total_carbohydrate_pool_molC / shoot_volume);
5591 bool should_update_context = collision_detection_enabled && (geometry_update_counter >= geometry_update_frequency);
5594 bool force_update = collision_avoidance_applied && force_update_on_collision;
5596 if (should_update_context || force_update) {
5597 shoot_tree->front()->updateShootNodes(
true);
5601 shoot_tree->front()->updateShootNodes(
false);
5605 collision_avoidance_applied =
false;
5608 if (ground_clipping_height != -99999) {
5609 pruneGroundCollisions(plantID);
5613 if (carbon_model_enabled) {
5614 subtractShootMaintenanceCarbon(dt_max_days);
5615 subtractShootGrowthCarbon();
5616 checkCarbonPool_transferCarbon(dt_max_days);
5617 checkCarbonPool_adjustPhyllochron(dt_max_days);
5618 checkCarbonPool_abortOrgans(dt_max_days);
5622 for (
auto &shoot: *shoot_tree) {
5628 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
5630 float volume_ratio = shoot->old_shoot_volume/shoot_volume;
5631 context_ptr->
setObjectData(shoot->internode_tube_objID,
"volume_ratio", volume_ratio);
5632 shoot->old_shoot_volume = shoot_volume;
5633 context_ptr->
setObjectData(shoot->internode_tube_objID,
"old_shoot_volume", shoot_volume);
5638 if (!plant_primitives.empty()) {
5639 if (output_object_data.at(
"plant_height")) {
5642 if (output_object_data.at(
"phenology_stage")) {
5649 if (nitrogen_model_enabled) {
5650 accumulateLeafNitrogen(dt_max_days);
5651 remobilizeNitrogen(dt_max_days);
5652 removeFruitNitrogen();
5653 updateNitrogenStressFactor();
5657 if (geometry_update_counter >= geometry_update_frequency) {
5658 geometry_update_counter = 0;
5660 geometry_update_counter++;
5671 if (solid_obstacle_pruning_enabled) {
5672 pruneSolidBoundaryCollisions();
5677 if (!collision_detection_enabled) {
5678 for (
uint plantID: plantIDs) {
5679 if (plant_instances.find(plantID) != plant_instances.end()) {
5680 plant_instances.at(plantID).shoot_tree.front()->updateShootNodes(
true);
5687 if (output_object_data.at(
"age")) {
5688 for (
uint plantID: plantIDs) {
5689 if (plant_instances.find(plantID) == plant_instances.end()) {
5693 auto shoot_tree = &plant_instances.at(plantID).shoot_tree;
5694 for (
auto &shoot: *shoot_tree) {
5697 if (shoot->build_context_geometry_internode && !shoot->phytomers.empty()) {
5700 float shoot_age = shoot->phytomers.back()->age;
5701 context_ptr->
setObjectData(shoot->internode_tube_objID,
"age", shoot_age);
5706 for (
auto &phytomer: shoot->phytomers) {
5707 if (phytomer->build_context_geometry_petiole) {
5708 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"age", phytomer->age);
5710 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"age", phytomer->age);
5711 for (
auto &petiole: phytomer->floral_buds) {
5712 for (
auto &fbud: petiole) {
5713 if (fbud.state != BUD_DORMANT && fbud.state != BUD_ACTIVE && fbud.state != BUD_DEAD) {
5714 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"age", fbud.age);
5715 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"age", fbud.age);
5729 if (!solid_obstacle_avoidance_enabled || solid_obstacle_UUIDs.empty() || !solid_obstacle_fruit_adjustment_enabled) {
5733 if (collision_detection_ptr ==
nullptr) {
5738 int debug_failures_shown = 0;
5739 const int max_debug_failures = 0;
5742 helios::ProgressBar progress_bar(plant_instances.size(), 50, plant_instances.size() > 1 && printmessages,
"Adjusting fruit collisions");
5743 if (progress_callback) {
5748 for (
const auto &plant_instance: plant_instances) {
5749 uint plantID = plant_instance.first;
5754 if (fruit_objIDs.empty()) {
5759 for (
uint fruit_objID: fruit_objIDs) {
5763 if (fruit_UUIDs.empty()) {
5768 std::vector<uint> collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5770 if (!collisions.empty()) {
5774 vec3 bbox_min, bbox_max;
5780 const Phytomer *fruit_phytomer =
nullptr;
5781 uint fruit_petiole_index = 0;
5782 uint fruit_bud_index = 0;
5783 bool found_base =
false;
5786 for (
const auto &shoot: plant_instance.second.shoot_tree) {
5787 for (
const auto &phytomer: shoot->phytomers) {
5788 uint petiole_idx = 0;
5789 for (
const auto &petiole: phytomer->floral_buds) {
5790 for (
const auto &fbud: petiole) {
5792 for (
size_t idx = 0; idx < fbud.inflorescence_objIDs.size(); idx++) {
5793 if (fbud.inflorescence_objIDs[idx] == fruit_objID && idx < fbud.inflorescence_bases.size()) {
5795 fruit_base = fbud.inflorescence_bases[idx];
5796 fruit_phytomer = phytomer.get();
5797 fruit_petiole_index = petiole_idx;
5798 fruit_bud_index = fbud.bud_index;
5802 peduncle_axis = phytomer->getPeduncleAxisVector(1.0f, petiole_idx, fbud.bud_index);
5803 }
catch (
const std::exception &e) {
5832 float fruit_radius = 0;
5833 fruit_radius = std::max(fruit_radius, (bbox_max - fruit_base).magnitude());
5834 fruit_radius = std::max(fruit_radius, (bbox_min - fruit_base).magnitude());
5835 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_min.
y, bbox_max.
z) - fruit_base).magnitude());
5836 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_max.
y, bbox_min.
z) - fruit_base).magnitude());
5837 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_min.
y, bbox_min.
z) - fruit_base).magnitude());
5838 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_max.
y, bbox_max.
z) - fruit_base).magnitude());
5839 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_min.
y, bbox_max.
z) - fruit_base).magnitude());
5840 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_max.
y, bbox_min.
z) - fruit_base).magnitude());
5844 float penetration_depth = std::max(0.0f, -bbox_min.
z);
5847 float initial_rotation = 0;
5848 if (fruit_radius > 0 && penetration_depth > 0) {
5850 float angle_estimate = std::asin(std::min(1.0f, penetration_depth / fruit_radius));
5852 initial_rotation = std::min(
deg2rad(35.0f), angle_estimate * 1.5f);
5855 initial_rotation =
deg2rad(10.0f);
5859 initial_rotation = std::max(initial_rotation,
deg2rad(8.0f));
5865 if (peduncle_axis.
magnitude() < 1e-6f) {
5873 vec3 bbox_center = 0.5f * (bbox_min + bbox_max);
5874 vec3 to_fruit_center = bbox_center - fruit_base;
5875 if (to_fruit_center.
magnitude() > 1e-6f) {
5879 to_fruit_center = peduncle_axis;
5884 rotation_axis =
cross(peduncle_axis, to_fruit_center);
5885 if (rotation_axis.
magnitude() < 1e-6f) {
5887 if (std::abs(peduncle_axis.
z) < 0.9f) {
5896 float rotation_step = initial_rotation;
5897 float total_rotation = 0;
5898 const float max_rotation =
deg2rad(120.0f);
5899 const int max_iterations = 25;
5902 bool debug_this_fruit = (debug_failures_shown < max_debug_failures);
5903 if (debug_this_fruit && printmessages) {
5904 std::cout <<
"\n=== DEBUG: Fruit " << fruit_objID <<
" collision adjustment ===" << std::endl;
5905 std::cout <<
"Fruit base: " << fruit_base << std::endl;
5906 std::cout <<
"Fruit bbox: " << bbox_min <<
" to " << bbox_max << std::endl;
5907 std::cout <<
"Fruit radius: " << fruit_radius << std::endl;
5908 std::cout <<
"Penetration depth: " << penetration_depth << std::endl;
5909 std::cout <<
"Peduncle axis: " << peduncle_axis << std::endl;
5910 std::cout <<
"Rotation axis: " << rotation_axis << std::endl;
5911 std::cout <<
"Initial rotation: " <<
rad2deg(initial_rotation) <<
" degrees" << std::endl;
5912 std::cout <<
"Initial collisions: " << collisions.size() << std::endl;
5915 for (
int iter = 0; iter < max_iterations && total_rotation < max_rotation; iter++) {
5918 context_ptr->
rotateObject(fruit_objID, -rotation_step, fruit_base, rotation_axis);
5919 total_rotation += rotation_step;
5923 collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5925 if (debug_this_fruit && printmessages) {
5926 std::cout <<
"Iter " << iter <<
": rotated " <<
rad2deg(rotation_step) <<
" deg (total " <<
rad2deg(total_rotation) <<
"), collisions: " << collisions.size() << std::endl;
5929 if (collisions.empty()) {
5932 float fine_tune_step =
deg2rad(3.0f);
5933 float fine_tune_attempts = 5;
5934 float original_total = total_rotation;
5936 if (debug_this_fruit && printmessages) {
5937 std::cout <<
"Fine-tuning: trying to rotate back down from " <<
rad2deg(total_rotation) <<
" degrees" << std::endl;
5940 for (
int fine_iter = 0; fine_iter < fine_tune_attempts; fine_iter++) {
5942 context_ptr->
rotateObject(fruit_objID, fine_tune_step, fruit_base, rotation_axis);
5946 std::vector<uint> test_collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5948 if (!test_collisions.empty()) {
5950 context_ptr->
rotateObject(fruit_objID, -fine_tune_step, fruit_base, rotation_axis);
5954 total_rotation -= fine_tune_step;
5963 rotation_step *= 0.7f;
5967 if (!collisions.empty()) {
5968 if (debug_this_fruit && printmessages) {
5969 std::cout <<
"FAILED: Fruit " << fruit_objID <<
" still colliding after " <<
rad2deg(total_rotation) <<
" degrees rotation (" << max_iterations <<
" iterations)" << std::endl;
5972 vec3 final_bbox_min, final_bbox_max;
5974 std::cout <<
"Final bbox: " << final_bbox_min <<
" to " << final_bbox_max << std::endl;
5975 std::cout <<
"Lowest point: " << final_bbox_min.
z << std::endl;
5977 debug_failures_shown++;
5991void PlantArchitecture::pruneSolidBoundaryCollisions() {
5992 if (!solid_obstacle_avoidance_enabled || solid_obstacle_UUIDs.empty()) {
5996 if (collision_detection_ptr ==
nullptr) {
6000 if (printmessages) {
6001 std::cout <<
"Performing solid boundary collision detection..." << std::endl;
6006 std::vector<uint> all_plant_primitives;
6010 std::vector<uint> intersecting_primitives = collision_detection_ptr->
findCollisions(solid_obstacle_UUIDs, {}, all_plant_primitives, {},
false);
6015 if (intersecting_primitives.empty()) {
6016 if (printmessages) {
6017 std::cout <<
"No collisions detected - this is unexpected given visible fruit penetration" << std::endl;
6022 if (printmessages) {
6023 std::cout <<
"Intersecting primitives found: " << intersecting_primitives.size() << std::endl;
6027 std::unordered_set<uint> collision_set(intersecting_objIDs.begin(), intersecting_objIDs.end());
6030 for (
auto &[plantID, plant]: plant_instances) {
6031 for (
uint shootID = 0; shootID < plant.shoot_tree.size(); shootID++) {
6032 auto &shoot = plant.shoot_tree.at(shootID);
6033 bool shoot_was_deleted =
false;
6037 if (collision_set.count(shoot->internode_tube_objID)) {
6039 if (shoot->rank != 0) {
6042 shoot_was_deleted =
true;
6048 if (shoot_was_deleted) {
6052 for (
uint node = 0; node < shoot->current_node_number; node++) {
6053 auto &phytomer = shoot->phytomers.at(node);
6056 for (
uint petiole = 0; petiole < phytomer->leaf_objIDs.size(); petiole++) {
6057 for (
uint leaflet = 0; leaflet < phytomer->leaf_objIDs.at(petiole).size(); leaflet++) {
6058 uint leaf_objID = phytomer->leaf_objIDs.at(petiole).at(leaflet);
6059 if (collision_set.count(leaf_objID)) {
6060 phytomer->removeLeaf();
6067 for (
uint petiole = 0; petiole < phytomer->petiole_objIDs.size(); petiole++) {
6068 for (
uint segment = 0; segment < phytomer->petiole_objIDs.at(petiole).size(); segment++) {
6069 uint petiole_objID = phytomer->petiole_objIDs.at(petiole).at(segment);
6070 if (collision_set.count(petiole_objID)) {
6071 phytomer->removeLeaf();
6078 for (
auto &petiole: phytomer->floral_buds) {
6079 for (
auto &fbud: petiole) {
6081 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
6082 uint objID = fbud.inflorescence_objIDs.at(p);
6083 if (collision_set.count(objID)) {
6085 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
6086 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
6090 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
6091 uint objID = fbud.peduncle_objIDs.at(p);
6092 if (collision_set.count(objID)) {
6096 fbud.peduncle_objIDs.clear();
6097 fbud.inflorescence_objIDs.clear();
6098 fbud.inflorescence_bases.clear();
6106 if (shoot_was_deleted) {
6112 if (printmessages) {
6113 std::cout <<
"Solid boundary collision pruning completed" << std::endl;
6117std::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) {
6118 uint Nverts = vertices.size();
6120 if (radii.size() != Nverts || colors.size() != Nverts) {
6121 helios_runtime_error(
"ERROR (makeTubeFromCones): Length of vertex vectors is not consistent.");
6125 bool all_radii_too_small =
true;
6126 float max_radius = 0.0f;
6127 for (
float radius: radii) {
6128 max_radius = std::max(max_radius, radius);
6129 if (radius >= MIN_TUBE_RADIUS_FOR_GEOMETRY) {
6130 all_radii_too_small =
false;
6136 float total_length = 0.0f;
6137 for (
uint v = 0; v < Nverts - 1; v++) {
6138 total_length += (vertices.at(v + 1) - vertices.at(v)).magnitude();
6143 if (all_radii_too_small || total_length < MIN_TUBE_LENGTH_FOR_GEOMETRY) {
6144 return std::vector<uint>();
6147 std::vector<uint> objIDs;
6148 objIDs.reserve(Nverts - 1);
6150 for (
uint v = 0; v < Nverts - 1; v++) {
6151 if ((vertices.at(v + 1) - vertices.at(v)).magnitude() < 1e-6f) {
6154 float r0 = std::max(radii.at(v), MIN_TUBE_RADIUS_FOR_GEOMETRY);
6155 float r1 = std::max(radii.at(v + 1), MIN_TUBE_RADIUS_FOR_GEOMETRY);
6156 objIDs.push_back(context_ptr->
addConeObject(radial_subdivisions, vertices.at(v), vertices.at(v + 1), r0, r1, colors.at(v)));
6162bool PlantArchitecture::detectGroundCollision(
uint objID) {
6163 std::vector<uint> objIDs = {objID};
6164 return detectGroundCollision(objIDs);
6167bool PlantArchitecture::detectGroundCollision(
const std::vector<uint> &objID)
const {
6168 for (
uint ID: objID) {
6171 for (
uint UUID: UUIDs) {
6173 for (
const vec3 &v: vertices) {
6174 if (v.
z < ground_clipping_height) {
6186 std::string label_lower = object_data_label;
6187 std::transform(label_lower.begin(), label_lower.end(), label_lower.begin(), ::tolower);
6190 if (label_lower ==
"all") {
6192 for (
auto &item: output_object_data) {
6199 if (output_object_data.find(object_data_label) == output_object_data.end()) {
6200 helios_runtime_error(
"ERROR (PlantArchitecture::optionalOutputObjectData): Output object data of '" + object_data_label +
"' is not a valid option.");
6203 output_object_data.at(object_data_label) =
true;
6207 for (
const auto &label: object_data_labels) {
6215 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
6216 delete collision_detection_ptr;
6217 collision_detection_ptr =
nullptr;
6218 owns_collision_detection =
false;
6225 owns_collision_detection =
true;
6226 collision_detection_enabled =
true;
6227 collision_target_UUIDs = target_object_UUIDs;
6228 collision_target_object_IDs = target_object_IDs;
6231 petiole_collision_detection_enabled = enable_petiole_collision;
6232 fruit_collision_detection_enabled = enable_fruit_collision;
6241 std::vector<uint> static_obstacles;
6242 static_obstacles.insert(static_obstacles.end(), target_object_UUIDs.begin(), target_object_UUIDs.end());
6243 static_obstacles.insert(static_obstacles.end(), target_object_IDs.begin(), target_object_IDs.end());
6246 rebuildBVHForTimestep();
6249 if (solid_obstacle_avoidance_enabled) {
6250 static_obstacles.insert(static_obstacles.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
6258 for (
uint plant_id: plant_ids) {
6260 if (!plant_primitives.empty()) {
6261 collision_detection_ptr->
registerTree(plant_id, plant_primitives);
6267 }
catch (
const std::exception &e) {
6268 helios_runtime_error(
"ERROR (PlantArchitecture::enableSoftCollisionAvoidance): Failed to create CollisionDetection instance: " + std::string(e.what()));
6273 collision_detection_enabled =
false;
6276 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
6277 delete collision_detection_ptr;
6278 owns_collision_detection =
false;
6281 collision_detection_ptr =
nullptr;
6282 collision_target_UUIDs.clear();
6283 collision_target_object_IDs.clear();
6285 if (printmessages) {
6286 std::cout <<
"Collision detection disabled for plant growth and internal instance cleaned up" << std::endl;
6291 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6292 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): cone_half_angle_deg must be between 0 and 180 degrees.");
6294 if (look_ahead_distance <= 0.0f) {
6295 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): sample_count must be positive.");
6297 if (inertia_weight < 0.0f || inertia_weight > 1.0f) {
6298 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): inertia_weight must be between 0.0 and 1.0.");
6301 collision_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6302 collision_cone_height = look_ahead_distance;
6303 collision_sample_count = sample_count;
6304 collision_inertia_weight = inertia_weight;
6308 if (collision_detection_ptr ==
nullptr) {
6309 helios_runtime_error(
"ERROR (PlantArchitecture::setStaticObstacles): Collision detection must be enabled before setting static obstacles.");
6314 if (printmessages) {
6315 std::cout <<
"Marked " << target_UUIDs.size() <<
" primitives as static obstacles for collision detection" << std::endl;
6320 return collision_detection_ptr;
6324 collision_include_internodes = include_internodes;
6325 collision_include_leaves = include_leaves;
6326 collision_include_petioles = include_petioles;
6327 collision_include_flowers = include_flowers;
6328 collision_include_fruit = include_fruit;
6333 if (printmessages) {
6334 std::cout <<
"Set collision-relevant organs: internodes=" << (include_internodes ?
"yes" :
"no") <<
", leaves=" << (include_leaves ?
"yes" :
"no") <<
", petioles=" << (include_petioles ?
"yes" :
"no")
6335 <<
", flowers=" << (include_flowers ?
"yes" :
"no") <<
", fruit=" << (include_fruit ?
"yes" :
"no") << std::endl;
6341 solid_obstacle_avoidance_enabled =
true;
6342 solid_obstacle_UUIDs = obstacle_UUIDs;
6343 solid_obstacle_avoidance_distance = avoidance_distance;
6344 solid_obstacle_fruit_adjustment_enabled = enable_fruit_adjustment;
6345 solid_obstacle_pruning_enabled = enable_obstacle_pruning;
6348 if (collision_detection_ptr ==
nullptr) {
6352 owns_collision_detection =
true;
6353 collision_detection_enabled =
true;
6361 rebuildBVHForTimestep();
6362 }
catch (std::exception &e) {
6363 helios_runtime_error(
"ERROR (PlantArchitecture::enableSolidObstacleAvoidance): Failed to create CollisionDetection instance: " + std::string(e.what()));
6368 if (collision_detection_enabled && collision_detection_ptr !=
nullptr && collision_detection_ptr->
isTreeBasedBVHEnabled()) {
6369 std::vector<uint> static_obstacles;
6370 static_obstacles.insert(static_obstacles.end(), collision_target_UUIDs.begin(), collision_target_UUIDs.end());
6371 static_obstacles.insert(static_obstacles.end(), collision_target_object_IDs.begin(), collision_target_object_IDs.end());
6372 static_obstacles.insert(static_obstacles.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
6378void PlantArchitecture::clearBVHCache()
const {
6379 bvh_cached_for_current_growth =
false;
6380 cached_target_geometry.clear();
6381 cached_filtered_geometry.clear();
6385void PlantArchitecture::rebuildBVHForTimestep() {
6386 if (!collision_detection_enabled || collision_detection_ptr ==
nullptr) {
6392 std::vector<uint> target_geometry;
6395 if (solid_obstacle_avoidance_enabled && !solid_obstacle_UUIDs.empty()) {
6396 target_geometry.insert(target_geometry.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
6399 if (!collision_target_UUIDs.empty()) {
6401 std::vector<uint> valid_targets;
6402 for (
uint uuid: collision_target_UUIDs) {
6404 valid_targets.push_back(uuid);
6408 target_geometry.insert(target_geometry.end(), valid_targets.begin(), valid_targets.end());
6409 }
else if (!collision_target_object_IDs.empty()) {
6411 for (
uint objID: collision_target_object_IDs) {
6414 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
6420 std::vector<uint> preserved_solid_obstacles = target_geometry;
6421 target_geometry.clear();
6425 if (collision_include_internodes) {
6427 target_geometry.insert(target_geometry.end(), internode_uuids.begin(), internode_uuids.end());
6429 if (collision_include_leaves) {
6431 target_geometry.insert(target_geometry.end(), leaf_uuids.begin(), leaf_uuids.end());
6433 if (collision_include_petioles) {
6435 target_geometry.insert(target_geometry.end(), petiole_uuids.begin(), petiole_uuids.end());
6437 if (collision_include_flowers) {
6439 target_geometry.insert(target_geometry.end(), flower_uuids.begin(), flower_uuids.end());
6441 if (collision_include_fruit) {
6443 target_geometry.insert(target_geometry.end(), fruit_uuids.begin(), fruit_uuids.end());
6445 }
catch (
const std::exception &e) {
6446 if (printmessages) {
6447 std::cout <<
"Warning: Exception in organ filtering, falling back to all geometry: " << e.what() << std::endl;
6453 target_geometry.insert(target_geometry.end(), preserved_solid_obstacles.begin(), preserved_solid_obstacles.end());
6456 std::vector<uint> all_context_geometry = context_ptr->
getAllUUIDs();
6457 std::set<uint> all_plant_geometry_set;
6460 all_plant_geometry_set.insert(all_plant.begin(), all_plant.end());
6461 }
catch (
const std::exception &e) {
6462 if (printmessages) {
6463 std::cout <<
"Warning: Could not get plant geometry for external obstacle filtering: " << e.what() << std::endl;
6467 for (
uint uuid: all_context_geometry) {
6468 if (all_plant_geometry_set.find(uuid) == all_plant_geometry_set.end()) {
6469 target_geometry.push_back(uuid);
6474 if (!target_geometry.empty()) {
6476 std::vector<uint> plant_geometry;
6479 }
catch (
const std::exception &e) {
6480 if (printmessages) {
6481 std::cout <<
"Warning: Could not get plant geometry for hierarchical BVH: " << e.what() << std::endl;
6483 plant_geometry.clear();
6485 std::set<uint> plant_set(plant_geometry.begin(), plant_geometry.end());
6487 std::vector<uint> static_obstacles;
6488 for (
uint uuid: target_geometry) {
6489 if (plant_set.find(uuid) == plant_set.end()) {
6490 static_obstacles.push_back(uuid);
6497 collision_detection_ptr->
updateBVH(target_geometry,
true);
6501 cached_target_geometry = target_geometry;
6502 cached_filtered_geometry = target_geometry;
6503 bvh_cached_for_current_growth =
true;
6508 if (update_frequency < 1) {
6509 helios_runtime_error(
"ERROR (PlantArchitecture::setGeometryUpdateScheduling): update_frequency must be at least 1.");
6512 geometry_update_frequency = update_frequency;
6513 geometry_update_counter = 0;
6519 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6520 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6522 if (look_ahead_distance <= 0.0f) {
6523 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): look_ahead_distance must be positive.");
6525 if (attraction_weight_input < 0.0f || attraction_weight_input > 1.0f) {
6526 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): attraction_weight must be between 0.0 and 1.0.");
6530 attraction_points_enabled =
true;
6531 attraction_points = attraction_points_input;
6532 attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6533 attraction_cone_height = look_ahead_distance;
6534 attraction_weight = attraction_weight_input;
6537 for (
auto &[plantID, plant]: plant_instances) {
6538 plant.attraction_points_enabled =
true;
6539 plant.attraction_points = attraction_points_input;
6540 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6541 plant.attraction_cone_height = look_ahead_distance;
6542 plant.attraction_weight = attraction_weight_input;
6548 attraction_points_enabled =
false;
6549 attraction_points.clear();
6552 for (
auto &[plantID, plant]: plant_instances) {
6553 plant.attraction_points_enabled =
false;
6554 plant.attraction_points.clear();
6559 if (!attraction_points_enabled) {
6560 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Attraction points must be enabled before updating positions.");
6562 if (attraction_points_input.empty()) {
6563 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): attraction_points cannot be empty.");
6567 attraction_points = attraction_points_input;
6570 for (
auto &[plantID, plant]: plant_instances) {
6571 if (plant.attraction_points_enabled) {
6572 plant.attraction_points = attraction_points_input;
6578 if (!attraction_points_enabled) {
6579 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Attraction points must be enabled before updating positions.");
6581 if (attraction_points_input.empty()) {
6582 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): attraction_points cannot be empty.");
6586 attraction_points.insert(attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6589 for (
auto &[plantID, plant]: plant_instances) {
6590 if (plant.attraction_points_enabled) {
6591 plant.attraction_points.insert(plant.attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6597 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6598 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): view_half_angle_deg must be between 0 and 180 degrees.");
6600 if (look_ahead_distance <= 0.0f) {
6601 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): look_ahead_distance must be positive.");
6603 if (attraction_weight_input < 0.0f || attraction_weight_input > 1.0f) {
6604 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): attraction_weight must be between 0.0 and 1.0.");
6606 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6607 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): obstacle_reduction_factor must be between 0.0 and 1.0.");
6611 attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6612 attraction_cone_height = look_ahead_distance;
6613 attraction_weight = attraction_weight_input;
6614 attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6617 for (
auto &[plantID, plant]: plant_instances) {
6618 if (plant.attraction_points_enabled) {
6619 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6620 plant.attraction_cone_height = look_ahead_distance;
6621 plant.attraction_weight = attraction_weight_input;
6622 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6626 if (printmessages) {
6627 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;
6628 if (!plant_instances.empty()) {
6629 std::cout <<
"Applied to " << plant_instances.size() <<
" existing plants with attraction points enabled" << std::endl;
6637 if (plant_instances.find(plantID) == plant_instances.end()) {
6638 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6641 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6642 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6644 if (look_ahead_distance <= 0.0f) {
6645 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): look_ahead_distance must be greater than 0.");
6647 if (attraction_points_input.empty()) {
6648 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): attraction_points cannot be empty.");
6651 auto &plant = plant_instances.at(plantID);
6652 plant.attraction_points_enabled =
true;
6653 plant.attraction_points = attraction_points_input;
6654 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6655 plant.attraction_cone_height = look_ahead_distance;
6656 plant.attraction_weight = attraction_weight_input;
6658 if (printmessages) {
6659 std::cout <<
"Enabled attraction points for plant " << plantID <<
" with " << attraction_points_input.size() <<
" target positions" << std::endl;
6660 std::cout <<
"Plant " << plantID <<
" attraction parameters: cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input << std::endl;
6665 if (plant_instances.find(plantID) == plant_instances.end()) {
6666 helios_runtime_error(
"ERROR (PlantArchitecture::disableAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6669 auto &plant = plant_instances.at(plantID);
6670 plant.attraction_points_enabled =
false;
6671 plant.attraction_points.clear();
6673 if (printmessages) {
6674 std::cout <<
"Disabled attraction points for plant " << plantID <<
" - will use natural growth patterns" << std::endl;
6679 if (plant_instances.find(plantID) == plant_instances.end()) {
6680 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6683 auto &plant = plant_instances.at(plantID);
6684 if (!plant.attraction_points_enabled) {
6685 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Attraction points must be enabled for plant " + std::to_string(plantID) +
" before updating positions.");
6687 if (attraction_points_input.empty()) {
6688 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): attraction_points cannot be empty.");
6691 plant.attraction_points = attraction_points_input;
6695 if (plant_instances.find(plantID) == plant_instances.end()) {
6696 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6699 auto &plant = plant_instances.at(plantID);
6700 if (!plant.attraction_points_enabled) {
6701 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Attraction points must be enabled for plant " + std::to_string(plantID) +
" before updating positions.");
6703 if (attraction_points_input.empty()) {
6704 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): attraction_points cannot be empty.");
6707 plant.attraction_points.insert(plant.attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6711 if (plant_instances.find(plantID) == plant_instances.end()) {
6712 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6715 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6716 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): view_half_angle_deg must be between 0 and 180 degrees.");
6718 if (look_ahead_distance <= 0.0f) {
6719 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): look_ahead_distance must be greater than 0.");
6721 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6722 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): obstacle_reduction_factor must be between 0 and 1.");
6725 auto &plant = plant_instances.at(plantID);
6726 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6727 plant.attraction_cone_height = look_ahead_distance;
6728 plant.attraction_weight = attraction_weight_input;
6729 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6731 if (printmessages) {
6732 std::cout <<
"Updated attraction parameters for plant " << plantID <<
": cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input
6733 <<
", obstacle_reduction=" << obstacle_reduction_factor << std::endl;
6737void 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) {
6738 if (plant_instances.find(plantID) == plant_instances.end()) {
6739 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6742 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6743 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6745 if (look_ahead_distance <= 0.0f) {
6746 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): look_ahead_distance must be greater than 0.");
6748 if (attraction_points_input.empty()) {
6749 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): attraction_points cannot be empty.");
6751 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6752 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): obstacle_reduction_factor must be between 0 and 1.");
6755 auto &plant = plant_instances.at(plantID);
6756 plant.attraction_points_enabled =
true;
6757 plant.attraction_points = attraction_points_input;
6758 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6759 plant.attraction_cone_height = look_ahead_distance;
6760 plant.attraction_weight = attraction_weight_input;
6761 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6765 printmessages =
false;
6766 if (collision_detection_ptr !=
nullptr) {
6772 printmessages =
true;
6773 if (collision_detection_ptr !=
nullptr) {