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);
152 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
153 delete collision_detection_ptr;
154 collision_detection_ptr =
nullptr;
155 owns_collision_detection =
false;
161 if (texture_file.empty()) {
165 std::filesystem::path filepath(texture_file);
168 if (filepath.is_absolute() && std::filesystem::exists(filepath)) {
174 if (!resolved_path.empty()) {
175 return resolved_path.string();
180 if (!resolved_path.empty()) {
181 return resolved_path.string();
185 if (texture_file.find(
"assets/") != 0) {
186 std::string filename = std::filesystem::path(texture_file).filename().string();
187 std::string extension = std::filesystem::path(texture_file).extension().string();
190 std::string subdirectory = (extension ==
".obj" || extension ==
".mtl") ?
"assets/obj/" :
"assets/textures/";
191 std::string assets_path = subdirectory + filename;
194 if (!resolved_path.empty()) {
195 return resolved_path.string();
200 helios_runtime_error(
"ERROR (PlantArchitecture): Could not resolve asset file: " + texture_file +
". Tried: direct path, plugin asset path, and assets/ subdirectory prefix.");
205 leaf_aspect_ratio.initialize(1.f, generator);
219 if (generator !=
nullptr) {
229 internode.pitch.initialize(20, generator);
230 internode.phyllotactic_angle.initialize(137.5, generator);
231 internode.radius_initial.initialize(0.001, generator);
237 petiole.petioles_per_internode = 1;
238 petiole.pitch.initialize(90, generator);
239 petiole.radius.initialize(0.001, generator);
240 petiole.length.initialize(0.05, generator);
241 petiole.curvature.initialize(0, generator);
242 petiole.taper.initialize(0, generator);
243 petiole.color = RGB::forestgreen;
245 petiole.radial_subdivisions = 7;
248 leaf.leaves_per_petiole.initialize(1, generator);
249 leaf.pitch.initialize(0, generator);
250 leaf.yaw.initialize(0, generator);
251 leaf.roll.initialize(0, generator);
252 leaf.leaflet_offset.initialize(0, generator);
253 leaf.leaflet_scale = 1;
254 leaf.prototype_scale.initialize(0.05, generator);
258 peduncle.length.initialize(0.05, generator);
259 peduncle.radius.initialize(0.001, generator);
260 peduncle.pitch.initialize(0, generator);
261 peduncle.roll.initialize(0, generator);
262 peduncle.curvature.initialize(0, generator);
263 petiole.color = RGB::forestgreen;
272 inflorescence.flower_prototype_scale.initialize(0.0075, generator);
273 inflorescence.fruit_prototype_scale.initialize(0.0075, generator);
274 inflorescence.fruit_gravity_factor_fraction.initialize(0, generator);
323 if (a_child_shoot_type_labels.size() != a_child_shoot_type_probabilities.size()) {
324 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Child shoot type labels and probabilities must be the same size.");
325 }
else if (a_child_shoot_type_labels.empty()) {
326 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Input argument vectors were empty.");
327 }
else if (
sum(a_child_shoot_type_probabilities) != 1.f) {
328 helios_runtime_error(
"ERROR (ShootParameters::defineChildShootTypes): Child shoot type probabilities must sum to 1.");
331 this->child_shoot_type_labels = a_child_shoot_type_labels;
332 this->child_shoot_type_probabilities = a_child_shoot_type_probabilities;
336 const std::map<std::string, float> &build_parameters) {
337 if (plant_count_xy.
x <= 0 || plant_count_xy.
y <= 0) {
338 helios_runtime_error(
"ERROR (PlantArchitecture::buildPlantCanopyFromLibrary): Plant count must be greater than zero.");
341 vec2 canopy_extent(plant_spacing_xy.
x *
float(plant_count_xy.
x - 1), plant_spacing_xy.
y *
float(plant_count_xy.
y - 1));
343 std::vector<uint> plantIDs;
344 plantIDs.reserve(plant_count_xy.
x * plant_count_xy.
y);
345 for (
int j = 0; j < plant_count_xy.
y; j++) {
346 for (
int i = 0; i < plant_count_xy.
x; i++) {
347 if (context_ptr->
randu() < germination_rate) {
348 plantIDs.push_back(
buildPlantInstanceFromLibrary(canopy_center_position +
make_vec3(-0.5f * canopy_extent.
x +
float(i) * plant_spacing_xy.
x, -0.5f * canopy_extent.
y +
float(j) * plant_spacing_xy.
y, 0), 0, build_parameters));
361 std::vector<uint> plantIDs;
362 plantIDs.reserve(plant_count);
363 for (
int i = 0; i < plant_count; i++) {
364 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);
373 if (this->shoot_types.find(shoot_type_label) != this->shoot_types.end()) {
375 this->shoot_types.at(shoot_type_label) = shoot_params;
377 this->shoot_types.emplace(shoot_type_label, shoot_params);
382 std::vector<vec3> nodes = parent_shoot_ptr->shoot_internode_vertices.at(
shoot_index.
x);
385 int s_minus = parent_shoot_ptr->shoot_internode_vertices.at(p_minus).size() - 1;
386 nodes.insert(nodes.begin(), parent_shoot_ptr->shoot_internode_vertices.at(p_minus).at(s_minus));
392 std::vector<float> node_radii = parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x);
395 int s_minus = parent_shoot_ptr->shoot_internode_radii.at(p_minus).size() - 1;
396 node_radii.insert(node_radii.begin(), parent_shoot_ptr->shoot_internode_radii.at(p_minus).at(s_minus));
413 if (petiole_index >= this->peduncle_vertices.size()) {
416 if (bud_index >= this->peduncle_vertices.at(petiole_index).size()) {
417 helios_runtime_error(
"ERROR (Phytomer::getPeduncleAxisVector): Floral bud index out of range.");
419 return getAxisVector(stem_fraction, this->peduncle_vertices.at(petiole_index).at(bud_index));
423 assert(stem_fraction >= 0 && stem_fraction <= 1);
426 float frac_plus, frac_minus;
427 if (stem_fraction + df <= 1) {
428 frac_minus = stem_fraction;
429 frac_plus = stem_fraction + df;
431 frac_minus = stem_fraction - df;
432 frac_plus = stem_fraction;
435 const vec3 node_minus = PlantArchitecture::interpolateTube(axis_vertices, frac_minus);
436 const vec3 node_plus = PlantArchitecture::interpolateTube(axis_vertices, frac_plus);
438 vec3 norm = node_plus - node_minus;
445 return parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x).front();
451 for (
int i = 0; i < node_vertices.size() - 1; i++) {
452 length += (node_vertices.at(i + 1) - node_vertices.at(i)).magnitude();
463 return PlantArchitecture::interpolateTube(parent_shoot_ptr->shoot_internode_radii.at(
shoot_index.
x), stem_fraction);
469 for (
auto &petiole: leaf_objIDs) {
470 for (
auto &leaf_objID: petiole) {
473 float scale_factor = current_leaf_scale_factor.at(p);
474 float scaled_area = obj_area /
powi(scale_factor, 2);
475 leaf_area += scaled_area;
485 if (petiole_index >= leaf_bases.size()) {
487 }
else if (leaf_index >= leaf_bases.at(petiole_index).size()) {
491 return leaf_bases.at(petiole_index).at(leaf_index);
495 for (
auto &petiole: axillary_vegetative_buds) {
496 for (
auto &bud: petiole) {
503 if (petiole_index >= axillary_vegetative_buds.size()) {
506 if (bud_index >= axillary_vegetative_buds.at(petiole_index).size()) {
517 for (
auto &petiole: floral_buds) {
518 for (
auto &fbud: petiole) {
519 if (!fbud.isterminal) {
527 if (petiole_index >= floral_buds.size()) {
530 if (bud_index >= floral_buds.at(petiole_index).size()) {
538 if (fbud.state == state) {
540 }
else if (state == BUD_DORMANT || state == BUD_ACTIVE) {
546 if (plantarchitecture_ptr->carbon_model_enabled) {
547 if (state == BUD_FLOWER_CLOSED || (fbud.state == BUD_ACTIVE && state == BUD_FLOWER_OPEN)) {
549 float flower_cost = calculateFlowerConstructionCosts(fbud);
550 plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC -= flower_cost;
551 }
else if (state == BUD_FRUITING) {
553 float fruit_cost = calculateFruitConstructionCosts(fbud);
554 fbud.previous_fruit_scale_factor = fbud.current_fruit_scale_factor;
555 if (plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC > fruit_cost) {
556 plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_tree.at(this->parent_shoot_ID)->carbohydrate_pool_molC -= fruit_cost;
565 fbud.inflorescence_objIDs.resize(0);
566 fbud.inflorescence_bases.resize(0);
567 fbud.inflorescence_rotation.resize(0);
568 fbud.inflorescence_base_scales.resize(0);
570 if (plantarchitecture_ptr->build_context_geometry_peduncle) {
572 fbud.peduncle_objIDs.resize(0);
577 if (state != BUD_DEAD) {
580 updateInflorescence(fbud);
581 fbud.time_counter = 0;
582 if (fbud.state == BUD_FRUITING) {
588helios::vec3 Phytomer::calculateCollisionAvoidanceDirection(
const helios::vec3 &internode_base_origin,
const helios::vec3 &internode_axis,
bool &collision_detection_active)
const {
589 vec3 collision_optimal_direction;
590 collision_detection_active =
false;
592 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
595 if (!plantarchitecture_ptr->bvh_cached_for_current_growth) {
596 if (plantarchitecture_ptr->printmessages) {
597 std::cout <<
"WARNING: BVH not cached - this indicates rebuildBVHForTimestep() was not called" << std::endl;
599 return collision_optimal_direction;
603 std::vector<uint> filtered_geometry;
607 float look_ahead_distance = plantarchitecture_ptr->collision_cone_height;
611 float max_relevant_distance = look_ahead_distance * 1.1f;
615 filtered_geometry = plantarchitecture_ptr->collision_detection_ptr->
filterGeometryByDistance(internode_base_origin, max_relevant_distance, plantarchitecture_ptr->cached_target_geometry);
619 plantarchitecture_ptr->cached_filtered_geometry = filtered_geometry;
621 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
623 vec3 apex = internode_base_origin;
624 vec3 central_axis = internode_axis;
626 float height = plantarchitecture_ptr->collision_cone_height;
627 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
628 int samples = plantarchitecture_ptr->collision_sample_count;
631 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
634 if (optimal_result.confidence > 0.0f) {
635 collision_optimal_direction = optimal_result.direction;
637 collision_detection_active =
true;
641 return collision_optimal_direction;
644helios::vec3 Phytomer::calculatePetioleCollisionAvoidanceDirection(
const helios::vec3 &petiole_base_origin,
const helios::vec3 &proposed_petiole_axis,
bool &collision_detection_active)
const {
645 vec3 collision_optimal_direction;
646 collision_detection_active =
false;
648 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
650 std::vector<uint> target_geometry;
651 if (!plantarchitecture_ptr->collision_target_UUIDs.empty()) {
652 target_geometry = plantarchitecture_ptr->collision_target_UUIDs;
653 }
else if (!plantarchitecture_ptr->collision_target_object_IDs.empty()) {
654 for (
uint objID: plantarchitecture_ptr->collision_target_object_IDs) {
656 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
664 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
666 vec3 apex = petiole_base_origin;
667 vec3 central_axis = proposed_petiole_axis;
669 float height = plantarchitecture_ptr->collision_cone_height;
670 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
671 int samples = plantarchitecture_ptr->collision_sample_count;
674 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
677 if (optimal_result.confidence > 0.0f) {
678 collision_optimal_direction = optimal_result.direction;
680 collision_detection_active =
true;
684 return collision_optimal_direction;
687helios::vec3 Phytomer::calculateFruitCollisionAvoidanceDirection(
const helios::vec3 &fruit_base_origin,
const helios::vec3 &proposed_fruit_axis,
bool &collision_detection_active)
const {
688 vec3 collision_optimal_direction;
689 collision_detection_active =
false;
692 if (plantarchitecture_ptr->collision_detection_enabled && plantarchitecture_ptr->collision_detection_ptr !=
nullptr) {
694 std::vector<uint> target_geometry;
695 if (!plantarchitecture_ptr->collision_target_UUIDs.empty()) {
696 target_geometry = plantarchitecture_ptr->collision_target_UUIDs;
697 }
else if (!plantarchitecture_ptr->collision_target_object_IDs.empty()) {
698 for (
uint objID: plantarchitecture_ptr->collision_target_object_IDs) {
700 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
708 if (plantarchitecture_ptr->bvh_cached_for_current_growth && !plantarchitecture_ptr->cached_filtered_geometry.empty()) {
710 vec3 apex = fruit_base_origin;
711 vec3 central_axis = proposed_fruit_axis;
713 float height = plantarchitecture_ptr->collision_cone_height;
714 float half_angle = plantarchitecture_ptr->collision_cone_half_angle_rad;
715 int samples = plantarchitecture_ptr->collision_sample_count;
718 auto optimal_result = plantarchitecture_ptr->collision_detection_ptr->
findOptimalConePath(apex, central_axis, half_angle, height, samples);
721 if (optimal_result.confidence > 0.0f) {
722 collision_optimal_direction = optimal_result.direction;
724 collision_detection_active =
true;
728 static int no_collision_count = 0;
729 if (optimal_result.confidence <= 0.0f) {
730 no_collision_count++;
733 static int no_bvh_count = 0;
737 return collision_optimal_direction;
740bool Phytomer::applySolidObstacleAvoidance(
const helios::vec3 ¤t_position,
helios::vec3 &internode_axis)
const {
741 if (!plantarchitecture_ptr->solid_obstacle_avoidance_enabled || plantarchitecture_ptr->solid_obstacle_UUIDs.empty()) {
751 vec3 growth_direction = internode_axis;
755 float nearest_obstacle_distance;
756 vec3 nearest_obstacle_direction;
759 float hard_detection_cone_angle =
deg2rad(20.0f);
760 float detection_distance = plantarchitecture_ptr->solid_obstacle_avoidance_distance;
762 if (plantarchitecture_ptr->collision_detection_ptr !=
nullptr && plantarchitecture_ptr->collision_detection_ptr->
findNearestSolidObstacleInCone(current_position, growth_direction, hard_detection_cone_angle, detection_distance,
763 plantarchitecture_ptr->solid_obstacle_UUIDs, nearest_obstacle_distance, nearest_obstacle_direction)) {
766 float buffer_distance = detection_distance * 0.05f;
769 float normalized_distance = nearest_obstacle_distance / detection_distance;
770 float buffer_threshold = buffer_distance / detection_distance;
772 vec3 avoidance_direction;
773 float rotation_fraction;
775 if (nearest_obstacle_distance <= buffer_distance) {
778 avoidance_direction = current_position - (current_position + nearest_obstacle_direction * nearest_obstacle_distance);
779 if (avoidance_direction.
magnitude() < 0.001f) {
781 avoidance_direction =
cross(growth_direction, nearest_obstacle_direction);
782 if (avoidance_direction.
magnitude() < 0.001f) {
783 avoidance_direction =
make_vec3(0, 0, 1);
789 rotation_fraction = 1.0f;
792 float buffer_blend_factor = 0.8f;
793 internode_axis = (1.0f - buffer_blend_factor) * growth_direction + buffer_blend_factor * avoidance_direction;
800 float dot_with_obstacle = normalize(growth_direction) * normalize(nearest_obstacle_direction);
801 float angle_deficit =
asin_safe(fabs(dot_with_obstacle));
804 vec3 rotation_axis =
cross(growth_direction, -nearest_obstacle_direction);
806 if (rotation_axis.
magnitude() > 0.001f) {
812 if (rotation_axis.
magnitude() > 0.001f) {
816 float surface_threshold_fraction = 0.2f;
818 if (normalized_distance <= surface_threshold_fraction) {
820 rotation_fraction = 1.0f;
823 float remaining_distance = normalized_distance - surface_threshold_fraction;
824 float max_remaining_distance = 1.0f - surface_threshold_fraction;
827 float distance_factor = remaining_distance / max_remaining_distance;
828 float min_rotation_fraction = 0.05f;
831 rotation_fraction = min_rotation_fraction + (1.0f - min_rotation_fraction) * exp(-3.0f * distance_factor);
835 float rotation_this_step = angle_deficit * rotation_fraction;
838 internode_axis =
rotatePointAboutLine(internode_axis, nullorigin, rotation_axis, rotation_this_step);
850 vec3 attraction_direction;
851 attraction_active =
false;
854 if (plantarchitecture_ptr->plant_instances.find(plantID) != plantarchitecture_ptr->plant_instances.end()) {
855 const auto &plant = plantarchitecture_ptr->plant_instances.at(plantID);
856 if (plant.attraction_points_enabled && !plant.attraction_points.empty()) {
858 vec3 look_direction = internode_axis;
860 float half_angle_degrees =
rad2deg(plant.attraction_cone_half_angle_rad);
861 float look_ahead_distance = plant.attraction_cone_height;
863 vec3 direction_to_closest;
864 if (plantarchitecture_ptr->
detectAttractionPointsInCone(plant.attraction_points, internode_base_origin, look_direction, look_ahead_distance, half_angle_degrees, direction_to_closest)) {
865 attraction_direction = direction_to_closest;
867 attraction_active =
true;
869 return attraction_direction;
874 if (!plantarchitecture_ptr->attraction_points_enabled || plantarchitecture_ptr->attraction_points.empty()) {
875 return attraction_direction;
879 vec3 look_direction = internode_axis;
881 float half_angle_degrees =
rad2deg(plantarchitecture_ptr->attraction_cone_half_angle_rad);
882 float look_ahead_distance = plantarchitecture_ptr->attraction_cone_height;
884 vec3 direction_to_closest;
885 if (plantarchitecture_ptr->
detectAttractionPointsInCone(plantarchitecture_ptr->attraction_points, internode_base_origin, look_direction, look_ahead_distance, half_angle_degrees, direction_to_closest)) {
886 attraction_direction = direction_to_closest;
888 attraction_active =
true;
891 return attraction_direction;
897 if (attraction_points.empty()) {
901 if (look_ahead_distance <= 0.0f) {
907 if (half_angle_degrees <= 0.0f || half_angle_degrees >= 180.0f) {
914 float half_angle_rad = half_angle_degrees *
M_PI / 180.0f;
917 vec3 axis = look_direction;
921 bool found_any =
false;
922 float min_angular_distance = std::numeric_limits<float>::max();
926 for (
const vec3 &point: attraction_points) {
928 vec3 to_point = point - vertex;
929 float distance_to_point = to_point.
magnitude();
932 if (distance_to_point < 1e-6f || distance_to_point > look_ahead_distance) {
937 vec3 direction_to_point = to_point;
941 float cos_angle = axis * direction_to_point;
944 cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
946 float angle = std::acos(cos_angle);
949 if (angle <= half_angle_rad) {
953 if (angle < min_angular_distance) {
954 min_angular_distance = angle;
955 closest_point = point;
962 direction_to_closest = closest_point - vertex;
974 if (attraction_points_input.empty()) {
978 if (look_ahead_distance <= 0.0f) {
984 if (half_angle_degrees <= 0.0f || half_angle_degrees >= 180.0f) {
991 float half_angle_rad = half_angle_degrees *
M_PI / 180.0f;
994 vec3 axis = look_direction;
998 bool found_any =
false;
999 float min_angular_distance = std::numeric_limits<float>::max();
1003 for (
const vec3 &point: attraction_points_input) {
1005 vec3 to_point = point - vertex;
1006 float distance_to_point = to_point.
magnitude();
1009 if (distance_to_point <= 1e-6 || distance_to_point > look_ahead_distance) {
1014 vec3 direction_to_point = to_point;
1018 float cos_angle = axis * direction_to_point;
1021 cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
1023 float angle = std::acos(cos_angle);
1026 if (angle <= half_angle_rad) {
1030 if (angle < min_angular_distance) {
1031 min_angular_distance = angle;
1032 closest_point = point;
1039 direction_to_closest = closest_point - vertex;
1048 auto shoot_tree_ptr = &plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree;
1051 vec3 parent_internode_axis;
1052 vec3 parent_petiole_axis;
1053 vec3 internode_base_position;
1054 if (phytomers.empty()) {
1056 if (parent_shoot_ID == -1) {
1058 parent_internode_axis =
make_vec3(0, 0, 1);
1059 parent_petiole_axis =
make_vec3(0, -1, 0);
1062 assert(parent_shoot_ID < shoot_tree_ptr->size() && parent_node_index < shoot_tree_ptr->at(parent_shoot_ID)->phytomers.size());
1063 parent_internode_axis = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
1065 if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
1066 parent_petiole_axis =
cross(parent_internode_axis,
make_vec3(0, 0, 1));
1067 if (parent_petiole_axis.
magnitude() < 0.01f) {
1069 parent_petiole_axis =
make_vec3(0, 1, 0);
1073 float phyllotactic_angle = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->internode_phyllotactic_angle;
1074 float cumulative_rotation = float(parent_node_index) * phyllotactic_angle;
1077 parent_petiole_axis = shoot_tree_ptr->at(parent_shoot_ID)->phytomers.at(parent_node_index)->getPetioleAxisVector(0.f, parent_petiole_index);
1080 internode_base_position = base_position;
1083 parent_internode_axis = phytomers.back()->getInternodeAxisVector(1.f);
1085 if (phytomers.back()->petiole_vertices.empty()) {
1086 parent_petiole_axis =
cross(parent_internode_axis,
make_vec3(0, 0, 1));
1087 if (parent_petiole_axis.
magnitude() < 0.01f) {
1089 parent_petiole_axis =
make_vec3(0, 1, 0);
1093 uint prev_phytomer_index = phytomers.size() - 1;
1094 float phyllotactic_angle = phytomers.back()->internode_phyllotactic_angle;
1095 float cumulative_rotation = float(prev_phytomer_index) * phyllotactic_angle;
1098 parent_petiole_axis = phytomers.back()->getPetioleAxisVector(0.f, 0);
1100 internode_base_position = shoot_internode_vertices.back().back();
1103 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,
1104 internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, rank, plantarchitecture_ptr, context_ptr);
1105 shoot_tree_ptr->at(ID)->phytomers.push_back(phytomer);
1106 phytomer = shoot_tree_ptr->at(ID)->phytomers.back();
1109 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1112 for (
auto &vbud: petiole) {
1113 phytomer->setVegetativeBudState(BUD_DORMANT, vbud);
1114 vbud.shoot_type_label = child_shoot_type_label;
1118 if (plantarchitecture_ptr->carbon_model_enabled) {
1119 if (sampleVegetativeBudBreak_carb(phytomer->shoot_index.x)) {
1121 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1123 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1128 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1130 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1138 uint petiole_index = 0;
1139 for (
auto &petiole: phytomer->floral_buds) {
1141 for (
auto &fbud: petiole) {
1143 phytomer->setFloralBudState(BUD_DORMANT, fbud);
1147 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1150 fbud.parent_index = petiole_index;
1151 fbud.bud_index = bud_index;
1163 if (plantarchitecture_ptr->build_context_geometry_internode && context_ptr->
doesObjectExist(internode_tube_objID)) {
1165 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1166 context_ptr->
setObjectData(internode_tube_objID,
"age", phytomer->age);
1168 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1169 context_ptr->
setObjectData(internode_tube_objID,
"rank", rank);
1171 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1172 context_ptr->
setObjectData(internode_tube_objID,
"plantID", (
int) plantID);
1174 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1175 context_ptr->
setObjectData(internode_tube_objID,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1178 if (plantarchitecture_ptr->build_context_geometry_petiole) {
1179 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1180 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"age", phytomer->age);
1182 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1183 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"rank", phytomer->rank);
1185 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1186 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"plantID", (
int) plantID);
1188 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1189 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1192 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
1193 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"age", phytomer->age);
1195 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
1196 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"rank", phytomer->rank);
1198 if (plantarchitecture_ptr->output_object_data.at(
"plantID")) {
1199 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"plantID", (
int) plantID);
1201 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
1202 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
1205 if (plantarchitecture_ptr->output_object_data.at(
"leafID")) {
1206 for (
auto &petiole: phytomer->leaf_objIDs) {
1207 for (
uint objID: petiole) {
1214 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);
1218 if (plantarchitecture_ptr->carbon_model_enabled) {
1219 this->carbohydrate_pool_molC -= phytomer->calculatePhytomerConstructionCosts();
1222 return (
int) phytomers.size() - 1;
1225void Shoot::breakDormancy() {
1228 int phytomer_ind = 0;
1229 for (
auto &phytomer: phytomers) {
1230 for (
auto &petiole: phytomer->floral_buds) {
1231 for (
auto &fbud: petiole) {
1232 if (fbud.state != BUD_DEAD) {
1233 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1235 if (meristem_is_alive && fbud.isterminal) {
1236 phytomer->setFloralBudState(BUD_ACTIVE, fbud);
1238 fbud.time_counter = 0;
1241 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1242 for (
auto &vbud: petiole) {
1243 if (vbud.state != BUD_DEAD) {
1244 if (plantarchitecture_ptr->carbon_model_enabled) {
1245 if (sampleVegetativeBudBreak_carb(phytomer_ind)) {
1247 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1249 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1254 phytomer->setVegetativeBudState(BUD_ACTIVE, vbud);
1256 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1263 phytomer->isdormant =
false;
1268void Shoot::makeDormant() {
1271 nodes_this_season = 0;
1273 for (
auto &phytomer: phytomers) {
1274 for (
auto &petiole: phytomer->floral_buds) {
1276 for (
auto &fbud: petiole) {
1277 if (fbud.state != BUD_DORMANT) {
1278 phytomer->setFloralBudState(BUD_DEAD, fbud);
1282 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1283 for (
auto &vbud: petiole) {
1284 if (vbud.state != BUD_DORMANT) {
1285 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1289 if (!plantarchitecture_ptr->plant_instances.at(plantID).is_evergreen) {
1290 phytomer->removeLeaf();
1292 phytomer->isdormant =
true;
1301 this->meristem_is_alive =
false;
1302 this->phyllochron_counter = 0;
1306 for (
auto &phytomer: phytomers) {
1307 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
1308 for (
auto &vbud: petiole) {
1309 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
1317 for (
int bud = 0; bud < Nbuds; bud++) {
1319 bud_new.isterminal =
true;
1320 bud_new.parent_index = 0;
1321 bud_new.bud_index = bud;
1322 bud_new.base_position = shoot_internode_vertices.back().back();
1323 float pitch_adjustment = 0;
1325 pitch_adjustment =
deg2rad(30);
1327 float yaw_adjustment =
static_cast<float>(bud_new.bud_index) * 2.f *
PI_F /
float(Nbuds);
1329 bud_new.base_rotation = make_AxisRotation(pitch_adjustment, yaw_adjustment, 0);
1330 bud_new.bending_axis =
make_vec3(1, 0, 0);
1332 phytomers.back()->floral_buds.push_back({bud_new});
1339 float shoot_volume = 0;
1340 for (
const auto &phytomer: phytomers) {
1345 return shoot_volume;
1349 float shoot_length = 0;
1350 for (
const auto &phytomer: phytomers) {
1351 shoot_length += phytomer->getInternodeLength();
1353 return shoot_length;
1358 if (parent_shoot_ID >= 0) {
1361 auto parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
1363 const vec3 current_origin = shoot_internode_vertices.front().front();
1364 const vec3 updated_origin = parent_shoot->shoot_internode_vertices.at(this->parent_node_index).back();
1365 vec3 shift = updated_origin - current_origin;
1371 for (
auto &phytomer: shoot_internode_vertices) {
1372 for (
vec3 &node: phytomer) {
1379 if (update_context_geometry && plantarchitecture_ptr->build_context_geometry_internode && context_ptr->
doesObjectExist(internode_tube_objID)) {
1385 for (
int p = 0; p < phytomers.size(); p++) {
1386 vec3 petiole_base = shoot_internode_vertices.at(p).back();
1387 if (parent_shoot_ID >= 0) {
1389 auto parent_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
1392 phytomers.at(p)->setPetioleBase(petiole_base);
1396 for (
const auto &node: childIDs) {
1397 for (
int child_shoot_ID: node.second) {
1398 plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->updateShootNodes(update_context_geometry);
1404 uint phytomer_count = this->phytomers.size();
1406 uint phytomer_index = 0;
1407 if (shoot_fraction > 0) {
1408 phytomer_index = std::ceil(shoot_fraction *
float(phytomer_count)) - 1;
1411 assert(phytomer_index < phytomer_count);
1413 return this->phytomers.at(phytomer_index)->getInternodeAxisVector(0.5);
1417 for (
int i = node_index; i >= 0; i--) {
1418 shoot->phytomers.at(i)->downstream_leaf_area += leaf_area;
1419 shoot->phytomers.at(i)->downstream_leaf_area = std::max(0.f, shoot->phytomers.at(i)->downstream_leaf_area);
1422 if (shoot->parent_shoot_ID >= 0) {
1423 Shoot *parent_shoot = plantarchitecture_ptr->plant_instances.at(shoot->plantID).shoot_tree.at(shoot->parent_shoot_ID).get();
1430 if (start_node_index >= phytomers.size()) {
1436 for (
uint p = start_node_index; p < phytomers.size(); p++) {
1438 auto phytomer = phytomers.at(p);
1439 for (
auto &petiole: phytomer->leaf_objIDs) {
1440 for (
uint objID: petiole) {
1448 if (childIDs.find(p) != childIDs.end()) {
1449 for (
int child_shoot_ID: childIDs.at(p)) {
1450 area += plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->sumShootLeafArea(0);
1460 if (start_node_index >= phytomers.size()) {
1466 for (
uint p = start_node_index; p < phytomers.size(); p++) {
1468 if (childIDs.find(p) != childIDs.end()) {
1469 for (
int child_shoot_ID: childIDs.at(p)) {
1470 volume += plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
1479 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,
1480 helios::Context *context_ptr) : rank(rank), context_ptr(context_ptr), plantarchitecture_ptr(plantarchitecture_ptr) {
1481 this->phytomer_parameters = params;
1484 ShootParameters parent_shoot_parameters = parent_shoot->shoot_parameters;
1486 this->internode_radius_initial = internode_radius;
1487 this->internode_length_max = internode_length_max;
1490 this->rank = parent_shoot->rank;
1491 this->plantID = parent_shoot->plantID;
1492 this->parent_shoot_ID = parent_shoot->ID;
1493 this->parent_shoot_ptr = parent_shoot;
1495 bool build_context_geometry_internode = plantarchitecture_ptr->build_context_geometry_internode;
1496 bool build_context_geometry_petiole = plantarchitecture_ptr->build_context_geometry_petiole;
1497 bool build_context_geometry_peduncle = plantarchitecture_ptr->build_context_geometry_peduncle;
1505 uint Ndiv_internode_length = std::max(
uint(1), phytomer_parameters.
internode.length_segments);
1506 uint Ndiv_internode_radius = std::max(
uint(3), phytomer_parameters.
internode.radial_subdivisions);
1507 uint Ndiv_petiole_length = std::max(
uint(1), phytomer_parameters.
petiole.length_segments);
1508 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
1511 if (phytomer_parameters.
internode.length_segments == 0 || phytomer_parameters.
internode.radial_subdivisions < 3) {
1512 build_context_geometry_internode =
false;
1514 if (phytomer_parameters.
petiole.length_segments == 0 || phytomer_parameters.
petiole.radial_subdivisions < 3) {
1515 build_context_geometry_petiole =
false;
1518 if (phytomer_parameters.
petiole.petioles_per_internode == 0) {
1520 build_context_geometry_petiole =
false;
1521 phytomer_parameters.
leaf.leaves_per_petiole = 0;
1524 if (phytomer_parameters.
petiole.petioles_per_internode < 0) {
1525 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Number of petioles per internode cannot be negative.");
1528 current_internode_scale_factor = internode_length_scale_factor_fraction;
1529 current_leaf_scale_factor.resize(phytomer_parameters.
petiole.petioles_per_internode);
1530 std::fill(current_leaf_scale_factor.begin(), current_leaf_scale_factor.end(), leaf_scale_factor_fraction);
1532 if (internode_radius == 0.f) {
1533 internode_radius = MIN_TUBE_RADIUS_FOR_GEOMETRY;
1537 float internode_length = internode_length_scale_factor_fraction * internode_length_max;
1538 float dr_internode = internode_length / float(phytomer_parameters.
internode.length_segments);
1539 float dr_internode_max = internode_length_max / float(phytomer_parameters.
internode.length_segments);
1540 std::vector<vec3> phytomer_internode_vertices;
1541 std::vector<float> phytomer_internode_radii;
1542 phytomer_internode_vertices.resize(Ndiv_internode_length + 1);
1543 phytomer_internode_vertices.at(0) = internode_base_origin;
1544 phytomer_internode_radii.resize(Ndiv_internode_length + 1);
1545 phytomer_internode_radii.at(0) = internode_radius;
1547 phytomer_parameters.
internode.pitch.resample();
1548 internode_phyllotactic_angle =
deg2rad(phytomer_parameters.
internode.phyllotactic_angle.val());
1549 phytomer_parameters.
internode.phyllotactic_angle.resample();
1552 petiole_length.resize(phytomer_parameters.
petiole.petioles_per_internode);
1554 petiole_radii.resize(phytomer_parameters.
petiole.petioles_per_internode);
1557 peduncle_vertices.resize(phytomer_parameters.
petiole.petioles_per_internode);
1558 peduncle_radii.resize(phytomer_parameters.
petiole.petioles_per_internode);
1559 peduncle_length.resize(phytomer_parameters.
petiole.petioles_per_internode);
1560 peduncle_radius.resize(phytomer_parameters.
petiole.petioles_per_internode);
1561 peduncle_pitch.resize(phytomer_parameters.
petiole.petioles_per_internode);
1562 peduncle_curvature.resize(phytomer_parameters.
petiole.petioles_per_internode);
1563 petiole_pitch.resize(phytomer_parameters.
petiole.petioles_per_internode);
1564 petiole_curvature.resize(phytomer_parameters.
petiole.petioles_per_internode);
1565 petiole_taper.resize(phytomer_parameters.
petiole.petioles_per_internode);
1566 petiole_axis_initial.resize(phytomer_parameters.
petiole.petioles_per_internode);
1567 petiole_rotation_axis.resize(phytomer_parameters.
petiole.petioles_per_internode);
1568 std::vector<float> dr_petiole(phytomer_parameters.
petiole.petioles_per_internode);
1569 std::vector<float> dr_petiole_max(phytomer_parameters.
petiole.petioles_per_internode);
1571 std::vector<bool> suppress_petiole_geometry(phytomer_parameters.
petiole.petioles_per_internode,
false);
1572 for (
int p = 0; p < phytomer_parameters.
petiole.petioles_per_internode; p++) {
1574 petiole_radii.at(p).resize(Ndiv_petiole_length + 1);
1576 suppress_petiole_geometry.at(p) = (phytomer_parameters.
petiole.radius.val() <= 0.f || phytomer_parameters.
petiole.length.val() <= 0.f);
1578 petiole_length.at(p) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.length.val();
1579 if (petiole_length.at(p) <= 0.f) {
1580 petiole_length.at(p) = MIN_TUBE_LENGTH_FOR_GEOMETRY;
1582 dr_petiole.at(p) = petiole_length.at(p) / float(phytomer_parameters.
petiole.length_segments);
1583 dr_petiole_max.at(p) = phytomer_parameters.
petiole.length.val() / float(phytomer_parameters.
petiole.length_segments);
1585 petiole_radii.at(p).at(0) = leaf_scale_factor_fraction * phytomer_parameters.
petiole.radius.val();
1586 if (petiole_radii.at(p).at(0) <= 0.f) {
1587 petiole_radii.at(p).at(0) = MIN_TUBE_RADIUS_FOR_GEOMETRY;
1590 phytomer_parameters.
petiole.length.resample();
1592 petiole_objIDs.resize(phytomer_parameters.
petiole.petioles_per_internode);
1595 leaf_bases.resize(phytomer_parameters.
petiole.petioles_per_internode);
1596 leaf_objIDs.resize(phytomer_parameters.
petiole.petioles_per_internode);
1597 leaf_size_max.resize(phytomer_parameters.
petiole.petioles_per_internode);
1598 leaf_rotation.resize(phytomer_parameters.
petiole.petioles_per_internode);
1599 int leaves_per_petiole = phytomer_parameters.
leaf.leaves_per_petiole.val();
1600 float leaflet_offset_val = clampOffset(leaves_per_petiole, phytomer_parameters.
leaf.leaflet_offset.val());
1601 phytomer_parameters.
leaf.leaves_per_petiole.resample();
1602 for (
uint petiole = 0; petiole < phytomer_parameters.
petiole.petioles_per_internode; petiole++) {
1603 leaf_size_max.at(petiole).resize(leaves_per_petiole);
1604 leaf_rotation.at(petiole).resize(leaves_per_petiole);
1607 internode_colors.resize(Ndiv_internode_length + 1);
1608 internode_colors.at(0) = phytomer_parameters.
internode.color;
1609 petiole_colors.resize(Ndiv_petiole_length + 1);
1610 petiole_colors.at(0) = phytomer_parameters.
petiole.color;
1612 vec3 internode_axis = parent_internode_axis;
1614 vec3 petiole_rotation_axis =
cross(parent_internode_axis, parent_petiole_axis);
1615 if (petiole_rotation_axis ==
make_vec3(0, 0, 0)) {
1616 petiole_rotation_axis =
make_vec3(1, 0, 0);
1620 if (phytomer_index == 0) {
1623 if (phytomer_index == 0) {
1626 if (internode_pitch != 0.f) {
1627 if (phytomer_index == 0) {
1630 if (phytomer_index == 0) {
1634 float roll_nudge = 0.f;
1640 if (phytomer_index == 0) {
1643 if (shoot_base_rotation.roll != 0.f || roll_nudge != 0.f) {
1644 if (phytomer_index == 0) {
1649 if (phytomer_index == 0) {
1653 vec3 base_pitch_axis = -1 *
cross(parent_internode_axis, parent_petiole_axis);
1656 if (shoot_base_rotation.pitch != 0.f) {
1657 if (phytomer_index == 0) {
1661 if (phytomer_index == 0) {
1666 if (shoot_base_rotation.yaw != 0) {
1667 if (phytomer_index == 0) {
1671 if (phytomer_index == 0) {
1683 if (internode_pitch != 0) {
1691 if (internode_axis ==
make_vec3(0, 0, 1)) {
1692 shoot_bending_axis =
make_vec3(0, 1, 0);
1696 vec3 collision_optimal_direction;
1697 bool collision_detection_active =
false;
1698 vec3 attraction_direction;
1699 bool attraction_active =
false;
1700 bool obstacle_found =
false;
1703 collision_optimal_direction = calculateCollisionAvoidanceDirection(internode_base_origin, internode_axis, collision_detection_active);
1706 attraction_direction = calculateAttractionPointDirection(internode_base_origin, internode_axis, attraction_active);
1715 for (
int inode_segment = 1; inode_segment <= Ndiv_internode_length; inode_segment++) {
1717 if ((fabs(parent_shoot->gravitropic_curvature) > 0 || parent_shoot_parameters.
tortuosity.val() > 0) &&
shoot_index.
x > 0) {
1720 float current_curvature_fact = 0.5f - internode_axis.
z / 2.f;
1721 if (internode_axis.
z < 0) {
1722 current_curvature_fact *= 2.f;
1725 float dt = dr_internode_max / float(Ndiv_internode_length);
1727 parent_shoot->curvature_perturbation += -0.5f * parent_shoot->curvature_perturbation * dt + parent_shoot_parameters.
tortuosity.val() * context_ptr->
randn() * sqrt(dt);
1729 float curvature_angle =
deg2rad((parent_shoot->gravitropic_curvature * current_curvature_fact * dr_internode_max + parent_shoot->curvature_perturbation));
1732 parent_shoot->yaw_perturbation += -0.5f * parent_shoot->yaw_perturbation * dt + parent_shoot_parameters.
tortuosity.val() * context_ptr->
randn() * sqrt(dt);
1734 float yaw_angle =
deg2rad((parent_shoot->yaw_perturbation));
1739 vec3 current_position = phytomer_internode_vertices.at(inode_segment - 1);
1740 obstacle_found = applySolidObstacleAvoidance(current_position, internode_axis);
1745 vec3 final_direction = internode_axis;
1747 if (attraction_active) {
1749 float attraction_weight = plantarchitecture_ptr->attraction_weight;
1751 if (obstacle_found) {
1754 attraction_weight *= plantarchitecture_ptr->attraction_obstacle_reduction_factor;
1758 final_direction = (1.0f - attraction_weight) * final_direction + attraction_weight * attraction_direction;
1762 plantarchitecture_ptr->collision_avoidance_applied =
true;
1764 }
else if (collision_detection_active && !obstacle_found) {
1766 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
1769 final_direction = inertia_weight * final_direction + (1.0f - inertia_weight) * collision_optimal_direction;
1773 plantarchitecture_ptr->collision_avoidance_applied =
true;
1776 if (obstacle_found) {
1778 plantarchitecture_ptr->collision_avoidance_applied =
true;
1782 internode_axis = final_direction;
1798 phytomer_internode_vertices.at(inode_segment) = phytomer_internode_vertices.at(inode_segment - 1) + dr_internode * internode_axis;
1800 phytomer_internode_radii.at(inode_segment) = internode_radius;
1801 internode_colors.at(inode_segment) = phytomer_parameters.
internode.color;
1806 parent_shoot_ptr->shoot_internode_vertices.push_back(phytomer_internode_vertices);
1807 parent_shoot_ptr->shoot_internode_radii.push_back(phytomer_internode_radii);
1810 parent_shoot_ptr->shoot_internode_vertices.emplace_back(phytomer_internode_vertices.begin() + 1, phytomer_internode_vertices.end());
1811 parent_shoot_ptr->shoot_internode_radii.emplace_back(phytomer_internode_radii.begin() + 1, phytomer_internode_radii.end());
1815 if (build_context_geometry_internode) {
1817 float texture_repeat_length = 0.25f;
1819 for (
auto &phytomer: parent_shoot_ptr->phytomers) {
1820 length += phytomer->internode_length_max;
1822 std::vector<float> uv_y(phytomer_internode_vertices.size());
1823 float dy = internode_length_max / float(uv_y.size() - 1);
1824 for (
int j = 0; j < uv_y.size(); j++) {
1825 uv_y.at(j) = (length + j * dy) / texture_repeat_length - std::floor((length + j * dy) / texture_repeat_length);
1831 if (!context_ptr->
doesObjectExist(parent_shoot->internode_tube_objID)) {
1833 if (!resolved_internode_texture.empty()) {
1834 parent_shoot->internode_tube_objID = context_ptr->
addTubeObject(Ndiv_internode_radius, phytomer_internode_vertices, phytomer_internode_radii, resolved_internode_texture.c_str(), uv_y);
1836 parent_shoot->internode_tube_objID = context_ptr->
addTubeObject(Ndiv_internode_radius, phytomer_internode_vertices, phytomer_internode_radii, internode_colors);
1839 std::string stem_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot->shoot_type_label +
"_stem";
1840 renameAutoMaterial(context_ptr, parent_shoot->internode_tube_objID, stem_material_name);
1843 for (
int inode_segment = 1; inode_segment <= Ndiv_internode_length; inode_segment++) {
1844 if (!resolved_internode_texture.empty()) {
1845 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(),
1846 {uv_y.at(inode_segment - 1), uv_y.at(inode_segment)});
1848 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));
1857 for (
int petiole = 0; petiole < phytomer_parameters.
petiole.petioles_per_internode; petiole++) {
1860 vec3 petiole_axis = internode_axis;
1867 petiole_pitch.at(petiole) =
deg2rad(5.f);
1870 petiole_pitch.at(petiole) =
deg2rad(phytomer_parameters.
petiole.pitch.val());
1871 phytomer_parameters.
petiole.pitch.resample();
1872 if (fabs(petiole_pitch.at(petiole)) <
deg2rad(5.f)) {
1873 petiole_pitch.at(petiole) =
deg2rad(5.f);
1879 if (phytomer_index != 0 && internode_phyllotactic_angle != 0) {
1886 petiole_curvature.at(petiole) = phytomer_parameters.
petiole.curvature.val();
1887 phytomer_parameters.
petiole.curvature.resample();
1889 vec3 petiole_rotation_axis_actual = petiole_rotation_axis;
1890 vec3 petiole_axis_actual = petiole_axis;
1893 float budrot = float(petiole) * 2.f *
PI_F / float(phytomer_parameters.
petiole.petioles_per_internode);
1899 this->petiole_axis_initial.at(petiole) = petiole_axis_actual;
1900 this->petiole_rotation_axis.at(petiole) = petiole_rotation_axis_actual;
1903 vec3 collision_optimal_petiole_direction;
1904 bool petiole_collision_active =
false;
1906 if (plantarchitecture_ptr->petiole_collision_detection_enabled) {
1907 collision_optimal_petiole_direction = calculatePetioleCollisionAvoidanceDirection(phytomer_internode_vertices.back(),
1908 petiole_axis_actual, petiole_collision_active);
1911 if (petiole_collision_active) {
1912 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
1913 vec3 natural_petiole_direction = petiole_axis_actual;
1918 petiole_axis_actual = inertia_weight * natural_petiole_direction + (1.0f - inertia_weight) * collision_optimal_petiole_direction;
1923 vec3 bending_direction = collision_optimal_petiole_direction - (collision_optimal_petiole_direction * natural_petiole_direction) * natural_petiole_direction;
1925 if (bending_direction.
magnitude() > 1e-6f) {
1930 vec3 curvature_axis =
cross(natural_petiole_direction, bending_direction);
1932 if (curvature_axis.
magnitude() > 1e-6f) {
1936 float angular_deviation = acosf(std::max(-1.0f, std::min(1.0f, collision_optimal_petiole_direction * natural_petiole_direction)));
1939 float desired_curvature_deg =
rad2deg(angular_deviation) * (1.0f - inertia_weight);
1942 float curvature_sign = (curvature_axis * petiole_rotation_axis_actual > 0) ? 1.0f : -1.0f;
1945 petiole_curvature.at(petiole) += curvature_sign * desired_curvature_deg * 0.5f;
1951 petiole_taper.at(petiole) = phytomer_parameters.
petiole.taper.val();
1952 phytomer_parameters.
petiole.taper.resample();
1956 for (
int j = 1; j <= Ndiv_petiole_length; j++) {
1957 if (fabs(petiole_curvature.at(petiole)) > 0) {
1963 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));
1964 petiole_colors.at(j) = phytomer_parameters.
petiole.color;
1967 assert(!std::isnan(petiole_radii.at(petiole).at(j)) && std::isfinite(petiole_radii.at(petiole).at(j)));
1970 if (build_context_geometry_petiole && !suppress_petiole_geometry.at(petiole)) {
1972 if (!petiole_objIDs.at(petiole).empty()) {
1974 std::string petiole_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot->shoot_type_label +
"_petiole";
1975 renameAutoMaterial(context_ptr, petiole_objIDs.at(petiole), petiole_material_name);
1981 std::vector<VegetativeBud> vegetative_buds_new;
1982 vegetative_buds_new.resize(phytomer_parameters.
internode.max_vegetative_buds_per_petiole.val());
1983 phytomer_parameters.
internode.max_vegetative_buds_per_petiole.resample();
1985 axillary_vegetative_buds.push_back(vegetative_buds_new);
1987 std::vector<FloralBud> floral_buds_new;
1988 floral_buds_new.resize(phytomer_parameters.
internode.max_floral_buds_per_petiole.val());
1989 phytomer_parameters.
internode.max_floral_buds_per_petiole.resample();
1992 for (
auto &fbud: floral_buds_new) {
1993 fbud.bud_index = index;
1994 fbud.parent_index = petiole;
1995 float pitch_adjustment = fbud.bud_index * 0.1f *
PI_F / float(axillary_vegetative_buds.size());
1996 float yaw_adjustment = -0.25f *
PI_F + fbud.bud_index * 0.5f *
PI_F / float(axillary_vegetative_buds.size());
1997 fbud.base_rotation = make_AxisRotation(pitch_adjustment, yaw_adjustment, 0);
1998 fbud.base_position = phytomer_internode_vertices.back();
1999 fbud.bending_axis = shoot_bending_axis;
2003 floral_buds.push_back(floral_buds_new);
2007 if (phytomer_parameters.
leaf.prototype.prototype_function ==
nullptr) {
2008 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Leaf prototype function was not defined for shoot type " + parent_shoot->shoot_type_label +
".");
2014 assert(phytomer_parameters.
leaf.prototype.unique_prototype_identifier != 0);
2015 if (phytomer_parameters.
leaf.prototype.unique_prototypes > 0 &&
2016 plantarchitecture_ptr->unique_leaf_prototype_objIDs.find(phytomer_parameters.
leaf.prototype.unique_prototype_identifier) == plantarchitecture_ptr->unique_leaf_prototype_objIDs.end()) {
2017 plantarchitecture_ptr->unique_leaf_prototype_objIDs[phytomer_parameters.
leaf.prototype.unique_prototype_identifier].resize(phytomer_parameters.
leaf.prototype.unique_prototypes);
2018 for (
int prototype = 0; prototype < phytomer_parameters.
leaf.prototype.unique_prototypes; prototype++) {
2019 for (
int leaf = 0; leaf < leaves_per_petiole; leaf++) {
2020 float ind_from_tip = float(leaf) - float(leaves_per_petiole - 1) / 2.f;
2021 uint objID_leaf = phytomer_parameters.
leaf.prototype.prototype_function(context_ptr, &phytomer_parameters.
leaf.prototype, ind_from_tip);
2022 if (phytomer_parameters.
leaf.prototype.prototype_function == GenericLeafPrototype) {
2025 plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).push_back(objID_leaf);
2026 std::string material_base_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot->shoot_type_label +
"_leaf";
2027 renameAutoMaterial(context_ptr, objID_leaf, material_base_name);
2035 for (
int leaf = 0; leaf < leaves_per_petiole; leaf++) {
2036 float ind_from_tip = float(leaf) - float(leaves_per_petiole - 1) / 2.f;
2039 if (phytomer_parameters.
leaf.prototype.unique_prototypes > 0) {
2041 int prototype = context_ptr->
randu(0, phytomer_parameters.
leaf.prototype.unique_prototypes - 1);
2042 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.find(phytomer_parameters.
leaf.prototype.unique_prototype_identifier) != plantarchitecture_ptr->unique_leaf_prototype_objIDs.end());
2043 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).size() > prototype);
2044 assert(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).size() > leaf);
2045 objID_leaf = context_ptr->
copyObject(plantarchitecture_ptr->unique_leaf_prototype_objIDs.at(phytomer_parameters.
leaf.prototype.unique_prototype_identifier).at(prototype).at(leaf));
2048 objID_leaf = phytomer_parameters.
leaf.prototype.prototype_function(context_ptr, &phytomer_parameters.
leaf.prototype, ind_from_tip);
2049 std::string material_base_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot->shoot_type_label +
"_leaf";
2050 renameAutoMaterial(context_ptr, objID_leaf, material_base_name);
2055 if (leaves_per_petiole > 0 && phytomer_parameters.
leaf.leaflet_scale.val() != 1.f && ind_from_tip != 0) {
2056 leaf_size_max.at(petiole).at(leaf) = powf(phytomer_parameters.
leaf.leaflet_scale.val(), fabs(ind_from_tip)) * phytomer_parameters.
leaf.prototype_scale.val();
2058 leaf_size_max.at(petiole).at(leaf) = phytomer_parameters.
leaf.prototype_scale.val();
2060 vec3 leaf_scale = leaf_scale_factor_fraction * leaf_size_max.at(petiole).at(leaf) *
make_vec3(1, 1, 1);
2064 float compound_rotation = 0;
2065 if (leaves_per_petiole > 1) {
2066 if (leaflet_offset_val == 0) {
2067 float dphi =
PI_F / (floor(0.5 *
float(leaves_per_petiole - 1)) + 1);
2068 compound_rotation = -float(
PI_F) + dphi * (leaf + 0.5f);
2070 if (leaf ==
float(leaves_per_petiole - 1) / 2.f) {
2072 compound_rotation = 0;
2073 }
else if (leaf <
float(leaves_per_petiole - 1) / 2.f) {
2074 compound_rotation = -0.5 *
PI_F;
2076 compound_rotation = 0.5 *
PI_F;
2089 if (leaves_per_petiole == 1) {
2091 roll_rot = -
deg2rad(phytomer_parameters.
leaf.roll.val()) * sign;
2092 }
else if (ind_from_tip != 0) {
2093 roll_rot = (
asin_safe(petiole_tip_axis.
z) +
deg2rad(phytomer_parameters.
leaf.roll.val())) * compound_rotation / std::fabs(compound_rotation);
2095 leaf_rotation.at(petiole).at(leaf).roll =
deg2rad(phytomer_parameters.
leaf.roll.val());
2096 phytomer_parameters.
leaf.roll.resample();
2100 leaf_rotation.at(petiole).at(leaf).pitch =
deg2rad(phytomer_parameters.
leaf.pitch.val());
2101 float pitch_rot = leaf_rotation.at(petiole).at(leaf).pitch;
2102 phytomer_parameters.
leaf.pitch.resample();
2103 if (ind_from_tip == 0) {
2106 context_ptr->
rotateObject(objID_leaf, -pitch_rot,
"y");
2109 if (ind_from_tip != 0) {
2110 float sign = -compound_rotation / fabs(compound_rotation);
2111 leaf_rotation.at(petiole).at(leaf).yaw = sign *
deg2rad(phytomer_parameters.
leaf.yaw.val());
2112 float yaw_rot = leaf_rotation.at(petiole).at(leaf).yaw;
2113 phytomer_parameters.
leaf.yaw.resample();
2116 leaf_rotation.at(petiole).at(leaf).yaw = 0;
2120 context_ptr->
rotateObject(objID_leaf, -std::atan2(petiole_tip_axis.
y, petiole_tip_axis.
x) + compound_rotation,
"z");
2134 if (leaves_per_petiole == 1) {
2136 const float r_h = sqrtf(petiole_tip_axis.
x * petiole_tip_axis.
x + petiole_tip_axis.
y * petiole_tip_axis.
y);
2138 float blade_correction = std::atan2(petiole_tip_axis.
z * r_h, r_h * r_h);
2139 const float petiole_len = petiole_length.at(petiole);
2140 const float leaf_size_ref = std::max(leaf_size_max.at(petiole).at(leaf), 1e-6f);
2141 const float length_ratio = std::min(petiole_len / leaf_size_ref, 1.f);
2142 blade_correction *= length_ratio;
2143 const float max_correction = 0.5f *
PI_F -
deg2rad(1.f);
2144 if (blade_correction > max_correction) blade_correction = max_correction;
2145 if (blade_correction < -max_correction) blade_correction = -max_correction;
2146 context_ptr->
rotateObject(objID_leaf, blade_correction *
static_cast<float>(sign), petiole_tip_axis);
2154 if (leaves_per_petiole > 1 && leaflet_offset_val > 0) {
2155 if (ind_from_tip != 0) {
2156 float offset = (fabs(ind_from_tip) - 0.5f) * leaflet_offset_val * phytomer_parameters.
petiole.length.val();
2157 leaf_base = PlantArchitecture::interpolateTube(
petiole_vertices.at(petiole), 1.f - offset / phytomer_parameters.
petiole.length.val());
2163 leaf_objIDs.at(petiole).push_back(objID_leaf);
2164 leaf_bases.at(petiole).push_back(leaf_base);
2166 phytomer_parameters.
leaf.prototype_scale.resample();
2168 inflorescence_bending_axis =
cross(parent_internode_axis, petiole_axis_actual);
2169 if (inflorescence_bending_axis ==
make_vec3(0, 0, 0)) {
2170 inflorescence_bending_axis =
make_vec3(1, 0, 0);
2175 if (phytomer_parameters.
petiole.petioles_per_internode == 0) {
2176 std::vector<VegetativeBud> vegetative_buds_new;
2177 vegetative_buds_new.resize(phytomer_parameters.
internode.max_vegetative_buds_per_petiole.val());
2178 phytomer_parameters.
internode.max_vegetative_buds_per_petiole.resample();
2179 axillary_vegetative_buds.push_back(vegetative_buds_new);
2181 std::vector<FloralBud> floral_buds_new;
2182 floral_buds_new.resize(phytomer_parameters.
internode.max_floral_buds_per_petiole.val());
2183 phytomer_parameters.
internode.max_floral_buds_per_petiole.resample();
2184 floral_buds.push_back(floral_buds_new);
2190 const auto &segment = parent_shoot_ptr->shoot_internode_radii.at(node_number);
2193 float avg_radius = 0.0f;
2194 for (
float radius: segment) {
2195 avg_radius += radius;
2197 avg_radius /= scast<float>(segment.size());
2203 float volume =
PI_F * avg_radius * avg_radius * length;
2208void 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) {
2212 if (fbud.state == BUD_FRUITING) {
2213 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2215 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2216 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_fruit_prototype_objIDs.at(phytomer_parameters.
inflorescence.fruit_prototype_function).at(prototype));
2219 objID_fruit = phytomer_parameters.
inflorescence.fruit_prototype_function(context_ptr, 1);
2220 std::string fruit_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_fruit";
2221 renameAutoMaterial(context_ptr, objID_fruit, fruit_material_name);
2225 if (phytomer_parameters.
inflorescence.unique_prototypes > 0) {
2227 int prototype = context_ptr->
randu(0,
int(phytomer_parameters.
inflorescence.unique_prototypes - 1));
2228 if (is_open_flower) {
2229 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_open_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype));
2231 objID_fruit = context_ptr->
copyObject(plantarchitecture_ptr->unique_closed_flower_prototype_objIDs.at(phytomer_parameters.
inflorescence.flower_prototype_function).at(prototype));
2235 objID_fruit = phytomer_parameters.
inflorescence.flower_prototype_function(context_ptr, 1, is_open_flower);
2236 std::string flower_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name + (is_open_flower ?
"_flower_open" :
"_flower_closed");
2237 renameAutoMaterial(context_ptr, objID_fruit, flower_material_name);
2243 context_ptr->
scaleObject(objID_fruit, fruit_scale);
2246 if (std::abs(roll) > 1e-6) {
2249 if (std::abs(pitch) > 1e-6) {
2252 if (std::abs(azimuth) > 1e-6) {
2260 if (std::abs(yaw_compound) > 1e-6) {
2261 context_ptr->
rotateObject(objID_fruit, yaw_compound, fruit_base, peduncle_axis);
2265 fbud.inflorescence_objIDs.push_back(objID_fruit);
2266 fbud.inflorescence_bases.push_back(fruit_base);
2269 flower_rotation.pitch = pitch;
2270 flower_rotation.yaw = yaw_compound;
2271 flower_rotation.roll = roll;
2272 flower_rotation.azimuth = azimuth;
2273 flower_rotation.peduncle_axis = peduncle_axis;
2274 fbud.inflorescence_rotation.push_back(flower_rotation);
2276 fbud.inflorescence_base_scales.push_back(scale_factor);
2278 assert(fbud.inflorescence_objIDs.size() == fbud.inflorescence_bases.size());
2279 assert(fbud.inflorescence_bases.size() == fbud.inflorescence_rotation.size());
2280 assert(fbud.inflorescence_rotation.size() == fbud.inflorescence_base_scales.size());
2283void PlantArchitecture::ensureInflorescencePrototypesInitialized(
const PhytomerParameters ¶ms,
const std::string &plant_name) {
2284 if (params.inflorescence.unique_prototypes > 0) {
2286 if (params.inflorescence.flower_prototype_function !=
nullptr && unique_closed_flower_prototype_objIDs.find(params.inflorescence.flower_prototype_function) == unique_closed_flower_prototype_objIDs.end()) {
2287 unique_closed_flower_prototype_objIDs[params.inflorescence.flower_prototype_function].resize(params.inflorescence.unique_prototypes);
2288 for (
int prototype = 0; prototype < params.inflorescence.unique_prototypes; prototype++) {
2289 uint objID_flower = params.inflorescence.flower_prototype_function(context_ptr, 1,
false);
2290 unique_closed_flower_prototype_objIDs.at(params.inflorescence.flower_prototype_function).at(prototype) = objID_flower;
2291 renameAutoMaterial(context_ptr, objID_flower, plant_name +
"_flower_closed");
2296 if (params.inflorescence.flower_prototype_function !=
nullptr && unique_open_flower_prototype_objIDs.find(params.inflorescence.flower_prototype_function) == unique_open_flower_prototype_objIDs.end()) {
2297 unique_open_flower_prototype_objIDs[params.inflorescence.flower_prototype_function].resize(params.inflorescence.unique_prototypes);
2298 for (
int prototype = 0; prototype < params.inflorescence.unique_prototypes; prototype++) {
2299 uint objID_flower = params.inflorescence.flower_prototype_function(context_ptr, 1,
true);
2300 unique_open_flower_prototype_objIDs.at(params.inflorescence.flower_prototype_function).at(prototype) = objID_flower;
2301 renameAutoMaterial(context_ptr, objID_flower, plant_name +
"_flower_open");
2306 if (params.inflorescence.fruit_prototype_function !=
nullptr && unique_fruit_prototype_objIDs.find(params.inflorescence.fruit_prototype_function) == unique_fruit_prototype_objIDs.end()) {
2307 unique_fruit_prototype_objIDs[params.inflorescence.fruit_prototype_function].resize(params.inflorescence.unique_prototypes);
2308 for (
int prototype = 0; prototype < params.inflorescence.unique_prototypes; prototype++) {
2309 uint objID_fruit = params.inflorescence.fruit_prototype_function(context_ptr, 1);
2310 unique_fruit_prototype_objIDs.at(params.inflorescence.fruit_prototype_function).at(prototype) = objID_fruit;
2311 renameAutoMaterial(context_ptr, objID_fruit, plant_name +
"_fruit");
2318void Phytomer::updateInflorescence(
FloralBud &fbud) {
2319 bool build_context_geometry_peduncle = plantarchitecture_ptr->build_context_geometry_peduncle;
2321 uint Ndiv_peduncle_length = std::max(
uint(1), phytomer_parameters.
peduncle.length_segments);
2322 uint Ndiv_peduncle_radius = std::max(
uint(3), phytomer_parameters.
peduncle.radial_subdivisions);
2323 if (phytomer_parameters.
peduncle.length_segments == 0 || phytomer_parameters.
peduncle.radial_subdivisions < 3) {
2324 build_context_geometry_peduncle =
false;
2328 float peduncle_length = phytomer_parameters.
peduncle.length.val();
2329 float dr_peduncle = peduncle_length / float(Ndiv_peduncle_length);
2331 std::vector<vec3> peduncle_vertices(phytomer_parameters.
peduncle.length_segments + 1);
2332 peduncle_vertices.at(0) = fbud.base_position;
2333 std::vector<float> peduncle_radii(phytomer_parameters.
peduncle.length_segments + 1);
2334 peduncle_radii.at(0) = phytomer_parameters.
peduncle.radius.val();
2335 std::vector<RGBcolor> peduncle_colors(phytomer_parameters.
peduncle.length_segments + 1);
2336 peduncle_colors.at(0) = phytomer_parameters.
peduncle.color;
2341 vec3 inflorescence_bending_axis_actual = inflorescence_bending_axis;
2344 if (phytomer_parameters.
peduncle.pitch.val() != 0.f || fbud.base_rotation.pitch != 0.f) {
2345 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, inflorescence_bending_axis_actual,
deg2rad(phytomer_parameters.
peduncle.pitch.val()) + fbud.base_rotation.pitch);
2350 vec3 parent_petiole_base_axis;
2353 parent_petiole_base_axis = internode_axis;
2357 float parent_petiole_azimuth = -std::atan2(parent_petiole_base_axis.
y, parent_petiole_base_axis.
x);
2358 float current_peduncle_azimuth = -std::atan2(peduncle_axis.
y, peduncle_axis.
x);
2359 float azimuthal_rotation = current_peduncle_azimuth - parent_petiole_azimuth;
2360 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, internode_axis, azimuthal_rotation);
2362 inflorescence_bending_axis_actual =
rotatePointAboutLine(inflorescence_bending_axis_actual, nullorigin, internode_axis, azimuthal_rotation);
2365 float theta_base = fabs(
cart2sphere(peduncle_axis).zenith);
2368 vec3 collision_optimal_peduncle_direction;
2369 bool peduncle_collision_active =
false;
2371 if (plantarchitecture_ptr->fruit_collision_detection_enabled) {
2372 collision_optimal_peduncle_direction = calculateFruitCollisionAvoidanceDirection(fbud.base_position, peduncle_axis, peduncle_collision_active);
2375 if (peduncle_collision_active) {
2376 float inertia_weight = plantarchitecture_ptr->collision_inertia_weight;
2377 vec3 natural_peduncle_direction = peduncle_axis;
2382 peduncle_axis = inertia_weight * natural_peduncle_direction + (1.0f - inertia_weight) * collision_optimal_peduncle_direction;
2387 float peduncle_curvature = phytomer_parameters.
peduncle.curvature.val();
2388 phytomer_parameters.
peduncle.curvature.resample();
2391 uint petiole_idx = fbud.parent_index;
2392 uint bud_idx = fbud.bud_index;
2393 if (petiole_idx < this->peduncle_length.size()) {
2394 if (this->peduncle_length.at(petiole_idx).size() <= bud_idx) {
2395 this->peduncle_length.at(petiole_idx).resize(bud_idx + 1);
2396 this->peduncle_radius.at(petiole_idx).resize(bud_idx + 1);
2397 this->peduncle_pitch.at(petiole_idx).resize(bud_idx + 1);
2398 this->peduncle_curvature.at(petiole_idx).resize(bud_idx + 1);
2400 this->peduncle_length.at(petiole_idx).at(bud_idx) = peduncle_length;
2401 this->peduncle_radius.at(petiole_idx).at(bud_idx) = phytomer_parameters.
peduncle.radius.val();
2402 this->peduncle_pitch.at(petiole_idx).at(bud_idx) = phytomer_parameters.
peduncle.pitch.val();
2403 this->peduncle_curvature.at(petiole_idx).at(bud_idx) = peduncle_curvature;
2406 for (
int i = 1; i <= phytomer_parameters.
peduncle.length_segments; i++) {
2407 if (peduncle_curvature != 0.f) {
2408 float curvature_value = peduncle_curvature;
2413 float axis_magnitude = horizontal_bending_axis.
magnitude();
2416 if (axis_magnitude > 0.001f) {
2417 horizontal_bending_axis = horizontal_bending_axis / axis_magnitude;
2420 float theta_curvature =
deg2rad(curvature_value * dr_peduncle);
2421 float theta_from_target;
2423 if (curvature_value > 0) {
2426 theta_from_target = std::acos(std::min(1.0f, std::max(-1.0f, peduncle_axis.
z)));
2430 theta_from_target = std::acos(std::min(1.0f, std::max(-1.0f, -peduncle_axis.
z)));
2434 if (fabs(theta_curvature) >= theta_from_target) {
2436 if (curvature_value > 0) {
2443 peduncle_axis =
rotatePointAboutLine(peduncle_axis, nullorigin, horizontal_bending_axis, theta_curvature);
2448 if (curvature_value > 0) {
2456 peduncle_vertices.at(i) = peduncle_vertices.at(i - 1) + dr_peduncle * peduncle_axis;
2458 peduncle_radii.at(i) = phytomer_parameters.
peduncle.radius.val();
2459 peduncle_colors.at(i) = phytomer_parameters.
peduncle.color;
2462 if (build_context_geometry_peduncle) {
2463 fbud.peduncle_objIDs.push_back(context_ptr->
addTubeObject(Ndiv_peduncle_radius, peduncle_vertices, peduncle_radii, peduncle_colors));
2465 std::string peduncle_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot_ptr->shoot_type_label +
"_peduncle";
2466 renameAutoMaterial(context_ptr, fbud.peduncle_objIDs.back(), peduncle_material_name);
2473 if (petiole_idx < this->peduncle_vertices.size()) {
2474 if (this->peduncle_vertices.at(petiole_idx).size() <= fbud.bud_index) {
2475 this->peduncle_vertices.at(petiole_idx).resize(fbud.bud_index + 1);
2477 this->peduncle_vertices.at(petiole_idx).at(fbud.bud_index) = peduncle_vertices;
2481 if (petiole_idx < this->peduncle_radii.size()) {
2482 if (this->peduncle_radii.at(petiole_idx).size() <= fbud.bud_index) {
2483 this->peduncle_radii.at(petiole_idx).resize(fbud.bud_index + 1);
2485 this->peduncle_radii.at(petiole_idx).at(fbud.bud_index) = peduncle_radii;
2489 phytomer_parameters.
peduncle.length.resample();
2490 phytomer_parameters.
peduncle.radius.resample();
2491 phytomer_parameters.
peduncle.pitch.resample();
2494 plantarchitecture_ptr->ensureInflorescencePrototypesInitialized(phytomer_parameters, plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
2496 int flowers_per_peduncle = phytomer_parameters.
inflorescence.flowers_per_peduncle.val();
2497 float flower_offset_val = clampOffset(flowers_per_peduncle, phytomer_parameters.
inflorescence.flower_offset.val());
2498 for (
int fruit = 0; fruit < flowers_per_peduncle; fruit++) {
2501 if (fbud.state == BUD_FRUITING) {
2502 scale_factor = phytomer_parameters.
inflorescence.fruit_prototype_scale.val();
2503 phytomer_parameters.
inflorescence.fruit_prototype_scale.resample();
2505 scale_factor = phytomer_parameters.
inflorescence.flower_prototype_scale.val();
2506 phytomer_parameters.
inflorescence.flower_prototype_scale.resample();
2509 float ind_from_tip = fabs(fruit -
float(flowers_per_peduncle - 1) /
float(phytomer_parameters.
petiole.petioles_per_internode));
2512 vec3 fruit_base = peduncle_vertices.back();
2514 if (flowers_per_peduncle > 1 && flower_offset_val > 0) {
2515 if (ind_from_tip != 0) {
2516 float offset = (ind_from_tip - 0.5f) * flower_offset_val * phytomer_parameters.
peduncle.length.val();
2517 if (phytomer_parameters.
peduncle.length.val() > 0) {
2518 frac = 1.f - offset / phytomer_parameters.
peduncle.length.val();
2520 fruit_base = PlantArchitecture::interpolateTube(peduncle_vertices, frac);
2525 float compound_rotation = 0;
2526 if (flowers_per_peduncle > 1) {
2527 if (flower_offset_val == 0) {
2529 float dphi =
PI_F / (floor(0.5 *
float(flowers_per_peduncle - 1)) + 1);
2530 compound_rotation = -float(
PI_F) + dphi * (fruit + 0.5f);
2532 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);
2533 phytomer_parameters.
internode.phyllotactic_angle.resample();
2547 float pitch_inflorescence = -
asin_safe(peduncle_axis.
z) + applied_pitch_param;
2548 if (fbud.state == BUD_FRUITING) {
2550 pitch_inflorescence = pitch_inflorescence + phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.val() * (0.5f *
PI_F - pitch_inflorescence);
2552 phytomer_parameters.
inflorescence.fruit_gravity_factor_fraction.resample();
2555 float azimuth = -std::atan2(peduncle_axis.
y, peduncle_axis.
x);
2558 float yaw_compound =
deg2rad(phytomer_parameters.
peduncle.roll.val()) + compound_rotation;
2561 bool is_open_flower = (fbud.state == BUD_FLOWER_OPEN);
2564 createInflorescenceGeometry(fbud, fruit_base, peduncle_axis, pitch_inflorescence, applied_roll, azimuth, yaw_compound, scale_factor, is_open_flower);
2566 phytomer_parameters.
inflorescence.flowers_per_peduncle.resample();
2567 phytomer_parameters.
peduncle.roll.resample();
2569 if (plantarchitecture_ptr->output_object_data.at(
"age")) {
2570 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"age", fbud.age);
2571 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"age", fbud.age);
2574 if (plantarchitecture_ptr->output_object_data.at(
"rank")) {
2575 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"rank", rank);
2576 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"rank", rank);
2579 if (plantarchitecture_ptr->output_object_data.at(
"plant_name")) {
2580 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
2581 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"plant_name", plantarchitecture_ptr->plant_instances.at(plantID).plant_name);
2584 if (plantarchitecture_ptr->output_object_data.at(
"peduncleID")) {
2585 for (
uint objID: fbud.peduncle_objIDs) {
2586 context_ptr->
setObjectData(objID,
"peduncleID", (
int) objID);
2589 for (
uint objID: fbud.inflorescence_objIDs) {
2590 if (fbud.state == BUD_FLOWER_CLOSED && plantarchitecture_ptr->output_object_data.at(
"closedflowerID")) {
2591 context_ptr->
setObjectData(objID,
"closedflowerID", (
int) objID);
2592 }
else if (fbud.state == BUD_FLOWER_OPEN && plantarchitecture_ptr->output_object_data.at(
"openflowerID")) {
2594 context_ptr->
setObjectData(objID,
"openflowerID", (
int) objID);
2595 }
else if (plantarchitecture_ptr->output_object_data.at(
"fruitID")) {
2608 vec3 shift = base_position - old_base;
2611 for (
auto &vertex: petiole_vertice) {
2616 if (build_context_geometry_petiole) {
2621 for (
auto &petiole: leaf_bases) {
2622 for (
auto &leaf_base: petiole) {
2627 for (
auto &petiole_peduncles: peduncle_vertices) {
2628 for (
auto &bud_peduncle_vertices: petiole_peduncles) {
2629 for (
auto &vertex: bud_peduncle_vertices) {
2635 for (
auto &floral_bud: floral_buds) {
2636 for (
auto &fbud: floral_bud) {
2639 for (
auto &base: fbud.inflorescence_bases) {
2642 if (build_context_geometry_peduncle) {
2650 if (petiole_index >= leaf_objIDs.size()) {
2652 }
else if (leaf_index >= leaf_objIDs.at(petiole_index).size()) {
2661 vec3 pitch_axis = -1 *
cross(internode_axis, petiole_axis);
2663 int leaves_per_petiole = leaf_rotation.at(petiole_index).size();
2666 float compound_rotation = 0;
2667 if (leaves_per_petiole > 1 && leaf_index ==
float(leaves_per_petiole - 1) / 2.f) {
2671 compound_rotation = 0;
2672 }
else if (leaves_per_petiole > 1 && leaf_index <
float(leaves_per_petiole - 1) / 2.f) {
2674 yaw = -rotation.yaw;
2675 roll = -rotation.roll;
2676 compound_rotation = -0.5 *
PI_F;
2679 yaw = -rotation.yaw;
2680 roll = rotation.roll;
2681 compound_rotation = 0;
2687 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), roll, leaf_bases.at(petiole_index).at(leaf_index), roll_axis);
2688 leaf_rotation.at(petiole_index).at(leaf_index).roll += roll;
2692 if (rotation.pitch != 0) {
2694 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), rotation.pitch, leaf_bases.at(petiole_index).at(leaf_index), pitch_axis);
2695 leaf_rotation.at(petiole_index).at(leaf_index).pitch += rotation.pitch;
2700 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index).at(leaf_index), yaw, leaf_bases.at(petiole_index).at(leaf_index), {0, 0, 1});
2701 leaf_rotation.at(petiole_index).at(leaf_index).yaw += yaw;
2707 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer::rotatePetiole): Invalid petiole index.");
2712 if (rotation.pitch == 0.f && rotation.yaw == 0.f && rotation.roll == 0.f) {
2719 auto applyRotation = [
this, petiole_index, &base](
float angle,
const vec3 &axis) {
2723 if (!petiole_objIDs.at(petiole_index).empty()) {
2724 context_ptr->
rotateObject(petiole_objIDs.at(petiole_index), angle, base, axis);
2726 if (petiole_index < leaf_objIDs.size() && !leaf_objIDs.at(petiole_index).empty()) {
2727 context_ptr->
rotateObject(leaf_objIDs.at(petiole_index), angle, base, axis);
2732 if (petiole_index < leaf_bases.size()) {
2733 for (
auto &leaf_base: leaf_bases.at(petiole_index)) {
2737 if (petiole_index < petiole_axis_initial.size()) {
2748 if (rotation.pitch != 0.f && petiole_index < petiole_rotation_axis.size()) {
2749 vec3 pitch_axis = petiole_rotation_axis.at(petiole_index);
2752 applyRotation(std::abs(rotation.pitch), pitch_axis);
2753 petiole_pitch.at(petiole_index) += std::abs(rotation.pitch);
2758 if (rotation.yaw != 0.f) {
2759 vec3 yaw_axis = internode_axis;
2762 applyRotation(rotation.yaw, yaw_axis);
2767 if (rotation.roll != 0.f) {
2771 applyRotation(rotation.roll, roll_axis);
2777 assert(internode_scale_factor_fraction >= 0 && internode_scale_factor_fraction <= 1);
2779 if (internode_scale_factor_fraction == current_internode_scale_factor) {
2783 float delta_scale = internode_scale_factor_fraction / current_internode_scale_factor;
2785 current_internode_scale_factor = internode_scale_factor_fraction;
2788 int s_start = (p == 0) ? 1 : 0;
2790 for (
int s = s_start; s < parent_shoot_ptr->shoot_internode_vertices.at(p).size(); s++) {
2794 int s_minus = s - 1;
2797 s_minus =
static_cast<int>(parent_shoot_ptr->shoot_internode_vertices.at(p_minus).size() - 1);
2800 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));
2801 float current_length = central_axis.
magnitude();
2802 central_axis = central_axis / current_length;
2803 vec3 dL = central_axis * current_length * (delta_scale - 1);
2806 for (
int p_downstream = p; p_downstream < parent_shoot_ptr->shoot_internode_vertices.size(); p_downstream++) {
2807 int sd_start = (p_downstream == p) ? s : 0;
2808 for (
int s_downstream = sd_start; s_downstream < parent_shoot_ptr->shoot_internode_vertices.at(p_downstream).size(); s_downstream++) {
2809 parent_shoot_ptr->shoot_internode_vertices.at(p_downstream).at(s_downstream) += dL;
2818 this->internode_length_max *= scale_factor;
2820 current_internode_scale_factor = current_internode_scale_factor / scale_factor;
2822 if (current_internode_scale_factor >= 1.f) {
2824 current_internode_scale_factor = 1.f;
2829 float scale_factor = internode_length_max_new / this->internode_length_max;
2834 this->internode_radius_max = internode_radius_max_new;
2839 assert(leaf_scale_factor_fraction >= 0 && leaf_scale_factor_fraction <= 1);
2841 if (current_leaf_scale_factor.size() <= petiole_index) {
2842 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf scale factor.");
2846 if (leaf_scale_factor_fraction == current_leaf_scale_factor.at(petiole_index) || (leaf_objIDs.at(petiole_index).empty() && petiole_objIDs.at(petiole_index).empty())) {
2850 float delta_scale = leaf_scale_factor_fraction / current_leaf_scale_factor.at(petiole_index);
2852 petiole_length.at(petiole_index) *= delta_scale;
2854 current_leaf_scale_factor.at(petiole_index) = leaf_scale_factor_fraction;
2856 assert(leaf_objIDs.size() == leaf_bases.size());
2860 if (!petiole_objIDs.at(petiole_index).empty()) {
2863 for (
uint objID: petiole_objIDs.at(petiole_index)) {
2867 petiole_radii.at(petiole_index).at(node) *= delta_scale;
2878 }
else if (build_context_geometry_petiole) {
2881 for (
uint node = 0; node < petiole_radii.at(petiole_index).size(); node++) {
2882 petiole_radii.at(petiole_index).at(node) *= delta_scale;
2890 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
2891 petiole_objIDs.at(petiole_index) =
makeTubeFromCones(Ndiv_petiole_radius,
petiole_vertices.at(petiole_index), petiole_radii.at(petiole_index), petiole_colors, context_ptr);
2892 if (!petiole_objIDs.at(petiole_index).empty()) {
2894 std::string petiole_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot_ptr->shoot_type_label +
"_petiole";
2895 renameAutoMaterial(context_ptr, petiole_objIDs.at(petiole_index), petiole_material_name);
2900 assert(leaf_objIDs.at(petiole_index).size() == leaf_bases.at(petiole_index).size());
2901 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2902 float ind_from_tip = float(leaf) - float(leaf_objIDs.at(petiole_index).size() - 1) / 2.f;
2904 float leaflet_offset_val = clampOffset(
int(leaf_objIDs.at(petiole_index).size()), phytomer_parameters.
leaf.leaflet_offset.val());
2906 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), -1 * leaf_bases.at(petiole_index).at(leaf));
2907 context_ptr->
scaleObject(leaf_objIDs.at(petiole_index).at(leaf), delta_scale *
make_vec3(1, 1, 1));
2908 if (ind_from_tip == 0) {
2910 leaf_bases.at(petiole_index).at(leaf) =
petiole_vertices.at(petiole_index).back();
2912 float offset = (fabs(ind_from_tip) - 0.5f) * leaflet_offset_val * phytomer_parameters.
petiole.length.val();
2913 vec3 leaf_base = PlantArchitecture::interpolateTube(
petiole_vertices.at(petiole_index), 1.f - offset / phytomer_parameters.
petiole.length.val());
2914 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), leaf_base);
2915 leaf_bases.at(petiole_index).at(leaf) = leaf_base;
2921 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2927 if (leaf_objIDs.size() <= petiole_index) {
2928 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf prototype scale.");
2930 if (leaf_prototype_scale < 0.f) {
2931 leaf_prototype_scale = 0;
2934 float tip_ind = ceil(scast<float>(leaf_size_max.at(petiole_index).size() - 1) / 2.f);
2935 float scale_factor = leaf_prototype_scale / leaf_size_max.at(petiole_index).at(tip_ind);
2936 current_leaf_scale_factor.at(petiole_index) *= scale_factor;
2938 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2939 leaf_size_max.at(petiole_index).at(leaf) *= scale_factor;
2940 context_ptr->
scaleObjectAboutPoint(leaf_objIDs.at(petiole_index).at(leaf), scale_factor *
make_vec3(1, 1, 1), leaf_bases.at(petiole_index).at(leaf));
2944 this->petiole_curvature.at(petiole_index) /= scale_factor;
2946 if (current_leaf_scale_factor.at(petiole_index) >= 1.f) {
2948 current_leaf_scale_factor.at(petiole_index) = 1.f;
2953 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2959 if (leaf_objIDs.size() <= petiole_index) {
2960 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer): Invalid petiole index for leaf prototype scale.");
2962 if (scale_factor < 0.f) {
2966 current_leaf_scale_factor.at(petiole_index) /= scale_factor;
2968 for (
int leaf = 0; leaf < leaf_objIDs.at(petiole_index).size(); leaf++) {
2969 leaf_size_max.at(petiole_index).at(leaf) *= scale_factor;
2970 context_ptr->
scaleObjectAboutPoint(leaf_objIDs.at(petiole_index).at(leaf), scale_factor *
make_vec3(1, 1, 1), leaf_bases.at(petiole_index).at(leaf));
2974 this->petiole_curvature.at(petiole_index) /= scale_factor;
2976 if (current_leaf_scale_factor.at(petiole_index) >= 1.f) {
2978 current_leaf_scale_factor.at(petiole_index) = 1.f;
2983 for (
uint petiole_index = 0; petiole_index < leaf_objIDs.size(); petiole_index++) {
2989 if (petiole_index >= petiole_length.size()) {
2990 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer::scalePetioleGeometry): Invalid petiole index " + std::to_string(petiole_index) +
".");
2992 if (target_length <= 0.f || target_base_radius <= 0.f) {
2993 helios_runtime_error(
"ERROR (PlantArchitecture::Phytomer::scalePetioleGeometry): Target length and radius must be positive.");
2997 float current_length = petiole_length.at(petiole_index);
2998 float current_base_radius = petiole_radii.at(petiole_index).at(0);
3000 if (current_length <= 0.f || current_base_radius <= 0.f) {
3002 petiole_length.at(petiole_index) = target_length;
3003 if (!petiole_radii.at(petiole_index).empty()) {
3004 petiole_radii.at(petiole_index).at(0) = target_base_radius;
3009 float length_scale = target_length / current_length;
3010 float radius_scale = target_base_radius / current_base_radius;
3018 petiole_vertices.at(petiole_index).at(j) = petiole_base + offset * length_scale;
3022 for (
size_t j = 0; j < petiole_radii.at(petiole_index).size(); j++) {
3023 petiole_radii.at(petiole_index).at(j) *= radius_scale;
3027 petiole_length.at(petiole_index) = target_length;
3030 if (!petiole_objIDs.at(petiole_index).empty()) {
3032 context_ptr->
deleteObject(petiole_objIDs.at(petiole_index));
3035 std::vector<RGBcolor> petiole_colors(petiole_radii.at(petiole_index).size(), phytomer_parameters.
petiole.color);
3036 uint Ndiv_petiole_radius = std::max(
uint(3), phytomer_parameters.
petiole.radial_subdivisions);
3038 petiole_objIDs.at(petiole_index) =
makeTubeFromCones(Ndiv_petiole_radius,
petiole_vertices.at(petiole_index), petiole_radii.at(petiole_index), petiole_colors, context_ptr);
3041 if (!petiole_objIDs.at(petiole_index).empty()) {
3043 std::string petiole_material_name = plantarchitecture_ptr->plant_instances.at(plantID).plant_name +
"_" + parent_shoot_ptr->shoot_type_label +
"_petiole";
3044 renameAutoMaterial(context_ptr, petiole_objIDs.at(petiole_index), petiole_material_name);
3049 if (petiole_index < leaf_bases.size()) {
3050 for (
size_t leaf = 0; leaf < leaf_bases.at(petiole_index).size(); leaf++) {
3051 vec3 offset = leaf_bases.at(petiole_index).at(leaf) - petiole_base;
3052 leaf_bases.at(petiole_index).at(leaf) = petiole_base + offset * length_scale;
3055 if (petiole_index < leaf_objIDs.size() && leaf < leaf_objIDs.at(petiole_index).size()) {
3056 vec3 translation = offset * length_scale - offset;
3057 context_ptr->
translateObject(leaf_objIDs.at(petiole_index).at(leaf), translation);
3063 if (petiole_index < floral_buds.size()) {
3064 for (
auto &fbud: floral_buds.at(petiole_index)) {
3065 vec3 offset = fbud.base_position - petiole_base;
3066 vec3 translation = offset * length_scale - offset;
3067 fbud.base_position = petiole_base + offset * length_scale;
3070 for (
size_t i = 0; i < fbud.inflorescence_bases.size(); i++) {
3071 fbud.inflorescence_bases.at(i) += translation;
3075 for (
size_t i = 0; i < fbud.inflorescence_objIDs.size(); i++) {
3076 context_ptr->
translateObject(fbud.inflorescence_objIDs.at(i), translation);
3078 for (
size_t i = 0; i < fbud.peduncle_objIDs.size(); i++) {
3079 context_ptr->
translateObject(fbud.peduncle_objIDs.at(i), translation);
3086 assert(inflorescence_scale_factor_fraction >= 0 && inflorescence_scale_factor_fraction <= 1);
3088 if (inflorescence_scale_factor_fraction == fbud.current_fruit_scale_factor) {
3092 float delta_scale = inflorescence_scale_factor_fraction / fbud.current_fruit_scale_factor;
3094 fbud.current_fruit_scale_factor = inflorescence_scale_factor_fraction;
3097 for (
int inflorescence = 0; inflorescence < fbud.inflorescence_objIDs.size(); inflorescence++) {
3098 context_ptr->
scaleObjectAboutPoint(fbud.inflorescence_objIDs.at(inflorescence), delta_scale *
make_vec3(1, 1, 1), fbud.inflorescence_bases.at(inflorescence));
3105 this->petiole_radii.resize(0);
3107 this->petiole_colors.resize(0);
3108 this->petiole_length.resize(0);
3109 this->leaf_size_max.resize(0);
3110 this->leaf_rotation.resize(0);
3111 this->leaf_bases.resize(0);
3114 leaf_objIDs.clear();
3117 if (build_context_geometry_petiole) {
3119 petiole_objIDs.resize(0);
3125 if (context_ptr->
doesObjectExist(parent_shoot_ptr->internode_tube_objID)) {
3128 uint tube_prune_index;
3130 tube_prune_index = 0;
3132 tube_prune_index = this->
shoot_index.
x * tube_segments + 1;
3134 if (tube_prune_index < tube_nodes) {
3135 context_ptr->
pruneTubeNodes(parent_shoot_ptr->internode_tube_objID, tube_prune_index);
3141 auto &phytomer = parent_shoot_ptr->phytomers.at(node);
3144 phytomer->removeLeaf();
3147 for (
auto &petiole: phytomer->floral_buds) {
3148 for (
auto &fbud: petiole) {
3149 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
3150 uint objID = fbud.inflorescence_objIDs.at(p);
3152 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
3153 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
3155 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
3158 fbud.peduncle_objIDs.clear();
3159 fbud.inflorescence_objIDs.clear();
3160 fbud.inflorescence_bases.clear();
3167 if (parent_shoot_ptr->childIDs.find(node) != parent_shoot_ptr->childIDs.end()) {
3168 for (
auto childID: parent_shoot_ptr->childIDs.at(node)) {
3169 auto child_shoot = plantarchitecture_ptr->plant_instances.at(plantID).shoot_tree.at(childID);
3170 if (!child_shoot->phytomers.empty()) {
3171 child_shoot->phytomers.front()->deletePhytomer();
3178 parent_shoot_ptr->shoot_internode_radii.resize(this->
shoot_index.
x);
3179 parent_shoot_ptr->shoot_internode_vertices.resize(this->
shoot_index.
x);
3180 parent_shoot_ptr->phytomers.resize(this->
shoot_index.
x);
3183 for (
const auto &phytomer: parent_shoot_ptr->phytomers) {
3184 phytomer->shoot_index.y = scast<int>(parent_shoot_ptr->phytomers.size());
3186 parent_shoot_ptr->current_node_number = scast<int>(parent_shoot_ptr->phytomers.size());
3190 return (!leaf_bases.empty() && !leaf_bases.front().empty());
3199 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),
3200 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) {
3201 carbohydrate_pool_molC = 0;
3202 phyllochron_counter = 0;
3205 context_ptr = plant_architecture_ptr->context_ptr;
3209 if (parent_shoot_ID >= 0) {
3210 plant_architecture_ptr->plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->childIDs[(int) parent_node_index].push_back(shoot_ID);
3214void Shoot::buildShootPhytomers(
float internode_radius,
float internode_length,
float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper) {
3215 for (
int i = 0; i < current_node_number; i++) {
3219 if (current_node_number > 1) {
3220 taper = 1.f - radius_taper * float(i) / float(current_node_number - 1);
3224 appendPhytomer(internode_radius * taper, internode_length, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, shoot_parameters.
phytomer_parameters);
3229 auto shoot_ptr =
this;
3231 assert(shoot_ptr->shoot_parameters.child_shoot_type_labels.size() == shoot_ptr->shoot_parameters.child_shoot_type_probabilities.size());
3233 std::string child_shoot_type_label;
3235 if (shoot_ptr->shoot_parameters.child_shoot_type_labels.empty()) {
3237 child_shoot_type_label = shoot_ptr->shoot_type_label;
3238 }
else if (shoot_ptr->shoot_parameters.child_shoot_type_labels.size() == 1) {
3240 child_shoot_type_label = shoot_ptr->shoot_parameters.child_shoot_type_labels.at(0);
3242 float randf = context_ptr->
randu();
3243 int shoot_type_index = -1;
3244 float cumulative_probability = 0;
3245 for (
int s = 0; s < shoot_ptr->shoot_parameters.child_shoot_type_labels.size(); s++) {
3246 cumulative_probability += shoot_ptr->shoot_parameters.child_shoot_type_probabilities.at(s);
3247 if (randf < cumulative_probability) {
3248 shoot_type_index = s;
3252 if (shoot_type_index < 0) {
3253 shoot_type_index = shoot_ptr->shoot_parameters.child_shoot_type_labels.size() - 1;
3255 child_shoot_type_label = shoot_ptr->shoot_type_label;
3256 if (shoot_type_index >= 0) {
3257 child_shoot_type_label = shoot_ptr->shoot_parameters.child_shoot_type_labels.at(shoot_type_index);
3261 return child_shoot_type_label;
3265 if (node_index >= phytomers.size()) {
3266 helios_runtime_error(
"ERROR (PlantArchitecture::sampleVegetativeBudBreak): Invalid node index. Node index must be less than the number of phytomers on the shoot.");
3269 float probability_min = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_min.val();
3270 float probability_max = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_max.val();
3271 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();
3273 float bud_break_probability;
3275 bud_break_probability = probability_min;
3276 }
else if (probability_decay > 0) {
3278 bud_break_probability = std::fmax(probability_min, probability_max - probability_decay *
float(this->current_node_number - node_index - 1));
3279 }
else if (probability_decay < 0) {
3281 bud_break_probability = std::fmax(probability_min, probability_max - fabs(probability_decay) *
float(node_index));
3283 if (probability_decay == 0.f) {
3284 bud_break_probability = probability_min;
3286 bud_break_probability = probability_max;
3290 bool bud_break =
true;
3291 if (context_ptr->
randu() > bud_break_probability) {
3299 std::string epicormic_shoot_label = plantarchitecture_ptr->plant_instances.at(this->plantID).epicormic_shoot_probability_perlength_per_day.first;
3301 if (epicormic_shoot_label.empty()) {
3305 float epicormic_probability = plantarchitecture_ptr->plant_instances.at(this->plantID).epicormic_shoot_probability_perlength_per_day.second;
3307 if (epicormic_probability == 0) {
3313 epicormic_positions_fraction.clear();
3319 float dta = std::min(time, 1.f);
3321 float shoot_fraction = context_ptr->
randu();
3325 bool new_shoot =
uint((epicormic_probability * shoot_length * dta * elevation > context_ptr->
randu()));
3327 Nshoots +=
uint(new_shoot);
3330 epicormic_positions_fraction.push_back(shoot_fraction);
3336 assert(epicormic_positions_fraction.size() == Nshoots);
3342 float radius_taper,
const std::string &shoot_type_label) {
3343 if (plant_instances.find(plantID) == plant_instances.end()) {
3344 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3345 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3346 helios_runtime_error(
"ERROR (PlantArchitecture::addBaseStemShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3349 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3351 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3352 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3354 if (current_node_number > shoot_parameters.max_nodes.val()) {
3355 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()) +
".");
3358 uint shootID = shoot_tree_ptr->size();
3359 vec3 base_position = plant_instances.at(plantID).base_position;
3362 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));
3363 shoot_tree_ptr->emplace_back(shoot_new);
3366 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3372 float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label) {
3373 if (plant_instances.find(plantID) == plant_instances.end()) {
3374 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3375 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3376 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3379 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3381 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3382 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3384 if (shoot_tree_ptr->empty()) {
3385 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Cannot append shoot to empty shoot. You must call addBaseStemShoot() first for each plant.");
3386 }
else if (parent_shoot_ID >=
int(shoot_tree_ptr->size())) {
3387 helios_runtime_error(
"ERROR (PlantArchitecture::appendShoot): Parent with ID of " + std::to_string(parent_shoot_ID) +
" does not exist.");
3388 }
else if (current_node_number > shoot_parameters.max_nodes.val()) {
3389 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()) +
".");
3390 }
else if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.empty()) {
3394 shoot_tree_ptr->at(parent_shoot_ID)->shoot_parameters.max_nodes = shoot_tree_ptr->at(parent_shoot_ID)->current_node_number;
3395 shoot_tree_ptr->at(parent_shoot_ID)->terminateApicalBud();
3398 int appended_shootID = int(shoot_tree_ptr->size());
3399 uint parent_node = shoot_tree_ptr->at(parent_shoot_ID)->current_node_number - 1;
3400 uint rank = shoot_tree_ptr->at(parent_shoot_ID)->rank;
3401 vec3 base_position = interpolateTube(shoot_tree_ptr->at(parent_shoot_ID)->phytomers.back()->getInternodeNodePositions(), 0.9f);
3404 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));
3405 shoot_tree_ptr->emplace_back(shoot_new);
3408 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3410 return appended_shootID;
3414 float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label,
uint petiole_index) {
3415 if (plant_instances.find(plantID) == plant_instances.end()) {
3416 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3417 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3418 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3421 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3423 if (parent_shoot_ID <= -1 || parent_shoot_ID >= shoot_tree_ptr->size()) {
3424 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Parent with ID of " + std::to_string(parent_shoot_ID) +
" does not exist.");
3425 }
else if (shoot_tree_ptr->at(parent_shoot_ID)->phytomers.size() <= parent_node_index) {
3426 helios_runtime_error(
"ERROR (PlantArchitecture::addChildShoot): Parent shoot does not have a node " + std::to_string(parent_node_index) +
".");
3430 auto shoot_parameters = plant_instances.at(plantID).shoot_types_snapshot.at(shoot_type_label);
3431 validateShootTypes(shoot_parameters, plant_instances.at(plantID).shoot_types_snapshot);
3432 uint parent_rank = (int) shoot_tree_ptr->at(parent_shoot_ID)->rank;
3433 int childID = int(shoot_tree_ptr->size());
3436 const auto parent_shoot_ptr = shoot_tree_ptr->at(parent_shoot_ID);
3438 vec3 shoot_base_position = parent_shoot_ptr->shoot_internode_vertices.at(parent_node_index).back();
3442 if (parent_shoot_ptr->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
3444 axis_vector = parent_shoot_ptr->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
3446 axis_vector = parent_shoot_ptr->phytomers.at(parent_node_index)->getPetioleAxisVector(0, petiole_index);
3448 shoot_base_position += 0.9f * axis_vector * parent_shoot_ptr->phytomers.at(parent_node_index)->getInternodeRadius(1.f);
3451 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));
3452 shoot_tree_ptr->emplace_back(shoot_new);
3455 shoot_new->buildShootPhytomers(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, radius_taper);
3461 float internode_length_scale_factor_fraction,
float leaf_scale_factor_fraction,
float radius_taper,
const std::string &shoot_type_label) {
3462 if (plant_instances.find(plantID) == plant_instances.end()) {
3463 helios_runtime_error(
"ERROR (PlantArchitecture::addEpicormicShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3464 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3465 helios_runtime_error(
"ERROR (PlantArchitecture::addEpicormicShoot): Shoot type with label of " + shoot_type_label +
" does not exist.");
3468 auto &parent_shoot = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID);
3470 uint parent_node_index = 0;
3471 if (parent_position_fraction > 0) {
3472 parent_node_index = std::ceil(parent_position_fraction *
float(parent_shoot->phytomers.size())) - 1;
3476 if (plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->petiole_vertices.empty()) {
3478 axis_vector = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->getInternodeAxisVector(1.f);
3480 axis_vector = plant_instances.at(plantID).shoot_tree.at(parent_shoot_ID)->phytomers.at(parent_node_index)->getPetioleAxisVector(0, 0);
3486 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);
3489void PlantArchitecture::validateShootTypes(
ShootParameters &shoot_parameters,
const std::map<std::string, ShootParameters> &shoot_types_ref)
const {
3490 assert(shoot_parameters.child_shoot_type_probabilities.size() == shoot_parameters.child_shoot_type_labels.size());
3492 for (
int ind = shoot_parameters.child_shoot_type_labels.size() - 1; ind >= 0; ind--) {
3493 if (shoot_types_ref.find(shoot_parameters.child_shoot_type_labels.at(ind)) == shoot_types_ref.end()) {
3494 shoot_parameters.child_shoot_type_labels.erase(shoot_parameters.child_shoot_type_labels.begin() + ind);
3495 shoot_parameters.child_shoot_type_probabilities.erase(shoot_parameters.child_shoot_type_probabilities.begin() + ind);
3501 float leaf_scale_factor_fraction) {
3502 if (plant_instances.find(plantID) == plant_instances.end()) {
3503 helios_runtime_error(
"ERROR (PlantArchitecture::appendPhytomerToShoot): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3506 auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
3508 if (shootID >= shoot_tree_ptr->size()) {
3509 helios_runtime_error(
"ERROR (PlantArchitecture::appendPhytomerToShoot): Parent with ID of " + std::to_string(shootID) +
" does not exist.");
3512 auto current_shoot_ptr = plant_instances.at(plantID).shoot_tree.at(shootID);
3514 int pID = current_shoot_ptr->appendPhytomer(internode_radius, internode_length_max, internode_length_scale_factor_fraction, leaf_scale_factor_fraction, phytomer_parameters);
3516 current_shoot_ptr->current_node_number++;
3517 current_shoot_ptr->nodes_this_season++;
3519 for (
auto &phytomers: current_shoot_ptr->phytomers) {
3520 phytomers->shoot_index.y = current_shoot_ptr->current_node_number;
3524 if (current_shoot_ptr->current_node_number == current_shoot_ptr->shoot_parameters.max_nodes.val()) {
3525 if (!current_shoot_ptr->shoot_parameters.flowers_require_dormancy && current_shoot_ptr->shoot_parameters.max_terminal_floral_buds.val() > 0) {
3526 current_shoot_ptr->addTerminalFloralBud();
3528 if (current_shoot_ptr->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function !=
nullptr) {
3529 state = BUD_FLOWER_CLOSED;
3530 }
else if (current_shoot_ptr->shoot_parameters.phytomer_parameters.inflorescence.fruit_prototype_function !=
nullptr) {
3531 state = BUD_FRUITING;
3535 for (
auto &fbuds: current_shoot_ptr->phytomers.back()->floral_buds) {
3536 for (
auto &fbud: fbuds) {
3537 if (fbud.isterminal) {
3539 current_shoot_ptr->phytomers.back()->updateInflorescence(fbud);
3547 else if (current_shoot_ptr->nodes_this_season >= current_shoot_ptr->shoot_parameters.max_nodes_per_season.val()) {
3548 if (!current_shoot_ptr->shoot_parameters.flowers_require_dormancy && current_shoot_ptr->shoot_parameters.max_terminal_floral_buds.val() > 0) {
3549 current_shoot_ptr->addTerminalFloralBud();
3550 for (
auto &fbuds: current_shoot_ptr->phytomers.back()->floral_buds) {
3551 for (
auto &fbud: fbuds) {
3552 if (fbud.isterminal) {
3553 fbud.state = BUD_DORMANT;
3554 current_shoot_ptr->phytomers.back()->updateInflorescence(fbud);
3559 current_shoot_ptr->phytomers.at(pID)->isdormant =
true;
3566 if (plant_instances.find(plantID) == plant_instances.end()) {
3567 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3568 }
else if (plant_instances.at(plantID).shoot_types_snapshot.find(epicormic_shoot_type_label) == plant_instances.at(plantID).shoot_types_snapshot.end()) {
3569 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Shoot type with label of " + epicormic_shoot_type_label +
" does not exist.");
3570 }
else if (epicormic_probability_perlength_perday < 0) {
3571 helios_runtime_error(
"ERROR (PlantArchitecture::enableEpicormicChildShoots): Epicormic probability must be greater than or equal to zero.");
3574 plant_instances.at(plantID).epicormic_shoot_probability_perlength_per_day = std::make_pair(epicormic_shoot_type_label, epicormic_probability_perlength_perday);
3578 build_context_geometry_internode =
false;
3582 build_context_geometry_petiole =
false;
3586 build_context_geometry_peduncle =
false;
3590 ground_clipping_height = ground_height;
3593void PlantArchitecture::incrementPhytomerInternodeGirth(
uint plantID,
uint shootID,
uint node_number,
float dt,
bool update_context_geometry) {
3594 if (plant_instances.find(plantID) == plant_instances.end()) {
3595 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3598 auto shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3600 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3601 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3602 }
else if (node_number >= shoot->current_node_number) {
3603 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.");
3606 auto phytomer = shoot->phytomers.at(node_number);
3608 float leaf_area = phytomer->downstream_leaf_area;
3611 context_ptr->
setObjectData(shoot->internode_tube_objID,
"leaf_area", leaf_area);
3613 float phytomer_age = phytomer->age;
3614 float girth_area_factor = shoot->shoot_parameters.girth_area_factor.val();
3615 if (phytomer_age > 365) {
3616 girth_area_factor = shoot->shoot_parameters.girth_area_factor.val() * 365 / phytomer_age;
3619 float internode_area = girth_area_factor * leaf_area * 1e-4;
3620 float phytomer_radius = sqrtf(internode_area /
PI_F);
3622 auto &segment = shoot->shoot_internode_radii.at(node_number);
3623 for (
float &radius: segment) {
3624 if (phytomer_radius > radius) {
3626 radius = radius + 0.5 * (phytomer_radius - radius);
3630 if (update_context_geometry && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
3631 context_ptr->
setTubeRadii(shoot->internode_tube_objID,
flatten(shoot->shoot_internode_radii));
3635void PlantArchitecture::pruneGroundCollisions(
uint plantID) {
3636 if (plant_instances.find(plantID) == plant_instances.end()) {
3637 helios_runtime_error(
"ERROR (PlantArchitecture::pruneGroundCollisions): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3640 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
3641 for (
auto &phytomer: shoot->phytomers) {
3643 if ((phytomer->shoot_index.x == 0 && phytomer->rank > 0) && context_ptr->
doesObjectExist(shoot->internode_tube_objID) && detectGroundCollision(shoot->internode_tube_objID)) {
3644 context_ptr->
deleteObject(shoot->internode_tube_objID);
3645 shoot->terminateApicalBud();
3649 for (
uint petiole = 0; petiole < phytomer->leaf_objIDs.size(); petiole++) {
3650 if (detectGroundCollision(phytomer->leaf_objIDs.at(petiole))) {
3651 phytomer->removeLeaf();
3656 for (
auto &petiole: phytomer->floral_buds) {
3657 for (
auto &fbud: petiole) {
3658 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
3659 uint objID = fbud.inflorescence_objIDs.at(p);
3660 if (detectGroundCollision(objID)) {
3662 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
3663 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
3666 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
3667 uint objID = fbud.peduncle_objIDs.at(p);
3668 if (detectGroundCollision(objID)) {
3671 fbud.peduncle_objIDs.clear();
3672 fbud.inflorescence_objIDs.clear();
3673 fbud.inflorescence_bases.clear();
3698 if (plant_instances.find(plantID) == plant_instances.end()) {
3699 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3702 auto parent_shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
3704 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
3705 helios_runtime_error(
"ERROR (PlantArchitecture::setPhytomerLeafScale): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
3706 }
else if (node_number >= parent_shoot->current_node_number) {
3707 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.");
3709 if (leaf_scale_factor_fraction < 0 || leaf_scale_factor_fraction > 1) {
3713 parent_shoot->phytomers.at(node_number)->setLeafScaleFraction(leaf_scale_factor_fraction);
3717 if (plant_instances.find(plantID) == plant_instances.end()) {
3718 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3721 plant_instances.at(plantID).base_position = base_position;
3724 if (!plant_instances.at(plantID).shoot_tree.empty()) {
3729 if (Beta_mu_inclination <= 0.f) {
3730 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3731 }
else if (Beta_nu_inclination <= 0.f) {
3732 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3735 setPlantLeafAngleDistribution_private({plantID}, Beta_mu_inclination, Beta_nu_inclination, 0.f, 0.f,
true,
false);
3739 if (Beta_mu_inclination <= 0.f) {
3740 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3741 }
else if (Beta_nu_inclination <= 0.f) {
3742 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafElevationAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3745 setPlantLeafAngleDistribution_private(plantIDs, Beta_mu_inclination, Beta_nu_inclination, 0.f, 0.f,
true,
false);
3749 if (eccentricity < 0.f || eccentricity > 1.f) {
3750 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAzimuthAngleDistribution): Eccentricity must be between 0 and 1.");
3753 setPlantLeafAngleDistribution_private({plantID}, 0.f, 0.f, eccentricity, ellipse_rotation_degrees,
false,
true);
3757 if (eccentricity < 0.f || eccentricity > 1.f) {
3758 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAzimuthAngleDistribution): Eccentricity must be between 0 and 1.");
3761 setPlantLeafAngleDistribution_private(plantIDs, 0.f, 0.f, eccentricity, ellipse_rotation_degrees,
false,
true);
3765 if (Beta_mu_inclination <= 0.f) {
3766 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): 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::setPlantLeafAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3769 }
else if (eccentricity < 0.f || eccentricity > 1.f) {
3770 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Eccentricity must be between 0 and 1.");
3773 setPlantLeafAngleDistribution_private({plantID}, Beta_mu_inclination, Beta_nu_inclination, eccentricity, ellipse_rotation_degrees,
true,
true);
3777 if (Beta_mu_inclination <= 0.f) {
3778 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_mu_inclination must be greater than or equal to zero.");
3779 }
else if (Beta_nu_inclination <= 0.f) {
3780 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Beta_nu_inclination must be greater than or equal to zero.");
3781 }
else if (eccentricity < 0.f || eccentricity > 1.f) {
3782 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Eccentricity must be between 0 and 1.");
3785 setPlantLeafAngleDistribution_private(plantIDs, Beta_mu_inclination, Beta_nu_inclination, eccentricity, ellipse_rotation_degrees,
true,
true);
3790 if (plant_instances.find(plantID) == plant_instances.end()) {
3791 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3792 }
else if (plant_instances.at(plantID).shoot_tree.empty()) {
3793 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantBasePosition): Plant with ID of " + std::to_string(plantID) +
" has no shoots, so could not get a base position.");
3795 return plant_instances.at(plantID).base_position;
3799 std::vector<vec3> positions;
3800 positions.reserve(plantIDs.size());
3801 for (
uint plantID: plantIDs) {
3808 if (plant_instances.find(plantID) == plant_instances.end()) {
3809 helios_runtime_error(
"ERROR (PlantArchitecture::sumPlantLeafArea): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3815 for (
uint objID: leaf_objIDs) {
3823 if (plant_instances.find(plantID) == plant_instances.end()) {
3824 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantStemHeight): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3827 auto base_shoot_ptr = plant_instances.at(plantID).shoot_tree.front();
3829 std::vector<uint> stem_objID{base_shoot_ptr->internode_tube_objID};
3832 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantStemHeight): The plant does not contain any geometry.");
3836 if (base_shoot_ptr->childIDs.find(base_shoot_ptr->current_node_number - 1) != base_shoot_ptr->childIDs.end()) {
3837 auto terminal_children = base_shoot_ptr->childIDs.at(base_shoot_ptr->current_node_number - 1);
3838 for (
uint childID: terminal_children) {
3839 auto child_shoot_ptr = plant_instances.at(plantID).shoot_tree.at(childID);
3840 if (child_shoot_ptr->rank == base_shoot_ptr->rank) {
3841 if (context_ptr->
doesObjectExist(child_shoot_ptr->internode_tube_objID)) {
3842 stem_objID.push_back(child_shoot_ptr->internode_tube_objID);
3852 return max_box.
z - min_box.
z;
3857 if (plant_instances.find(plantID) == plant_instances.end()) {
3858 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantHeight): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3865 return max_box.
z - min_box.
z;
3869 if (plant_instances.find(plantID) == plant_instances.end()) {
3870 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafInclinationAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3876 std::vector<float> leaf_inclination_angles(Nbins, 0.f);
3877 const float dtheta = 0.5f *
PI_F / float(Nbins);
3878 for (
const uint UUID: leaf_UUIDs) {
3880 const float theta =
acos_safe(fabs(normal.
z));
3882 uint bin =
static_cast<uint>(std::floor(theta / dtheta));
3886 if (!std::isnan(area)) {
3887 leaf_inclination_angles.at(bin) += area;
3894 for (
float &angle: leaf_inclination_angles) {
3900 return leaf_inclination_angles;
3904 std::vector<float> leaf_inclination_angles(Nbins, 0.f);
3905 for (
const uint plantID: plantIDs) {
3912 for (
float &angle: leaf_inclination_angles) {
3918 return leaf_inclination_angles;
3922 if (plant_instances.find(plantID) == plant_instances.end()) {
3923 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafAzimuthAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3929 std::vector<float> leaf_azimuth_angles(Nbins, 0.f);
3930 const float dtheta = 2.f *
PI_F /
static_cast<float>(Nbins);
3931 for (
const uint UUID: leaf_UUIDs) {
3935 uint bin =
static_cast<uint>(std::floor(phi / dtheta));
3939 if (!std::isnan(area)) {
3940 leaf_azimuth_angles.at(bin) += area;
3947 for (
float &angle: leaf_azimuth_angles) {
3953 return leaf_azimuth_angles;
3957 std::vector<float> leaf_azimuth_angles(Nbins, 0.f);
3958 for (
const uint plantID: plantIDs) {
3965 for (
float &angle: leaf_azimuth_angles) {
3971 return leaf_azimuth_angles;
3976 if (plant_instances.find(plantID) == plant_instances.end()) {
3977 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafCount): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3984 if (plant_instances.find(plantID) == plant_instances.end()) {
3985 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafBases): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
3988 std::vector<vec3> leaf_bases;
3991 size_t total_size = 0;
3992 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
3993 for (
const auto &phytomer: shoot->phytomers) {
3994 total_size += phytomer->leaf_bases.size() * phytomer->leaf_bases.front().size();
3997 leaf_bases.reserve(total_size);
4000 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
4001 for (
const auto &phytomer: shoot->phytomers) {
4002 std::vector<vec3> bases_flat =
flatten(phytomer->leaf_bases);
4003 leaf_bases.insert(leaf_bases.end(), bases_flat.begin(), bases_flat.end());
4011 std::vector<helios::vec3> leaf_bases;
4012 for (
const uint plantID: plantIDs) {
4014 leaf_bases.insert(leaf_bases.end(), bases.begin(), bases.end());
4020 if (plant_instances.find(plantID) == plant_instances.end()) {
4021 helios_runtime_error(
"ERROR (PlantArchitecture::isPlantDormant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4024 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
4025 if (!shoot->isdormant) {
4034 if (plant_instances.find(plantID) == plant_instances.end()) {
4035 helios_runtime_error(
"ERROR (PlantArchitecture::determinePhenologyStage): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4046 if (!flowers.empty() || !fruits.empty()) {
4047 return "reproductive";
4051 const auto &plant_instance = plant_instances.at(plantID);
4052 if (plant_instance.dd_to_dormancy > 0) {
4053 float senescence_threshold = plant_instance.dd_to_dormancy_break + plant_instance.dd_to_dormancy * 0.9f;
4054 if (plant_instance.time_since_dormancy > senescence_threshold) {
4060 return "vegetative";
4064 if (plant_instances.find(plantID) == plant_instances.end()) {
4065 helios_runtime_error(
"ERROR (PlantArchitecture::writePlantMeshVertices): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4071 file.open(filename);
4073 if (!file.is_open()) {
4074 helios_runtime_error(
"ERROR (PlantArchitecture::writePlantMeshVertices): Could not open file " + filename +
" for writing.");
4077 for (
uint UUID: plant_UUIDs) {
4079 for (
vec3 &v: vertex) {
4080 file << v.x <<
" " << v.y <<
" " << v.z << std::endl;
4093 if (plant_instances.find(plantID) == plant_instances.end()) {
4094 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantName): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4096 return plant_instances.at(plantID).plant_name;
4100 if (plant_instances.find(plantID) == plant_instances.end()) {
4101 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAge): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4102 }
else if (plant_instances.at(plantID).shoot_tree.empty()) {
4103 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAge): Plant with ID of " + std::to_string(plantID) +
" has no shoots, so could not get a base position.");
4105 return plant_instances.at(plantID).current_age;
4110 if (plant_instances.find(plantID) == plant_instances.end()) {
4111 helios_runtime_error(
"ERROR (PlantArchitecture::listShootTypeLabels): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4115 const auto &shoot_types_snap = plant_instances.at(plantID).shoot_types_snapshot;
4118 std::vector<std::string> labels;
4119 labels.reserve(shoot_types_snap.size());
4120 for (
const auto &pair: shoot_types_snap) {
4121 labels.push_back(pair.first);
4128 if (plant_instances.find(plantID) == plant_instances.end()) {
4129 helios_runtime_error(
"ERROR (PlantArchitecture::harvestPlant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4132 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4133 for (
auto &phytomer: shoot->phytomers) {
4134 for (
auto &petiole: phytomer->floral_buds) {
4135 for (
auto &fbud: petiole) {
4136 if (fbud.state != BUD_DORMANT) {
4137 phytomer->setFloralBudState(BUD_DEAD, fbud);
4146 if (plant_instances.find(plantID) == plant_instances.end()) {
4147 helios_runtime_error(
"ERROR (PlantArchitecture::removePlantLeaves): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4150 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4151 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootLeaves): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
4154 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4156 for (
auto &phytomer: shoot->phytomers) {
4157 phytomer->removeLeaf();
4162 if (plant_instances.find(plantID) == plant_instances.end()) {
4163 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootVegetativeBuds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4166 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4167 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootVegetativeBuds): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
4170 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4172 for (
auto &phytomer: shoot->phytomers) {
4173 phytomer->setVegetativeBudState(BUD_DEAD);
4178 if (plant_instances.find(plantID) == plant_instances.end()) {
4179 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootFloralBuds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4182 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4183 helios_runtime_error(
"ERROR (PlantArchitecture::removeShootFloralBuds): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
4186 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4188 for (
auto &phytomer: shoot->phytomers) {
4189 phytomer->setFloralBudState(BUD_DEAD);
4194 if (plant_instances.find(plantID) == plant_instances.end()) {
4195 helios_runtime_error(
"ERROR (PlantArchitecture::removePlantLeaves): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4198 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4199 for (
auto &phytomer: shoot->phytomers) {
4200 phytomer->removeLeaf();
4206 if (plant_instances.find(plantID) == plant_instances.end()) {
4207 helios_runtime_error(
"ERROR (PlantArchitecture::makePlantDormant): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4210 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4211 shoot->makeDormant();
4213 plant_instances.at(plantID).time_since_dormancy = 0;
4217 if (plant_instances.find(plantID) == plant_instances.end()) {
4218 helios_runtime_error(
"ERROR (PlantArchitecture::breakPlantDormancy): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4221 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
4222 shoot->breakDormancy();
4227 if (plant_instances.find(plantID) == plant_instances.end()) {
4228 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4229 }
else if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
4230 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Shoot with ID of " + std::to_string(shootID) +
" does not exist on plant " + std::to_string(plantID) +
".");
4231 }
else if (node_index >= plant_instances.at(plantID).shoot_tree.at(shootID)->current_node_number) {
4232 helios_runtime_error(
"ERROR (PlantArchitecture::pruneBranch): Node index " + std::to_string(node_index) +
" is out of range for shoot " + std::to_string(shootID) +
".");
4235 auto &shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
4237 shoot->phytomers.at(node_index)->deletePhytomer();
4239 if (plant_instances.at(plantID).shoot_tree.empty()) {
4240 std::cout <<
"WARNING (PlantArchitecture::pruneBranch): Plant " << plantID <<
" base shoot was pruned." << std::endl;
4245static vec3 orthonormal_axis(
const vec3 &v) {
4254static vec3 rodrigues(
const vec3 &v,
const vec3 &k,
float a) {
4255 float c = std::cos(a);
4256 float s = std::sin(a);
4259 return v * c +
cross(k, v) * s + k * (kv * (1.f - c));
4262void 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,
4263 bool set_azimuth)
const {
4264 for (
uint plantID: plantIDs) {
4265 if (plant_instances.find(plantID) == plant_instances.end()) {
4266 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantLeafAngleDistribution): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4273 size_t N = objIDs.size();
4274 assert(bases.size() == N);
4275 if (N == 0 || (!set_elevation && !set_azimuth))
4279 std::vector<float> theta(N), phi(N), theta_t(N), phi_t(N);
4280 for (
size_t i = 0; i < N; ++i) {
4283 if (!std::isfinite(n0.
x) || !std::isfinite(n0.
y) || !std::isfinite(n0.
z) || n0.
magnitude() < 1e-6f) {
4284 n0 =
vec3(0.f, 0.f, 1.f);
4294 if (set_elevation && !set_azimuth) {
4297 }
else if (!set_elevation && set_azimuth) {
4298 theta_t[i] = theta[i];
4308 if (set_elevation && !set_azimuth) {
4310 for (
size_t i = 0; i < N; ++i) {
4311 float elev =
PI_F * 0.5f - theta_t[i];
4317 if (!set_elevation && set_azimuth) {
4319 for (
size_t i = 0; i < N; ++i) {
4320 float elev =
PI_F * 0.5f - theta[i];
4328 std::vector<vec3> V0(N), V1(N);
4329 for (
size_t i = 0; i < N; ++i) {
4330 float e0 =
PI_F * 0.5f - theta[i];
4331 float e1 =
PI_F * 0.5f - theta_t[i];
4337 std::vector<int> assignment(N);
4340 std::vector<std::vector<double>> C(N, std::vector<double>(N));
4341 for (
size_t i = 0; i < N; ++i) {
4342 for (
size_t j = 0; j < N; ++j) {
4343 double d = (V0[i] - V1[j]).magnitude();
4344 C[i][j] = std::isfinite(d) ? d : ((std::numeric_limits<double>::max)() * 0.5);
4347 hung.Solve(C, assignment);
4351 for (
size_t i = 0; i < N; ++i) {
4352 int j = assignment[i];
4355 vec3 u = (j >= 0 && j < (int) N ? V1[j] : V0[i]);
4358 v = (v.
magnitude() < 1e-6f ?
vec3(0, 0, 1) : v.normalize());
4359 u = (u.
magnitude() < 1e-6f ?
vec3(0, 0, 1) : u.normalize());
4362 float dot = std::clamp(v * u, -1.f, 1.f);
4367 if (!set_elevation && set_azimuth) {
4369 axis =
vec3(0.f, 0.f, 1.f);
4372 axis = orthonormal_axis(v);
4378 vec3 r = rodrigues(v, axis, ang);
4379 if (!std::isfinite(r.
x) || !std::isfinite(r.
y) || !std::isfinite(r.
z) || r.
magnitude() < 1e-6f) {
4499 if (plant_instances.find(plantID) == plant_instances.end()) {
4500 helios_runtime_error(
"ERROR (PlantArchitecture::getShootNodeCount): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4501 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
4502 helios_runtime_error(
"ERROR (PlantArchitecture::getShootNodeCount): Shoot ID is out of range.");
4504 return plant_instances.at(plantID).shoot_tree.at(shootID)->current_node_number;
4508 if (plant_instances.find(plantID) == plant_instances.end()) {
4509 helios_runtime_error(
"ERROR (PlantArchitecture::getShootTaper): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4510 }
else if (plant_instances.at(plantID).shoot_tree.size() <= shootID) {
4514 float r0 = plant_instances.at(plantID).shoot_tree.at(shootID)->shoot_internode_radii.front().front();
4515 float r1 = plant_instances.at(plantID).shoot_tree.at(shootID)->shoot_internode_radii.back().back();
4517 float taper = (r0 - r1) / r0;
4520 }
else if (taper > 1) {
4528 std::vector<uint> objIDs;
4529 objIDs.reserve(plant_instances.size());
4531 for (
const auto &plant: plant_instances) {
4532 objIDs.push_back(plant.first);
4539 if (plant_instances.find(plantID) == plant_instances.end()) {
4540 helios_runtime_error(
"ERROR (PlantArchitecture::getAllPlantObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4543 std::vector<uint> objIDs;
4545 for (
const auto &shoot: plant_instances.at(plantID).shoot_tree) {
4547 objIDs.push_back(shoot->internode_tube_objID);
4549 for (
const auto &phytomer: shoot->phytomers) {
4550 std::vector<uint> petiole_objIDs_flat =
flatten(phytomer->petiole_objIDs);
4551 objIDs.insert(objIDs.end(), petiole_objIDs_flat.begin(), petiole_objIDs_flat.end());
4552 std::vector<uint> leaf_objIDs_flat =
flatten(phytomer->leaf_objIDs);
4553 objIDs.insert(objIDs.end(), leaf_objIDs_flat.begin(), leaf_objIDs_flat.end());
4554 for (
auto &petiole: phytomer->floral_buds) {
4555 for (
auto &fbud: petiole) {
4556 std::vector<uint> inflorescence_objIDs_flat = fbud.inflorescence_objIDs;
4557 objIDs.insert(objIDs.end(), inflorescence_objIDs_flat.begin(), inflorescence_objIDs_flat.end());
4558 std::vector<uint> peduncle_objIDs_flat = fbud.peduncle_objIDs;
4559 objIDs.insert(objIDs.end(), peduncle_objIDs_flat.begin(), peduncle_objIDs_flat.end());
4568std::vector<uint> PlantArchitecture::getAllPrototypeObjectIDs()
const {
4569 std::vector<uint> objIDs;
4570 for (
const auto &[key, prototype_vec] : unique_leaf_prototype_objIDs) {
4571 for (
const auto &leaflet_vec : prototype_vec) {
4572 for (
uint objID : leaflet_vec) {
4574 objIDs.push_back(objID);
4579 for (
const auto &[key, prototype_vec] : unique_closed_flower_prototype_objIDs) {
4580 for (
uint objID : prototype_vec) {
4582 objIDs.push_back(objID);
4586 for (
const auto &[key, prototype_vec] : unique_open_flower_prototype_objIDs) {
4587 for (
uint objID : prototype_vec) {
4589 objIDs.push_back(objID);
4593 for (
const auto &[key, prototype_vec] : unique_fruit_prototype_objIDs) {
4594 for (
uint objID : prototype_vec) {
4596 objIDs.push_back(objID);
4603void PlantArchitecture::deleteAllPrototypes() {
4604 std::vector<uint> prototype_objIDs = getAllPrototypeObjectIDs();
4605 for (
uint objID : prototype_objIDs) {
4608 unique_leaf_prototype_objIDs.clear();
4609 unique_open_flower_prototype_objIDs.clear();
4610 unique_closed_flower_prototype_objIDs.clear();
4611 unique_fruit_prototype_objIDs.clear();
4616 if (include_hidden) {
4617 std::vector<uint> prototype_objIDs = getAllPrototypeObjectIDs();
4618 objIDs.insert(objIDs.end(), prototype_objIDs.begin(), prototype_objIDs.end());
4624 if (plant_instances.find(plantID) == plant_instances.end()) {
4625 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4628 std::vector<uint> objIDs;
4630 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4632 for (
auto &shoot: shoot_tree) {
4634 objIDs.push_back(shoot->internode_tube_objID);
4642 if (plant_instances.find(plantID) == plant_instances.end()) {
4643 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4646 std::vector<uint> objIDs;
4648 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4650 bool shoot_type_found =
false;
4651 for (
auto &shoot: shoot_tree) {
4652 if (shoot->shoot_type_label == shoot_type_label) {
4653 shoot_type_found =
true;
4655 objIDs.push_back(shoot->internode_tube_objID);
4660 if (!shoot_type_found) {
4661 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInternodeObjectIDs): No shoots with shoot type label '" + shoot_type_label +
"' exist for plant with ID " + std::to_string(plantID) +
".");
4668 if (plant_instances.find(plantID) == plant_instances.end()) {
4669 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantPetioleObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4672 std::vector<uint> objIDs;
4674 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4676 for (
auto &shoot: shoot_tree) {
4677 for (
auto &phytomer: shoot->phytomers) {
4678 for (
auto &petiole: phytomer->petiole_objIDs) {
4679 objIDs.insert(objIDs.end(), petiole.begin(), petiole.end());
4688 if (plant_instances.find(plantID) == plant_instances.end()) {
4689 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantLeafObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4692 std::vector<uint> objIDs;
4694 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4696 for (
auto &shoot: shoot_tree) {
4697 for (
auto &phytomer: shoot->phytomers) {
4698 for (
auto &leaf_objID: phytomer->leaf_objIDs) {
4699 objIDs.insert(objIDs.end(), leaf_objID.begin(), leaf_objID.end());
4708 std::vector<uint> objIDs;
4709 objIDs.reserve(50 * plantIDs.size());
4710 for (
const uint plantID: plantIDs) {
4712 objIDs.insert(objIDs.end(), leaf_objIDs.begin(), leaf_objIDs.end());
4718 if (plant_instances.find(plantID) == plant_instances.end()) {
4719 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantPeduncleObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4722 std::vector<uint> objIDs;
4724 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4726 for (
auto &shoot: shoot_tree) {
4727 for (
auto &phytomer: shoot->phytomers) {
4728 for (
auto &petiole: phytomer->floral_buds) {
4729 for (
auto &fbud: petiole) {
4730 objIDs.insert(objIDs.end(), fbud.peduncle_objIDs.begin(), fbud.peduncle_objIDs.end());
4740 if (plant_instances.find(plantID) == plant_instances.end()) {
4741 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): 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 (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4751 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4752 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FLOWER_OPEN || phytomer->floral_buds.at(petiole).at(bud).state == BUD_FLOWER_CLOSED) {
4753 objIDs.insert(objIDs.end(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.begin(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.end());
4764 if (plant_instances.find(plantID) == plant_instances.end()) {
4765 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4768 std::vector<uint> objIDs;
4770 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4772 for (
auto &shoot: shoot_tree) {
4773 for (
auto &phytomer: shoot->phytomers) {
4774 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4775 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4776 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FRUITING) {
4777 objIDs.insert(objIDs.end(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.begin(), phytomer->floral_buds.at(petiole).at(bud).inflorescence_objIDs.end());
4789 if (plant_instances.find(plantID) == plant_instances.end()) {
4790 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4793 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4795 for (
const auto& shoot : shoot_tree) {
4797 int fruit_count = 0;
4799 for (
const auto& phytomer : shoot->phytomers) {
4800 for (
int petiole = 0; petiole < phytomer->floral_buds.size(); petiole++) {
4801 for (
int bud = 0; bud < phytomer->floral_buds.at(petiole).size(); bud++) {
4802 if (phytomer->floral_buds.at(petiole).at(bud).state == BUD_FRUITING) {
4810 context_ptr->
setObjectData(shoot->internode_tube_objID,
"fruit_count", fruit_count);
4818 if (plant_instances.find(plantID) == plant_instances.end()) {
4819 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantInflorescenceObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4822 std::vector<uint> objIDs;
4824 auto &shoot_tree = plant_instances.at(plantID).shoot_tree;
4826 for (
auto &shoot: shoot_tree) {
4827 objIDs.push_back(shoot->internode_tube_objID);
4836 if (plant_instances.find(plantID) == plant_instances.end()) {
4837 helios_runtime_error(
"ERROR (PlantArchitecture::getPlantCollisionRelevantObjectIDs): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4840 std::vector<uint> collision_relevant_objects;
4845 if (collision_include_internodes) {
4847 collision_relevant_objects.insert(collision_relevant_objects.end(), internodes.begin(), internodes.end());
4851 if (collision_include_leaves) {
4853 collision_relevant_objects.insert(collision_relevant_objects.end(), leaves.begin(), leaves.end());
4857 if (collision_include_petioles) {
4859 collision_relevant_objects.insert(collision_relevant_objects.end(), petioles.begin(), petioles.end());
4863 if (collision_include_flowers) {
4865 collision_relevant_objects.insert(collision_relevant_objects.end(), flowers.begin(), flowers.end());
4869 if (collision_include_fruit) {
4871 collision_relevant_objects.insert(collision_relevant_objects.end(), fruit.begin(), fruit.end());
4874 return collision_relevant_objects;
4878 std::vector<uint> UUIDs_all;
4879 for (
const auto &instance: plant_instances) {
4881 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4887 std::vector<uint> UUIDs_all;
4888 for (
const auto &instance: plant_instances) {
4891 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4897 std::vector<uint> UUIDs_all;
4898 for (
const auto &instance: plant_instances) {
4901 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4907 std::vector<uint> UUIDs_all;
4908 for (
const auto &instance: plant_instances) {
4911 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4917 std::vector<uint> UUIDs_all;
4918 for (
const auto &instance: plant_instances) {
4921 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4927 std::vector<uint> UUIDs_all;
4928 for (
const auto &instance: plant_instances) {
4931 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4937 std::vector<uint> UUIDs_all;
4938 for (
const auto &instance: plant_instances) {
4941 UUIDs_all.insert(UUIDs_all.end(), UUIDs.begin(), UUIDs.end());
4947 std::vector<uint> objIDs_all;
4948 for (
const auto &instance: plant_instances) {
4950 objIDs_all.insert(objIDs_all.end(), objIDs.begin(), objIDs.end());
4956 carbon_model_enabled =
true;
4960 carbon_model_enabled =
false;
4964 if (current_age < 0) {
4965 helios_runtime_error(
"ERROR (PlantArchitecture::addPlantInstance): Current age must be greater than or equal to zero.");
4968 PlantInstance instance(base_position, current_age,
"custom", context_ptr);
4970 plant_instances.emplace(plant_count, instance);
4973 plant_instances.at(plant_count).shoot_types_snapshot = shoot_types;
4977 return plant_count - 1;
4981 if (plant_instances.find(plantID) == plant_instances.end()) {
4982 helios_runtime_error(
"ERROR (PlantArchitecture::duplicatePlantInstance): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
4985 auto plant_shoot_tree = &plant_instances.at(plantID).shoot_tree;
4990 plant_instances.at(plantID_new).shoot_types_snapshot = plant_instances.at(plantID).shoot_types_snapshot;
4992 if (plant_shoot_tree->empty()) {
4996 if (plant_shoot_tree->front()->phytomers.empty()) {
5001 for (
const auto &shoot: *plant_shoot_tree) {
5002 uint shootID_new = 0;
5003 for (
int node = 0; node < shoot->current_node_number; node++) {
5004 auto phytomer = shoot->phytomers.at(node);
5005 float internode_radius = phytomer->internode_radius_initial;
5006 float internode_length_max = phytomer->internode_length_max;
5007 float internode_scale_factor_fraction = phytomer->current_internode_scale_factor;
5008 float leaf_scale_factor_fraction = 1.f;
5012 AxisRotation original_base_rotation = shoot->base_rotation;
5013 if (shoot->parent_shoot_ID == -1) {
5015 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);
5018 uint parent_node = plant_shoot_tree->at(shoot->parent_shoot_ID)->parent_node_index;
5019 uint parent_petiole_index = 0;
5020 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
5021 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,
5022 shoot->shoot_type_label, parent_petiole_index);
5023 parent_petiole_index++;
5028 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,
5029 leaf_scale_factor_fraction);
5031 auto phytomer_new = plant_instances.at(plantID_new).shoot_tree.at(shootID_new)->phytomers.back();
5032 for (
uint petiole_index = 0; petiole_index < phytomer->petiole_objIDs.size(); petiole_index++) {
5033 phytomer_new->setLeafScaleFraction(petiole_index, phytomer->current_leaf_scale_factor.at(petiole_index));
5042 if (plant_instances.find(plantID) == plant_instances.end()) {
5048 plant_instances.erase(plantID);
5050 if (plant_instances.empty()) {
5051 deleteAllPrototypes();
5056 for (
uint ID: plantIDs) {
5062 float max_leaf_lifespan,
bool is_evergreen) {
5063 if (plant_instances.find(plantID) == plant_instances.end()) {
5064 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantPhenologicalThresholds): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
5067 plant_instances.at(plantID).dd_to_dormancy_break = time_to_dormancy_break;
5068 plant_instances.at(plantID).dd_to_flower_initiation = time_to_flower_initiation;
5069 plant_instances.at(plantID).dd_to_flower_opening = time_to_flower_opening;
5070 plant_instances.at(plantID).dd_to_fruit_set = time_to_fruit_set;
5071 plant_instances.at(plantID).dd_to_fruit_maturity = time_to_fruit_maturity;
5072 plant_instances.at(plantID).dd_to_dormancy = time_to_dormancy;
5073 if (max_leaf_lifespan == 0) {
5074 plant_instances.at(plantID).max_leaf_lifespan = 1e6;
5076 plant_instances.at(plantID).max_leaf_lifespan = max_leaf_lifespan;
5078 plant_instances.at(plantID).is_evergreen = is_evergreen;
5082 if (plant_instances.find(plantID) == plant_instances.end()) {
5083 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantCarbohydrateModelParameters): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
5086 plant_instances.at(plantID).carb_parameters = carb_parameters;
5090 for (
uint plantID: plantIDs) {
5096 plant_instances.at(plantID).dd_to_dormancy_break = 0;
5097 plant_instances.at(plantID).dd_to_flower_initiation = -1;
5098 plant_instances.at(plantID).dd_to_flower_opening = -1;
5099 plant_instances.at(plantID).dd_to_fruit_set = -1;
5100 plant_instances.at(plantID).dd_to_fruit_maturity = -1;
5101 plant_instances.at(plantID).dd_to_dormancy = 1e6;
5113 std::vector<uint> plantIDs = {plantID};
5118 for (
uint plantID: plantIDs) {
5119 if (plant_instances.find(plantID) == plant_instances.end()) {
5120 helios_runtime_error(
"ERROR (PlantArchitecture::advanceTime): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
5128 if (collision_detection_enabled && collision_detection_ptr !=
nullptr) {
5129 rebuildBVHForTimestep();
5133 float phyllochron_min = 9999;
5134 for (
uint plantID: plantIDs) {
5135 PlantInstance &plant_instance = plant_instances.at(plantID);
5136 auto shoot_tree = &plant_instance.shoot_tree;
5137 if (shoot_tree->empty()) {
5140 float phyllochron_min_shoot = shoot_tree->front()->shoot_parameters.phyllochron_min.val();
5141 if (phyllochron_min_shoot < phyllochron_min) {
5142 phyllochron_min = phyllochron_min_shoot;
5144 for (
int i = 1; i < shoot_tree->size(); i++) {
5145 if (shoot_tree->at(i)->shoot_parameters.phyllochron_min.val() < phyllochron_min) {
5146 phyllochron_min_shoot = shoot_tree->at(i)->shoot_parameters.phyllochron_min.val();
5147 if (phyllochron_min_shoot < phyllochron_min) {
5148 phyllochron_min = phyllochron_min_shoot;
5153 if (phyllochron_min == 9999) {
5158 if (carbon_model_enabled) {
5159 accumulateShootPhotosynthesis();
5165 if (time_step_days <= phyllochron_min) {
5166 Nsteps = time_step_days;
5169 Nsteps = std::floor(time_step_days / phyllochron_min);
5170 dt_max_days = phyllochron_min;
5173 float remainder_time = time_step_days - dt_max_days * float(Nsteps);
5174 if (remainder_time > 0.f) {
5179 helios::ProgressBar progress_bar(Nsteps, 50, Nsteps > 1 && printmessages,
"Advancing time");
5180 if (progress_callback) {
5184 for (
int timestep = 0; timestep < Nsteps; timestep++) {
5187 bool should_rebuild_bvh =
false;
5188 if (collision_detection_enabled && collision_detection_ptr !=
nullptr) {
5192 should_rebuild_bvh = (timestep % 25 == 0);
5194 should_rebuild_bvh = (timestep % 10 == 0);
5198 if (should_rebuild_bvh) {
5199 rebuildBVHForTimestep();
5203 for (
uint plantID: plantIDs) {
5205 if (!plant_primitives.empty()) {
5206 collision_detection_ptr->
registerTree(plantID, plant_primitives);
5212 if (timestep == Nsteps - 1 && remainder_time != 0.f) {
5213 dt_max_days = remainder_time;
5216 for (
uint plantID: plantIDs) {
5217 PlantInstance &plant_instance = plant_instances.at(plantID);
5219 auto shoot_tree = &plant_instance.shoot_tree;
5221 if (shoot_tree->empty()) {
5225 if (plant_instance.current_age <= plant_instance.max_age && plant_instance.current_age + dt_max_days > plant_instance.max_age) {
5226 }
else if (plant_instance.current_age >= plant_instance.max_age) {
5228 shoot_tree->front()->updateShootNodes(
true);
5232 plant_instance.current_age += dt_max_days;
5233 plant_instance.time_since_dormancy += dt_max_days;
5235 if (plant_instance.time_since_dormancy > plant_instance.dd_to_dormancy_break + plant_instance.dd_to_dormancy) {
5236 plant_instance.time_since_dormancy = 0;
5237 for (
const auto &shoot: *shoot_tree) {
5238 shoot->makeDormant();
5239 shoot->phyllochron_counter = 0;
5245 size_t shoot_count = shoot_tree->size();
5246 for (
int i = 0; i < shoot_count; i++) {
5247 auto shoot = shoot_tree->at(i);
5249 for (
auto &phytomer: shoot->phytomers) {
5250 phytomer->age += dt_max_days;
5252 if (phytomer->phytomer_parameters.phytomer_callback_function !=
nullptr) {
5253 phytomer->phytomer_parameters.phytomer_callback_function(phytomer);
5260 if (shoot->isdormant && plant_instance.time_since_dormancy >= plant_instance.dd_to_dormancy_break) {
5261 shoot->phyllochron_counter = 0;
5262 shoot->breakDormancy();
5265 if (shoot->isdormant) {
5270 for (
auto &phytomer: shoot->phytomers) {
5271 if (phytomer->age > plant_instance.max_leaf_lifespan) {
5273 phytomer->removeLeaf();
5276 if (phytomer->floral_buds.empty()) {
5281 for (
auto &petiole: phytomer->floral_buds) {
5282 for (
auto &fbud: petiole) {
5283 if (fbud.state != BUD_DORMANT && fbud.state != BUD_DEAD) {
5284 fbud.time_counter += dt_max_days;
5286 if (fbud.state != BUD_ACTIVE) {
5287 fbud.age += dt_max_days;
5292 if (shoot->shoot_parameters.phytomer_parameters.inflorescence.flower_prototype_function !=
nullptr) {
5295 if (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation >= 0.f) {
5297 if ((!shoot->shoot_parameters.flowers_require_dormancy && fbud.time_counter >= plant_instance.dd_to_flower_initiation) ||
5298 (shoot->shoot_parameters.flowers_require_dormancy && fbud.time_counter >= plant_instance.dd_to_flower_initiation)) {
5299 fbud.time_counter = 0;
5300 if (context_ptr->
randu() < shoot->shoot_parameters.flower_bud_break_probability.val()) {
5301 phytomer->setFloralBudState(BUD_FLOWER_CLOSED, fbud);
5303 phytomer->setFloralBudState(BUD_DEAD, fbud);
5305 if (shoot->shoot_parameters.determinate_shoot_growth) {
5306 shoot->terminateApicalBud();
5307 shoot->terminateAxillaryVegetativeBuds();
5312 }
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)) {
5313 if (fbud.time_counter >= plant_instance.dd_to_flower_opening) {
5314 fbud.time_counter = 0;
5315 if (fbud.state == BUD_FLOWER_CLOSED) {
5316 phytomer->setFloralBudState(BUD_FLOWER_OPEN, fbud);
5318 if (context_ptr->
randu() < shoot->shoot_parameters.flower_bud_break_probability.val()) {
5319 phytomer->setFloralBudState(BUD_FLOWER_OPEN, fbud);
5321 phytomer->setFloralBudState(BUD_DEAD, fbud);
5324 if (shoot->shoot_parameters.determinate_shoot_growth) {
5325 shoot->terminateApicalBud();
5326 shoot->terminateAxillaryVegetativeBuds();
5334 if (shoot->shoot_parameters.phytomer_parameters.inflorescence.fruit_prototype_function !=
nullptr) {
5335 if ((fbud.state == BUD_FLOWER_OPEN && plant_instance.dd_to_fruit_set >= 0.f) ||
5337 (fbud.state == BUD_ACTIVE && plant_instance.dd_to_flower_initiation < 0.f &&
5338 (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) ||
5340 (fbud.state == BUD_FLOWER_CLOSED && plant_instance.dd_to_flower_opening < 0.f && plant_instance.dd_to_fruit_set >= 0.f)) {
5342 if (fbud.time_counter >= plant_instance.dd_to_fruit_set) {
5343 fbud.time_counter = 0;
5345 float fruit_set_prob = shoot->shoot_parameters.fruit_set_probability.val();
5346 if (fbud.state == BUD_ACTIVE) {
5348 fruit_set_prob *= shoot->shoot_parameters.flower_bud_break_probability.val();
5350 if (context_ptr->
randu() < fruit_set_prob) {
5351 phytomer->setFloralBudState(BUD_FRUITING, fbud);
5353 phytomer->setFloralBudState(BUD_DEAD, fbud);
5355 if (shoot->shoot_parameters.determinate_shoot_growth) {
5356 shoot->terminateApicalBud();
5357 shoot->terminateAxillaryVegetativeBuds();
5369 for (
auto &phytomer: shoot->phytomers) {
5371 if (phytomer->current_internode_scale_factor < 1) {
5372 float dL_internode = dt_max_days * shoot->elongation_rate_instantaneous * phytomer->internode_length_max;
5373 float length_scale = fmin(1.f, (phytomer->getInternodeLength() + dL_internode) / phytomer->internode_length_max);
5374 phytomer->setInternodeLengthScaleFraction(length_scale,
false);
5378 if (shoot->shoot_parameters.girth_area_factor.val() > 0.f) {
5379 if (carbon_model_enabled) {
5380 incrementPhytomerInternodeGirth_carb(plantID, shoot->ID, node_index, dt_max_days,
false);
5382 incrementPhytomerInternodeGirth(plantID, shoot->ID, node_index, dt_max_days,
false);
5390 for (
auto &phytomer: shoot->phytomers) {
5392 if (phytomer->hasLeaf()) {
5393 for (
uint petiole_index = 0; petiole_index < phytomer->current_leaf_scale_factor.size(); petiole_index++) {
5394 if (phytomer->current_leaf_scale_factor.at(petiole_index) >= 1) {
5405 float tip_ind = ceil(
float(phytomer->leaf_size_max.at(petiole_index).size() - 1) / 2.f);
5406 float leaf_length = phytomer->current_leaf_scale_factor.at(petiole_index) * phytomer->leaf_size_max.at(petiole_index).at(tip_ind);
5407 float dL_leaf = dt_max_days * shoot->elongation_rate_instantaneous * phytomer->leaf_size_max.at(petiole_index).at(tip_ind);
5408 float leaf_scale = fmin(1.f, (leaf_length + dL_leaf) / phytomer->phytomer_parameters.leaf.prototype_scale.val());
5412 float scale = fmin(1.f, (leaf_length + dL_leaf) / phytomer->phytomer_parameters.leaf.prototype_scale.val());
5413 phytomer->phytomer_parameters.leaf.prototype_scale.resample();
5414 phytomer->setLeafScaleFraction(petiole_index, scale);
5419 for (
auto &petiole: phytomer->floral_buds) {
5420 for (
auto &fbud: petiole) {
5422 if (fbud.state == BUD_FRUITING && fbud.time_counter > 0) {
5424 fbud.previous_fruit_scale_factor = fbud.current_fruit_scale_factor;
5425 float scale = fmin(1, 0.25f + 0.75f * fbud.time_counter / plant_instance.dd_to_fruit_maturity);
5426 phytomer->setInflorescenceScaleFraction(fbud, scale);
5432 uint parent_petiole_index = 0;
5433 for (
auto &petiole: phytomer->axillary_vegetative_buds) {
5434 for (
auto &vbud: petiole) {
5435 if (vbud.state == BUD_ACTIVE && phytomer->age + dt_max_days > shoot->shoot_parameters.vegetative_bud_break_time.val()) {
5437 int parent_node_count = shoot->current_node_number;
5444 new_shoot_parameters->
base_yaw.resample();
5450 float internode_length_max;
5458 float internode_radius = phytomer->internode_radius_initial;
5460 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);
5462 phytomer->setVegetativeBudState(BUD_DEAD, vbud);
5463 vbud.shoot_ID = childID;
5464 shoot_tree->at(childID)->isdormant =
false;
5467 parent_petiole_index++;
5474 if (shoot->current_node_number >= shoot->shoot_parameters.max_nodes.val()) {
5475 shoot->terminateApicalBud();
5479 if (!shoot->meristem_is_alive) {
5484 shoot->phyllochron_counter += dt_max_days;
5485 if (shoot->phyllochron_counter >= shoot->phyllochron_instantaneous && !shoot->phytomers.back()->isdormant) {
5486 float internode_radius = shoot->shoot_parameters.phytomer_parameters.internode.radius_initial.val();
5487 shoot->shoot_parameters.phytomer_parameters.internode.radius_initial.resample();
5488 float internode_length_max = shoot->internode_length_max_shoot_initial;
5491 shoot->phyllochron_counter = shoot->phyllochron_counter - shoot->phyllochron_instantaneous;
5495 std::string epicormic_shoot_label = plant_instance.epicormic_shoot_probability_perlength_per_day.first;
5496 if (!epicormic_shoot_label.empty()) {
5497 std::vector<float> epicormic_fraction;
5498 uint Nepicormic = shoot->sampleEpicormicShoot(time_step_days, epicormic_fraction);
5499 for (
int s = 0; s < Nepicormic; s++) {
5500 float internode_radius = plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).phytomer_parameters.internode.radius_initial.val();
5501 plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).phytomer_parameters.internode.radius_initial.resample();
5502 float internode_length_max = plant_instance.
shoot_types_snapshot.at(epicormic_shoot_label).internode_length_max.val();
5504 addEpicormicShoot(plantID, shoot->ID, epicormic_fraction.at(s), 1, 0, internode_radius, internode_length_max, 0.01, 0.01, 0, epicormic_shoot_label);
5507 if (carbon_model_enabled) {
5508 if (output_object_data.find(
"carbohydrate_concentration") != output_object_data.end() && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
5509 float shoot_volume = shoot->calculateShootInternodeVolume();
5510 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", shoot->carbohydrate_pool_molC / shoot_volume);
5517 bool should_update_context = collision_detection_enabled && (geometry_update_counter >= geometry_update_frequency);
5520 bool force_update = collision_avoidance_applied && force_update_on_collision;
5522 if (should_update_context || force_update) {
5523 shoot_tree->front()->updateShootNodes(
true);
5527 shoot_tree->front()->updateShootNodes(
false);
5531 collision_avoidance_applied =
false;
5534 if (ground_clipping_height != -99999) {
5535 pruneGroundCollisions(plantID);
5539 if (carbon_model_enabled) {
5540 subtractShootMaintenanceCarbon(dt_max_days);
5541 subtractShootGrowthCarbon();
5542 checkCarbonPool_transferCarbon(dt_max_days);
5543 checkCarbonPool_adjustPhyllochron(dt_max_days);
5544 checkCarbonPool_abortOrgans(dt_max_days);
5548 for (
auto &shoot: *shoot_tree) {
5549 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
5551 float volume_ratio = shoot->old_shoot_volume/shoot_volume;
5552 context_ptr->
setObjectData(shoot->internode_tube_objID,
"volume_ratio", volume_ratio);
5553 shoot->old_shoot_volume = shoot_volume;
5554 context_ptr->
setObjectData(shoot->internode_tube_objID,
"old_shoot_volume", shoot_volume);
5559 if (!plant_primitives.empty()) {
5560 if (output_object_data.at(
"plant_height")) {
5563 if (output_object_data.at(
"phenology_stage")) {
5570 if (nitrogen_model_enabled) {
5571 accumulateLeafNitrogen(dt_max_days);
5572 remobilizeNitrogen(dt_max_days);
5573 removeFruitNitrogen();
5574 updateNitrogenStressFactor();
5578 if (geometry_update_counter >= geometry_update_frequency) {
5579 geometry_update_counter = 0;
5581 geometry_update_counter++;
5592 if (solid_obstacle_pruning_enabled) {
5593 pruneSolidBoundaryCollisions();
5598 if (!collision_detection_enabled) {
5599 for (
uint plantID: plantIDs) {
5600 if (plant_instances.find(plantID) != plant_instances.end()) {
5601 plant_instances.at(plantID).shoot_tree.front()->updateShootNodes(
true);
5608 if (output_object_data.at(
"age")) {
5609 for (
uint plantID: plantIDs) {
5610 if (plant_instances.find(plantID) == plant_instances.end()) {
5614 auto shoot_tree = &plant_instances.at(plantID).shoot_tree;
5615 for (
auto &shoot: *shoot_tree) {
5618 if (shoot->build_context_geometry_internode && !shoot->phytomers.empty()) {
5621 float shoot_age = shoot->phytomers.back()->age;
5622 context_ptr->
setObjectData(shoot->internode_tube_objID,
"age", shoot_age);
5627 for (
auto &phytomer: shoot->phytomers) {
5628 if (phytomer->build_context_geometry_petiole) {
5629 context_ptr->
setObjectData(phytomer->petiole_objIDs,
"age", phytomer->age);
5631 context_ptr->
setObjectData(phytomer->leaf_objIDs,
"age", phytomer->age);
5632 for (
auto &petiole: phytomer->floral_buds) {
5633 for (
auto &fbud: petiole) {
5634 if (fbud.state != BUD_DORMANT && fbud.state != BUD_ACTIVE && fbud.state != BUD_DEAD) {
5635 context_ptr->
setObjectData(fbud.inflorescence_objIDs,
"age", fbud.age);
5636 context_ptr->
setObjectData(fbud.peduncle_objIDs,
"age", fbud.age);
5650 if (!solid_obstacle_avoidance_enabled || solid_obstacle_UUIDs.empty() || !solid_obstacle_fruit_adjustment_enabled) {
5654 if (collision_detection_ptr ==
nullptr) {
5659 int debug_failures_shown = 0;
5660 const int max_debug_failures = 0;
5663 helios::ProgressBar progress_bar(plant_instances.size(), 50, plant_instances.size() > 1 && printmessages,
"Adjusting fruit collisions");
5664 if (progress_callback) {
5669 for (
const auto &plant_instance: plant_instances) {
5670 uint plantID = plant_instance.first;
5675 if (fruit_objIDs.empty()) {
5680 for (
uint fruit_objID: fruit_objIDs) {
5684 if (fruit_UUIDs.empty()) {
5689 std::vector<uint> collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5691 if (!collisions.empty()) {
5695 vec3 bbox_min, bbox_max;
5701 const Phytomer *fruit_phytomer =
nullptr;
5702 uint fruit_petiole_index = 0;
5703 uint fruit_bud_index = 0;
5704 bool found_base =
false;
5707 for (
const auto &shoot: plant_instance.second.shoot_tree) {
5708 for (
const auto &phytomer: shoot->phytomers) {
5709 uint petiole_idx = 0;
5710 for (
const auto &petiole: phytomer->floral_buds) {
5711 for (
const auto &fbud: petiole) {
5713 for (
size_t idx = 0; idx < fbud.inflorescence_objIDs.size(); idx++) {
5714 if (fbud.inflorescence_objIDs[idx] == fruit_objID && idx < fbud.inflorescence_bases.size()) {
5716 fruit_base = fbud.inflorescence_bases[idx];
5717 fruit_phytomer = phytomer.get();
5718 fruit_petiole_index = petiole_idx;
5719 fruit_bud_index = fbud.bud_index;
5723 peduncle_axis = phytomer->getPeduncleAxisVector(1.0f, petiole_idx, fbud.bud_index);
5724 }
catch (
const std::exception &e) {
5753 float fruit_radius = 0;
5754 fruit_radius = std::max(fruit_radius, (bbox_max - fruit_base).magnitude());
5755 fruit_radius = std::max(fruit_radius, (bbox_min - fruit_base).magnitude());
5756 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_min.
y, bbox_max.
z) - fruit_base).magnitude());
5757 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_max.
y, bbox_min.
z) - fruit_base).magnitude());
5758 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_min.
y, bbox_min.
z) - fruit_base).magnitude());
5759 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_min.
x, bbox_max.
y, bbox_max.
z) - fruit_base).magnitude());
5760 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_min.
y, bbox_max.
z) - fruit_base).magnitude());
5761 fruit_radius = std::max(fruit_radius, (
make_vec3(bbox_max.
x, bbox_max.
y, bbox_min.
z) - fruit_base).magnitude());
5765 float penetration_depth = std::max(0.0f, -bbox_min.
z);
5768 float initial_rotation = 0;
5769 if (fruit_radius > 0 && penetration_depth > 0) {
5771 float angle_estimate = std::asin(std::min(1.0f, penetration_depth / fruit_radius));
5773 initial_rotation = std::min(
deg2rad(35.0f), angle_estimate * 1.5f);
5776 initial_rotation =
deg2rad(10.0f);
5780 initial_rotation = std::max(initial_rotation,
deg2rad(8.0f));
5786 if (peduncle_axis.
magnitude() < 1e-6f) {
5794 vec3 bbox_center = 0.5f * (bbox_min + bbox_max);
5795 vec3 to_fruit_center = bbox_center - fruit_base;
5796 if (to_fruit_center.
magnitude() > 1e-6f) {
5800 to_fruit_center = peduncle_axis;
5805 rotation_axis =
cross(peduncle_axis, to_fruit_center);
5806 if (rotation_axis.
magnitude() < 1e-6f) {
5808 if (std::abs(peduncle_axis.
z) < 0.9f) {
5817 float rotation_step = initial_rotation;
5818 float total_rotation = 0;
5819 const float max_rotation =
deg2rad(120.0f);
5820 const int max_iterations = 25;
5823 bool debug_this_fruit = (debug_failures_shown < max_debug_failures);
5824 if (debug_this_fruit && printmessages) {
5825 std::cout <<
"\n=== DEBUG: Fruit " << fruit_objID <<
" collision adjustment ===" << std::endl;
5826 std::cout <<
"Fruit base: " << fruit_base << std::endl;
5827 std::cout <<
"Fruit bbox: " << bbox_min <<
" to " << bbox_max << std::endl;
5828 std::cout <<
"Fruit radius: " << fruit_radius << std::endl;
5829 std::cout <<
"Penetration depth: " << penetration_depth << std::endl;
5830 std::cout <<
"Peduncle axis: " << peduncle_axis << std::endl;
5831 std::cout <<
"Rotation axis: " << rotation_axis << std::endl;
5832 std::cout <<
"Initial rotation: " <<
rad2deg(initial_rotation) <<
" degrees" << std::endl;
5833 std::cout <<
"Initial collisions: " << collisions.size() << std::endl;
5836 for (
int iter = 0; iter < max_iterations && total_rotation < max_rotation; iter++) {
5839 context_ptr->
rotateObject(fruit_objID, -rotation_step, fruit_base, rotation_axis);
5840 total_rotation += rotation_step;
5844 collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5846 if (debug_this_fruit && printmessages) {
5847 std::cout <<
"Iter " << iter <<
": rotated " <<
rad2deg(rotation_step) <<
" deg (total " <<
rad2deg(total_rotation) <<
"), collisions: " << collisions.size() << std::endl;
5850 if (collisions.empty()) {
5853 float fine_tune_step =
deg2rad(3.0f);
5854 float fine_tune_attempts = 5;
5855 float original_total = total_rotation;
5857 if (debug_this_fruit && printmessages) {
5858 std::cout <<
"Fine-tuning: trying to rotate back down from " <<
rad2deg(total_rotation) <<
" degrees" << std::endl;
5861 for (
int fine_iter = 0; fine_iter < fine_tune_attempts; fine_iter++) {
5863 context_ptr->
rotateObject(fruit_objID, fine_tune_step, fruit_base, rotation_axis);
5867 std::vector<uint> test_collisions = collision_detection_ptr->
findCollisions(fruit_UUIDs, {}, solid_obstacle_UUIDs, {},
false);
5869 if (!test_collisions.empty()) {
5871 context_ptr->
rotateObject(fruit_objID, -fine_tune_step, fruit_base, rotation_axis);
5875 total_rotation -= fine_tune_step;
5884 rotation_step *= 0.7f;
5888 if (!collisions.empty()) {
5889 if (debug_this_fruit && printmessages) {
5890 std::cout <<
"FAILED: Fruit " << fruit_objID <<
" still colliding after " <<
rad2deg(total_rotation) <<
" degrees rotation (" << max_iterations <<
" iterations)" << std::endl;
5893 vec3 final_bbox_min, final_bbox_max;
5895 std::cout <<
"Final bbox: " << final_bbox_min <<
" to " << final_bbox_max << std::endl;
5896 std::cout <<
"Lowest point: " << final_bbox_min.
z << std::endl;
5898 debug_failures_shown++;
5912void PlantArchitecture::pruneSolidBoundaryCollisions() {
5913 if (!solid_obstacle_avoidance_enabled || solid_obstacle_UUIDs.empty()) {
5917 if (collision_detection_ptr ==
nullptr) {
5921 if (printmessages) {
5922 std::cout <<
"Performing solid boundary collision detection..." << std::endl;
5927 std::vector<uint> all_plant_primitives;
5931 std::vector<uint> intersecting_primitives = collision_detection_ptr->
findCollisions(solid_obstacle_UUIDs, {}, all_plant_primitives, {},
false);
5936 if (intersecting_primitives.empty()) {
5937 if (printmessages) {
5938 std::cout <<
"No collisions detected - this is unexpected given visible fruit penetration" << std::endl;
5943 if (printmessages) {
5944 std::cout <<
"Intersecting primitives found: " << intersecting_primitives.size() << std::endl;
5948 std::unordered_set<uint> collision_set(intersecting_objIDs.begin(), intersecting_objIDs.end());
5951 for (
auto &[plantID, plant]: plant_instances) {
5952 for (
uint shootID = 0; shootID < plant.shoot_tree.size(); shootID++) {
5953 auto &shoot = plant.shoot_tree.at(shootID);
5954 bool shoot_was_deleted =
false;
5958 if (collision_set.count(shoot->internode_tube_objID)) {
5960 if (shoot->rank != 0) {
5963 shoot_was_deleted =
true;
5969 if (shoot_was_deleted) {
5973 for (
uint node = 0; node < shoot->current_node_number; node++) {
5974 auto &phytomer = shoot->phytomers.at(node);
5977 for (
uint petiole = 0; petiole < phytomer->leaf_objIDs.size(); petiole++) {
5978 for (
uint leaflet = 0; leaflet < phytomer->leaf_objIDs.at(petiole).size(); leaflet++) {
5979 uint leaf_objID = phytomer->leaf_objIDs.at(petiole).at(leaflet);
5980 if (collision_set.count(leaf_objID)) {
5981 phytomer->removeLeaf();
5988 for (
uint petiole = 0; petiole < phytomer->petiole_objIDs.size(); petiole++) {
5989 for (
uint segment = 0; segment < phytomer->petiole_objIDs.at(petiole).size(); segment++) {
5990 uint petiole_objID = phytomer->petiole_objIDs.at(petiole).at(segment);
5991 if (collision_set.count(petiole_objID)) {
5992 phytomer->removeLeaf();
5999 for (
auto &petiole: phytomer->floral_buds) {
6000 for (
auto &fbud: petiole) {
6002 for (
int p = fbud.inflorescence_objIDs.size() - 1; p >= 0; p--) {
6003 uint objID = fbud.inflorescence_objIDs.at(p);
6004 if (collision_set.count(objID)) {
6006 fbud.inflorescence_objIDs.erase(fbud.inflorescence_objIDs.begin() + p);
6007 fbud.inflorescence_bases.erase(fbud.inflorescence_bases.begin() + p);
6011 for (
int p = fbud.peduncle_objIDs.size() - 1; p >= 0; p--) {
6012 uint objID = fbud.peduncle_objIDs.at(p);
6013 if (collision_set.count(objID)) {
6017 fbud.peduncle_objIDs.clear();
6018 fbud.inflorescence_objIDs.clear();
6019 fbud.inflorescence_bases.clear();
6027 if (shoot_was_deleted) {
6033 if (printmessages) {
6034 std::cout <<
"Solid boundary collision pruning completed" << std::endl;
6038std::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) {
6039 uint Nverts = vertices.size();
6041 if (radii.size() != Nverts || colors.size() != Nverts) {
6042 helios_runtime_error(
"ERROR (makeTubeFromCones): Length of vertex vectors is not consistent.");
6046 bool all_radii_too_small =
true;
6047 float max_radius = 0.0f;
6048 for (
float radius: radii) {
6049 max_radius = std::max(max_radius, radius);
6050 if (radius >= MIN_TUBE_RADIUS_FOR_GEOMETRY) {
6051 all_radii_too_small =
false;
6057 float total_length = 0.0f;
6058 for (
uint v = 0; v < Nverts - 1; v++) {
6059 total_length += (vertices.at(v + 1) - vertices.at(v)).magnitude();
6064 if (all_radii_too_small || total_length < MIN_TUBE_LENGTH_FOR_GEOMETRY) {
6065 return std::vector<uint>();
6068 std::vector<uint> objIDs;
6069 objIDs.reserve(Nverts - 1);
6071 for (
uint v = 0; v < Nverts - 1; v++) {
6072 if ((vertices.at(v + 1) - vertices.at(v)).magnitude() < 1e-6f) {
6075 float r0 = std::max(radii.at(v), MIN_TUBE_RADIUS_FOR_GEOMETRY);
6076 float r1 = std::max(radii.at(v + 1), MIN_TUBE_RADIUS_FOR_GEOMETRY);
6077 objIDs.push_back(context_ptr->
addConeObject(radial_subdivisions, vertices.at(v), vertices.at(v + 1), r0, r1, colors.at(v)));
6083bool PlantArchitecture::detectGroundCollision(
uint objID) {
6084 std::vector<uint> objIDs = {objID};
6085 return detectGroundCollision(objIDs);
6088bool PlantArchitecture::detectGroundCollision(
const std::vector<uint> &objID)
const {
6089 for (
uint ID: objID) {
6092 for (
uint UUID: UUIDs) {
6094 for (
const vec3 &v: vertices) {
6095 if (v.
z < ground_clipping_height) {
6107 std::string label_lower = object_data_label;
6108 std::transform(label_lower.begin(), label_lower.end(), label_lower.begin(), ::tolower);
6111 if (label_lower ==
"all") {
6113 for (
auto &item: output_object_data) {
6120 if (output_object_data.find(object_data_label) == output_object_data.end()) {
6121 helios_runtime_error(
"ERROR (PlantArchitecture::optionalOutputObjectData): Output object data of '" + object_data_label +
"' is not a valid option.");
6124 output_object_data.at(object_data_label) =
true;
6128 for (
const auto &label: object_data_labels) {
6136 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
6137 delete collision_detection_ptr;
6138 collision_detection_ptr =
nullptr;
6139 owns_collision_detection =
false;
6146 owns_collision_detection =
true;
6147 collision_detection_enabled =
true;
6148 collision_target_UUIDs = target_object_UUIDs;
6149 collision_target_object_IDs = target_object_IDs;
6152 petiole_collision_detection_enabled = enable_petiole_collision;
6153 fruit_collision_detection_enabled = enable_fruit_collision;
6162 std::vector<uint> static_obstacles;
6163 static_obstacles.insert(static_obstacles.end(), target_object_UUIDs.begin(), target_object_UUIDs.end());
6164 static_obstacles.insert(static_obstacles.end(), target_object_IDs.begin(), target_object_IDs.end());
6167 rebuildBVHForTimestep();
6170 if (solid_obstacle_avoidance_enabled) {
6171 static_obstacles.insert(static_obstacles.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
6179 for (
uint plant_id: plant_ids) {
6181 if (!plant_primitives.empty()) {
6182 collision_detection_ptr->
registerTree(plant_id, plant_primitives);
6188 }
catch (
const std::exception &e) {
6189 helios_runtime_error(
"ERROR (PlantArchitecture::enableSoftCollisionAvoidance): Failed to create CollisionDetection instance: " + std::string(e.what()));
6194 collision_detection_enabled =
false;
6197 if (collision_detection_ptr !=
nullptr && owns_collision_detection) {
6198 delete collision_detection_ptr;
6199 owns_collision_detection =
false;
6202 collision_detection_ptr =
nullptr;
6203 collision_target_UUIDs.clear();
6204 collision_target_object_IDs.clear();
6206 if (printmessages) {
6207 std::cout <<
"Collision detection disabled for plant growth and internal instance cleaned up" << std::endl;
6212 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6213 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): cone_half_angle_deg must be between 0 and 180 degrees.");
6215 if (look_ahead_distance <= 0.0f) {
6216 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): sample_count must be positive.");
6218 if (inertia_weight < 0.0f || inertia_weight > 1.0f) {
6219 helios_runtime_error(
"ERROR (PlantArchitecture::setSoftCollisionAvoidanceParameters): inertia_weight must be between 0.0 and 1.0.");
6222 collision_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6223 collision_cone_height = look_ahead_distance;
6224 collision_sample_count = sample_count;
6225 collision_inertia_weight = inertia_weight;
6229 if (collision_detection_ptr ==
nullptr) {
6230 helios_runtime_error(
"ERROR (PlantArchitecture::setStaticObstacles): Collision detection must be enabled before setting static obstacles.");
6235 if (printmessages) {
6236 std::cout <<
"Marked " << target_UUIDs.size() <<
" primitives as static obstacles for collision detection" << std::endl;
6241 return collision_detection_ptr;
6245 collision_include_internodes = include_internodes;
6246 collision_include_leaves = include_leaves;
6247 collision_include_petioles = include_petioles;
6248 collision_include_flowers = include_flowers;
6249 collision_include_fruit = include_fruit;
6254 if (printmessages) {
6255 std::cout <<
"Set collision-relevant organs: internodes=" << (include_internodes ?
"yes" :
"no") <<
", leaves=" << (include_leaves ?
"yes" :
"no") <<
", petioles=" << (include_petioles ?
"yes" :
"no")
6256 <<
", flowers=" << (include_flowers ?
"yes" :
"no") <<
", fruit=" << (include_fruit ?
"yes" :
"no") << std::endl;
6262 solid_obstacle_avoidance_enabled =
true;
6263 solid_obstacle_UUIDs = obstacle_UUIDs;
6264 solid_obstacle_avoidance_distance = avoidance_distance;
6265 solid_obstacle_fruit_adjustment_enabled = enable_fruit_adjustment;
6266 solid_obstacle_pruning_enabled = enable_obstacle_pruning;
6269 if (collision_detection_ptr ==
nullptr) {
6273 owns_collision_detection =
true;
6274 collision_detection_enabled =
true;
6282 rebuildBVHForTimestep();
6283 }
catch (std::exception &e) {
6284 helios_runtime_error(
"ERROR (PlantArchitecture::enableSolidObstacleAvoidance): Failed to create CollisionDetection instance: " + std::string(e.what()));
6289 if (collision_detection_enabled && collision_detection_ptr !=
nullptr && collision_detection_ptr->
isTreeBasedBVHEnabled()) {
6290 std::vector<uint> static_obstacles;
6291 static_obstacles.insert(static_obstacles.end(), collision_target_UUIDs.begin(), collision_target_UUIDs.end());
6292 static_obstacles.insert(static_obstacles.end(), collision_target_object_IDs.begin(), collision_target_object_IDs.end());
6293 static_obstacles.insert(static_obstacles.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
6299void PlantArchitecture::clearBVHCache()
const {
6300 bvh_cached_for_current_growth =
false;
6301 cached_target_geometry.clear();
6302 cached_filtered_geometry.clear();
6306void PlantArchitecture::rebuildBVHForTimestep() {
6307 if (!collision_detection_enabled || collision_detection_ptr ==
nullptr) {
6313 std::vector<uint> target_geometry;
6316 if (solid_obstacle_avoidance_enabled && !solid_obstacle_UUIDs.empty()) {
6317 target_geometry.insert(target_geometry.end(), solid_obstacle_UUIDs.begin(), solid_obstacle_UUIDs.end());
6320 if (!collision_target_UUIDs.empty()) {
6322 std::vector<uint> valid_targets;
6323 for (
uint uuid: collision_target_UUIDs) {
6325 valid_targets.push_back(uuid);
6329 target_geometry.insert(target_geometry.end(), valid_targets.begin(), valid_targets.end());
6330 }
else if (!collision_target_object_IDs.empty()) {
6332 for (
uint objID: collision_target_object_IDs) {
6335 target_geometry.insert(target_geometry.end(), obj_primitives.begin(), obj_primitives.end());
6341 std::vector<uint> preserved_solid_obstacles = target_geometry;
6342 target_geometry.clear();
6346 if (collision_include_internodes) {
6348 target_geometry.insert(target_geometry.end(), internode_uuids.begin(), internode_uuids.end());
6350 if (collision_include_leaves) {
6352 target_geometry.insert(target_geometry.end(), leaf_uuids.begin(), leaf_uuids.end());
6354 if (collision_include_petioles) {
6356 target_geometry.insert(target_geometry.end(), petiole_uuids.begin(), petiole_uuids.end());
6358 if (collision_include_flowers) {
6360 target_geometry.insert(target_geometry.end(), flower_uuids.begin(), flower_uuids.end());
6362 if (collision_include_fruit) {
6364 target_geometry.insert(target_geometry.end(), fruit_uuids.begin(), fruit_uuids.end());
6366 }
catch (
const std::exception &e) {
6367 if (printmessages) {
6368 std::cout <<
"Warning: Exception in organ filtering, falling back to all geometry: " << e.what() << std::endl;
6374 target_geometry.insert(target_geometry.end(), preserved_solid_obstacles.begin(), preserved_solid_obstacles.end());
6377 std::vector<uint> all_context_geometry = context_ptr->
getAllUUIDs();
6378 std::set<uint> all_plant_geometry_set;
6381 all_plant_geometry_set.insert(all_plant.begin(), all_plant.end());
6382 }
catch (
const std::exception &e) {
6383 if (printmessages) {
6384 std::cout <<
"Warning: Could not get plant geometry for external obstacle filtering: " << e.what() << std::endl;
6388 for (
uint uuid: all_context_geometry) {
6389 if (all_plant_geometry_set.find(uuid) == all_plant_geometry_set.end()) {
6390 target_geometry.push_back(uuid);
6395 if (!target_geometry.empty()) {
6397 std::vector<uint> plant_geometry;
6400 }
catch (
const std::exception &e) {
6401 if (printmessages) {
6402 std::cout <<
"Warning: Could not get plant geometry for hierarchical BVH: " << e.what() << std::endl;
6404 plant_geometry.clear();
6406 std::set<uint> plant_set(plant_geometry.begin(), plant_geometry.end());
6408 std::vector<uint> static_obstacles;
6409 for (
uint uuid: target_geometry) {
6410 if (plant_set.find(uuid) == plant_set.end()) {
6411 static_obstacles.push_back(uuid);
6418 collision_detection_ptr->
updateBVH(target_geometry,
true);
6422 cached_target_geometry = target_geometry;
6423 cached_filtered_geometry = target_geometry;
6424 bvh_cached_for_current_growth =
true;
6429 if (update_frequency < 1) {
6430 helios_runtime_error(
"ERROR (PlantArchitecture::setGeometryUpdateScheduling): update_frequency must be at least 1.");
6433 geometry_update_frequency = update_frequency;
6434 geometry_update_counter = 0;
6440 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6441 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6443 if (look_ahead_distance <= 0.0f) {
6444 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): look_ahead_distance must be positive.");
6446 if (attraction_weight_input < 0.0f || attraction_weight_input > 1.0f) {
6447 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): attraction_weight must be between 0.0 and 1.0.");
6451 attraction_points_enabled =
true;
6452 attraction_points = attraction_points_input;
6453 attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6454 attraction_cone_height = look_ahead_distance;
6455 attraction_weight = attraction_weight_input;
6458 for (
auto &[plantID, plant]: plant_instances) {
6459 plant.attraction_points_enabled =
true;
6460 plant.attraction_points = attraction_points_input;
6461 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6462 plant.attraction_cone_height = look_ahead_distance;
6463 plant.attraction_weight = attraction_weight_input;
6469 attraction_points_enabled =
false;
6470 attraction_points.clear();
6473 for (
auto &[plantID, plant]: plant_instances) {
6474 plant.attraction_points_enabled =
false;
6475 plant.attraction_points.clear();
6480 if (!attraction_points_enabled) {
6481 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Attraction points must be enabled before updating positions.");
6483 if (attraction_points_input.empty()) {
6484 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): attraction_points cannot be empty.");
6488 attraction_points = attraction_points_input;
6491 for (
auto &[plantID, plant]: plant_instances) {
6492 if (plant.attraction_points_enabled) {
6493 plant.attraction_points = attraction_points_input;
6499 if (!attraction_points_enabled) {
6500 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Attraction points must be enabled before updating positions.");
6502 if (attraction_points_input.empty()) {
6503 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): attraction_points cannot be empty.");
6507 attraction_points.insert(attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6510 for (
auto &[plantID, plant]: plant_instances) {
6511 if (plant.attraction_points_enabled) {
6512 plant.attraction_points.insert(plant.attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6518 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6519 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): view_half_angle_deg must be between 0 and 180 degrees.");
6521 if (look_ahead_distance <= 0.0f) {
6522 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): look_ahead_distance must be positive.");
6524 if (attraction_weight_input < 0.0f || attraction_weight_input > 1.0f) {
6525 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): attraction_weight must be between 0.0 and 1.0.");
6527 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6528 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): obstacle_reduction_factor must be between 0.0 and 1.0.");
6532 attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6533 attraction_cone_height = look_ahead_distance;
6534 attraction_weight = attraction_weight_input;
6535 attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6538 for (
auto &[plantID, plant]: plant_instances) {
6539 if (plant.attraction_points_enabled) {
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;
6543 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6547 if (printmessages) {
6548 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;
6549 if (!plant_instances.empty()) {
6550 std::cout <<
"Applied to " << plant_instances.size() <<
" existing plants with attraction points enabled" << std::endl;
6558 if (plant_instances.find(plantID) == plant_instances.end()) {
6559 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6562 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6563 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6565 if (look_ahead_distance <= 0.0f) {
6566 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): look_ahead_distance must be greater than 0.");
6568 if (attraction_points_input.empty()) {
6569 helios_runtime_error(
"ERROR (PlantArchitecture::enableAttractionPoints): attraction_points cannot be empty.");
6572 auto &plant = plant_instances.at(plantID);
6573 plant.attraction_points_enabled =
true;
6574 plant.attraction_points = attraction_points_input;
6575 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6576 plant.attraction_cone_height = look_ahead_distance;
6577 plant.attraction_weight = attraction_weight_input;
6579 if (printmessages) {
6580 std::cout <<
"Enabled attraction points for plant " << plantID <<
" with " << attraction_points_input.size() <<
" target positions" << std::endl;
6581 std::cout <<
"Plant " << plantID <<
" attraction parameters: cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input << std::endl;
6586 if (plant_instances.find(plantID) == plant_instances.end()) {
6587 helios_runtime_error(
"ERROR (PlantArchitecture::disableAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6590 auto &plant = plant_instances.at(plantID);
6591 plant.attraction_points_enabled =
false;
6592 plant.attraction_points.clear();
6594 if (printmessages) {
6595 std::cout <<
"Disabled attraction points for plant " << plantID <<
" - will use natural growth patterns" << std::endl;
6600 if (plant_instances.find(plantID) == plant_instances.end()) {
6601 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6604 auto &plant = plant_instances.at(plantID);
6605 if (!plant.attraction_points_enabled) {
6606 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): Attraction points must be enabled for plant " + std::to_string(plantID) +
" before updating positions.");
6608 if (attraction_points_input.empty()) {
6609 helios_runtime_error(
"ERROR (PlantArchitecture::updateAttractionPoints): attraction_points cannot be empty.");
6612 plant.attraction_points = attraction_points_input;
6616 if (plant_instances.find(plantID) == plant_instances.end()) {
6617 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6620 auto &plant = plant_instances.at(plantID);
6621 if (!plant.attraction_points_enabled) {
6622 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): Attraction points must be enabled for plant " + std::to_string(plantID) +
" before updating positions.");
6624 if (attraction_points_input.empty()) {
6625 helios_runtime_error(
"ERROR (PlantArchitecture::appendAttractionPoints): attraction_points cannot be empty.");
6628 plant.attraction_points.insert(plant.attraction_points.end(), attraction_points_input.begin(), attraction_points_input.end());
6632 if (plant_instances.find(plantID) == plant_instances.end()) {
6633 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6636 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6637 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): view_half_angle_deg must be between 0 and 180 degrees.");
6639 if (look_ahead_distance <= 0.0f) {
6640 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): look_ahead_distance must be greater than 0.");
6642 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6643 helios_runtime_error(
"ERROR (PlantArchitecture::setAttractionParameters): obstacle_reduction_factor must be between 0 and 1.");
6646 auto &plant = plant_instances.at(plantID);
6647 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6648 plant.attraction_cone_height = look_ahead_distance;
6649 plant.attraction_weight = attraction_weight_input;
6650 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6652 if (printmessages) {
6653 std::cout <<
"Updated attraction parameters for plant " << plantID <<
": cone_angle=" << view_half_angle_deg <<
"°, look_ahead=" << look_ahead_distance <<
"m, weight=" << attraction_weight_input
6654 <<
", obstacle_reduction=" << obstacle_reduction_factor << std::endl;
6658void 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) {
6659 if (plant_instances.find(plantID) == plant_instances.end()) {
6660 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): Plant with ID " + std::to_string(plantID) +
" does not exist.");
6663 if (view_half_angle_deg <= 0.0f || view_half_angle_deg > 180.f) {
6664 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): view_half_angle_deg must be between 0 and 180 degrees.");
6666 if (look_ahead_distance <= 0.0f) {
6667 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): look_ahead_distance must be greater than 0.");
6669 if (attraction_points_input.empty()) {
6670 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): attraction_points cannot be empty.");
6672 if (obstacle_reduction_factor < 0.0f || obstacle_reduction_factor > 1.0f) {
6673 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantAttractionPoints): obstacle_reduction_factor must be between 0 and 1.");
6676 auto &plant = plant_instances.at(plantID);
6677 plant.attraction_points_enabled =
true;
6678 plant.attraction_points = attraction_points_input;
6679 plant.attraction_cone_half_angle_rad =
deg2rad(view_half_angle_deg);
6680 plant.attraction_cone_height = look_ahead_distance;
6681 plant.attraction_weight = attraction_weight_input;
6682 plant.attraction_obstacle_reduction_factor = obstacle_reduction_factor;
6686 printmessages =
false;
6687 if (collision_detection_ptr !=
nullptr) {
6693 printmessages =
true;
6694 if (collision_detection_ptr !=
nullptr) {