Skip to content

Commit 9198ef5

Browse files
committed
[1.3.63] 2026-01-21
- Updated docs to be mobile friendly with slide-out sidebar navigation and hamburger menu. - Removed direct CUDA dependency; ray tracing now uses the CollisionDetection plugin for GPU acceleration. - Renamed `calculateLeafAreaGPU()` to `calculateLeafArea()` (old methods deprecated but still available). - Added `enableGPUAcceleration()` and `disableGPUAcceleration()` methods to control CollisionDetection GPU usage. - Added adaptive triangulation filtering for multi-return data using separation ratio thresholds. - Improved `gapfillMisses()` to track filled grid positions and avoid duplicate gap-filled points. - Refined carbohydrate model with new parameters for structural vs non-structural carbon tracking (`carbohydrate_percentage`, `stem_structural_carbon_percentage`, `living_wood_fraction`, `dormant_respiration_fraction`) and separate upward/downward transfer thresholds. - Added `vegetative_bud_break_probability_max` parameter to ShootParameters for controlling maximum bud break probability. - Added `updateShootFruitCounts()` and `getShootInternodeObjectIDs()` methods. - Added additional internode object data outputs for carbon model diagnostics (`carbohydrate_pool_molC`, `daily_net_photosynthesis`, `daily_respiration`, `daily_growth`). - Added two new almond tree model variants: `almond_aldrich` and `almond_wood_colony`.
1 parent 713560e commit 9198ef5

27 files changed

Lines changed: 4895 additions & 1158367 deletions

doc/CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
# [1.3.63] 2026-01-21
4+
5+
## Core
6+
- Updated docs to be mobile friendly with slide-out sidebar navigation and hamburger menu.
7+
8+
## LiDAR
9+
- Removed direct CUDA dependency; ray tracing now uses the CollisionDetection plugin for GPU acceleration.
10+
- Renamed `calculateLeafAreaGPU()` to `calculateLeafArea()` (old methods deprecated but still available).
11+
- Added `enableGPUAcceleration()` and `disableGPUAcceleration()` methods to control CollisionDetection GPU usage.
12+
- Added adaptive triangulation filtering for multi-return data using separation ratio thresholds.
13+
- Improved `gapfillMisses()` to track filled grid positions and avoid duplicate gap-filled points.
14+
15+
## Plant Architecture
16+
- Refined carbohydrate model with new parameters for structural vs non-structural carbon tracking (`carbohydrate_percentage`, `stem_structural_carbon_percentage`, `living_wood_fraction`, `dormant_respiration_fraction`) and separate upward/downward transfer thresholds.
17+
- Added `vegetative_bud_break_probability_max` parameter to ShootParameters for controlling maximum bud break probability.
18+
- Added `updateShootFruitCounts()` and `getShootInternodeObjectIDs()` methods.
19+
- Added additional internode object data outputs for carbon model diagnostics (`carbohydrate_pool_molC`, `daily_net_photosynthesis`, `daily_respiration`, `daily_growth`).
20+
- Added two new almond tree model variants: `almond_aldrich` and `almond_wood_colony`.
21+
322
# [1.3.62] 2026-01-12
423

524
## Core

doc/assets/custom.css

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,155 @@ html {
5353
#searchbox-container doxygen-awesome-dark-mode-toggle,
5454
#titlearea tr:has(> td > #MSearchBox) doxygen-awesome-dark-mode-toggle {
5555
flex: 0 0 auto;
56+
}
57+
58+
/* ============================================
59+
Nav Tree Arrowhead Styles (missing from older doxygen-awesome)
60+
============================================ */
61+
62+
/* Arrowhead triangle for expandable nav items */
63+
span.arrowhead {
64+
display: inline-block;
65+
width: 0;
66+
height: 0;
67+
border-left: 4px solid transparent;
68+
border-right: 4px solid transparent;
69+
border-top: 6px solid var(--primary-light-color);
70+
vertical-align: middle;
71+
margin-right: 4px;
72+
transition: transform var(--animation-duration) ease-out;
73+
}
74+
75+
/* Closed state - arrow points right */
76+
span.arrowhead.closed {
77+
transform: rotate(-90deg);
78+
}
79+
80+
/* Open state - arrow points down */
81+
span.arrowhead.opened {
82+
transform: rotate(0deg);
83+
}
84+
85+
/* Selected item arrowhead color */
86+
.selected span.arrowhead {
87+
border-top-color: var(--primary-color);
88+
}
89+
90+
/* ============================================
91+
Mobile Navigation Sidebar Toggle
92+
============================================ */
93+
94+
/* Mobile hamburger button - hidden on desktop, shown on mobile */
95+
#mobile-menu-btn {
96+
display: none;
97+
position: fixed;
98+
top: 10px;
99+
left: 10px;
100+
z-index: 101;
101+
flex-direction: column;
102+
justify-content: center;
103+
align-items: center;
104+
width: 44px;
105+
height: 44px;
106+
padding: 10px;
107+
background: var(--page-background-color);
108+
border: 1px solid var(--separator-color);
109+
border-radius: var(--border-radius-medium);
110+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
111+
cursor: pointer;
112+
gap: 5px;
113+
}
114+
115+
#mobile-menu-btn span {
116+
display: block;
117+
width: 20px;
118+
height: 2px;
119+
background-color: var(--page-foreground-color);
120+
border-radius: 1px;
121+
transition: transform 0.3s ease, opacity 0.3s ease;
122+
}
123+
124+
/* Hamburger to X animation when open */
125+
.mobile-sidebar-open #mobile-menu-btn span:nth-child(1) {
126+
transform: translateY(7px) rotate(45deg);
127+
}
128+
129+
.mobile-sidebar-open #mobile-menu-btn span:nth-child(2) {
130+
opacity: 0;
131+
}
132+
133+
.mobile-sidebar-open #mobile-menu-btn span:nth-child(3) {
134+
transform: translateY(-7px) rotate(-45deg);
135+
}
136+
137+
/* Mobile overlay - hidden by default */
138+
#mobile-nav-overlay {
139+
display: none;
140+
position: fixed;
141+
top: 0;
142+
left: 0;
143+
right: 0;
144+
bottom: 0;
145+
background: rgba(0, 0, 0, 0.5);
146+
z-index: 99;
147+
opacity: 0;
148+
transition: opacity 0.3s ease;
149+
}
150+
151+
/* Mobile-specific styles */
152+
@media screen and (max-width: 767px) {
153+
/* Show hamburger button on mobile */
154+
#mobile-menu-btn {
155+
display: flex;
156+
}
157+
158+
/* Override doxygen-awesome hiding the sidebar - make it a slide-out drawer */
159+
#side-nav {
160+
display: block !important;
161+
position: fixed !important;
162+
top: 0 !important;
163+
left: 0 !important;
164+
bottom: 0 !important;
165+
width: 280px !important;
166+
max-width: 85vw !important;
167+
z-index: 100 !important;
168+
transform: translateX(-100%);
169+
transition: transform 0.3s ease;
170+
overflow-y: auto !important;
171+
box-shadow: none;
172+
}
173+
174+
/* Show sidebar when open */
175+
.mobile-sidebar-open #side-nav {
176+
transform: translateX(0);
177+
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.2);
178+
}
179+
180+
/* Show and animate overlay when sidebar is open */
181+
.mobile-sidebar-open #mobile-nav-overlay {
182+
display: block;
183+
opacity: 1;
184+
}
185+
186+
/* Prevent body scroll when sidebar is open */
187+
.mobile-sidebar-open {
188+
overflow: hidden;
189+
}
190+
191+
/* Ensure content doesn't shift */
192+
#doc-content {
193+
margin-left: 0 !important;
194+
padding-top: 60px !important;
195+
}
196+
197+
/* Hide titlearea inside sidebar on mobile - we only want nav tree */
198+
#side-nav #titlearea {
199+
display: none !important;
200+
}
201+
202+
/* Ensure nav tree arrows are visible on hover in mobile sidebar */
203+
#side-nav #nav-tree div.item:hover .arrow,
204+
#side-nav #nav-tree a:focus .arrow {
205+
opacity: 0.9 !important;
206+
}
56207
}

doc/header.html

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,35 @@
3232
DoxygenAwesomeTabs.init()
3333
DoxygenAwesomeInteractiveToc.init()
3434
</script>
35+
<script type="text/javascript">
36+
// Mobile sidebar toggle functionality
37+
function toggleMobileSidebar() {
38+
var sidebar = document.getElementById('side-nav');
39+
var overlay = document.getElementById('mobile-nav-overlay');
40+
var menuBtn = document.getElementById('mobile-menu-btn');
41+
var body = document.body;
42+
43+
if (sidebar && overlay && menuBtn) {
44+
var isOpen = body.classList.contains('mobile-sidebar-open');
45+
if (isOpen) {
46+
body.classList.remove('mobile-sidebar-open');
47+
menuBtn.setAttribute('aria-expanded', 'false');
48+
menuBtn.setAttribute('aria-label', 'Open navigation menu');
49+
} else {
50+
body.classList.add('mobile-sidebar-open');
51+
menuBtn.setAttribute('aria-expanded', 'true');
52+
menuBtn.setAttribute('aria-label', 'Close navigation menu');
53+
}
54+
}
55+
}
56+
57+
// Close sidebar on escape key
58+
document.addEventListener('keydown', function(e) {
59+
if (e.key === 'Escape' && document.body.classList.contains('mobile-sidebar-open')) {
60+
toggleMobileSidebar();
61+
}
62+
});
63+
</script>
3564
<script>
3665
try {
3766
// Remove the stored “last visited page” so the tree always starts fresh
@@ -69,6 +98,16 @@
6998
gtag('config', 'G-3F0Y5Z6543');
7099
</script>
71100
<body>
101+
<!-- Mobile sidebar overlay -->
102+
<div id="mobile-nav-overlay" onclick="toggleMobileSidebar()"></div>
103+
104+
<!-- Mobile hamburger menu button - must be outside #side-nav to avoid transform containment -->
105+
<button id="mobile-menu-btn" aria-label="Open navigation menu" onclick="toggleMobileSidebar()">
106+
<span></span>
107+
<span></span>
108+
<span></span>
109+
</button>
110+
72111
<!--BEGIN DISABLE_INDEX-->
73112
<!--BEGIN FULL_SIDEBAR-->
74113
<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! -->

plugins/collisiondetection/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ if(CUDAToolkit_FOUND)
8282
target_include_directories(collisiondetection PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
8383

8484
target_link_libraries(collisiondetection PUBLIC CUDA::cudart_static)
85-
85+
8686
# Define preprocessor macro to indicate CUDA support
8787
target_compile_definitions(collisiondetection PRIVATE HELIOS_CUDA_AVAILABLE)
88-
88+
8989
else()
9090
message(STATUS "CUDA not found - building CPU-only version")
9191

plugins/collisiondetection/include/CollisionDetection.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1546,7 +1546,7 @@ class CollisionDetection {
15461546
* \param[in] ray_origins Vector of ray origin points
15471547
* \param[in] ray_directions Vector of ray direction vectors
15481548
*/
1549-
void calculateVoxelRayPathLengths_GPU(const std::vector<helios::vec3> &ray_origins, const std::vector<helios::vec3> &ray_directions);
1549+
bool calculateVoxelRayPathLengths_GPU(const std::vector<helios::vec3> &ray_origins, const std::vector<helios::vec3> &ray_directions);
15501550

15511551
/**
15521552
* \brief Validate voxel grid indices

plugins/collisiondetection/src/CollisionDetection.cpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ struct GPUBVHNode {
5151
extern "C" {
5252
void launchBVHTraversal(void *h_nodes, int node_count, unsigned int *h_primitive_indices, int primitive_count, float *h_primitive_aabb_min, float *h_primitive_aabb_max, float *h_query_aabb_min, float *h_query_aabb_max, int num_queries,
5353
unsigned int *h_results, unsigned int *h_result_counts, int max_results_per_query);
54-
void launchVoxelRayPathLengths(int num_rays, float *h_ray_origins, float *h_ray_directions, float grid_center_x, float grid_center_y, float grid_center_z, float grid_size_x, float grid_size_y, float grid_size_z, int grid_divisions_x,
54+
bool launchVoxelRayPathLengths(int num_rays, float *h_ray_origins, float *h_ray_directions, float grid_center_x, float grid_center_y, float grid_center_z, float grid_size_x, float grid_size_y, float grid_size_z, int grid_divisions_x,
5555
int grid_divisions_y, int grid_divisions_z, int primitive_count, int *h_voxel_ray_counts, float *h_voxel_path_lengths, int *h_voxel_transmitted, int *h_voxel_hit_before, int *h_voxel_hit_after, int *h_voxel_hit_inside);
5656
// Warp-efficient GPU kernels
5757
void launchWarpEfficientBVH(void *h_bvh_soa_gpu, unsigned int *h_primitive_indices, int primitive_count, float *h_primitive_aabb_min, float *h_primitive_aabb_max, float *h_ray_origins, float *h_ray_directions, float *h_ray_max_distances,
@@ -928,6 +928,11 @@ void CollisionDetection::buildBVHRecursive(uint node_index, size_t primitive_sta
928928
centroid_b_coord = 0.5f * (aabb_b.first.z + aabb_b.second.z);
929929
}
930930

931+
// Add stable tiebreaker using UUID to ensure deterministic BVH construction
932+
// This prevents non-deterministic behavior when primitives have equal centroids
933+
if (centroid_a_coord == centroid_b_coord) {
934+
return a < b;
935+
}
931936
return centroid_a_coord < centroid_b_coord;
932937
});
933938

@@ -3137,7 +3142,16 @@ void CollisionDetection::calculateVoxelRayPathLengths(const vec3 &grid_center, c
31373142
// Choose GPU or CPU implementation based on acceleration setting
31383143
#ifdef HELIOS_CUDA_AVAILABLE
31393144
if (isGPUAccelerationEnabled()) {
3140-
calculateVoxelRayPathLengths_GPU(ray_origins, ray_directions);
3145+
// Try GPU first, but fall back to CPU if GPU fails (no hardware, out of memory, etc.)
3146+
bool gpu_success = calculateVoxelRayPathLengths_GPU(ray_origins, ray_directions);
3147+
if (!gpu_success) {
3148+
// GPU failed - fall back to CPU and disable GPU for future calls
3149+
if (printmessages) {
3150+
warnings.addWarning("gpu_voxel_fallback", "GPU voxel calculation failed, falling back to CPU");
3151+
}
3152+
gpu_acceleration_enabled = false;
3153+
calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
3154+
}
31413155
} else {
31423156
calculateVoxelRayPathLengths_CPU(ray_origins, ray_directions);
31433157
}
@@ -3933,7 +3947,15 @@ void CollisionDetection::calculateVoxelRayPathLengths_CPU(const std::vector<vec3
39333947
}
39343948

39353949
#ifdef HELIOS_CUDA_AVAILABLE
3936-
void CollisionDetection::calculateVoxelRayPathLengths_GPU(const std::vector<vec3> &ray_origins, const std::vector<vec3> &ray_directions) {
3950+
bool CollisionDetection::calculateVoxelRayPathLengths_GPU(const std::vector<vec3> &ray_origins, const std::vector<vec3> &ray_directions) {
3951+
// Check if GPU is actually available at runtime
3952+
int deviceCount = 0;
3953+
cudaError_t err = cudaGetDeviceCount(&deviceCount);
3954+
if (err != cudaSuccess || deviceCount == 0) {
3955+
// No GPU hardware available - return false to trigger CPU fallback
3956+
return false;
3957+
}
3958+
39373959
if (printmessages) {
39383960
}
39393961

@@ -3967,10 +3989,15 @@ void CollisionDetection::calculateVoxelRayPathLengths_GPU(const std::vector<vec3
39673989
int primitive_count = static_cast<int>(primitive_cache.size());
39683990

39693991
// Launch CUDA kernel
3970-
launchVoxelRayPathLengths(num_rays, h_ray_origins.data(), h_ray_directions.data(), voxel_grid_center.x, voxel_grid_center.y, voxel_grid_center.z, voxel_grid_size.x, voxel_grid_size.y, voxel_grid_size.z, voxel_grid_divisions.x,
3992+
bool gpu_success = launchVoxelRayPathLengths(num_rays, h_ray_origins.data(), h_ray_directions.data(), voxel_grid_center.x, voxel_grid_center.y, voxel_grid_center.z, voxel_grid_size.x, voxel_grid_size.y, voxel_grid_size.z, voxel_grid_divisions.x,
39713993
voxel_grid_divisions.y, voxel_grid_divisions.z, primitive_count, h_voxel_ray_counts.data(), h_voxel_path_lengths.data(), h_voxel_transmitted.data(), h_voxel_hit_before.data(), h_voxel_hit_after.data(),
39723994
h_voxel_hit_inside.data());
39733995

3996+
if (!gpu_success) {
3997+
// GPU kernel failed - return false to trigger CPU fallback
3998+
return false;
3999+
}
4000+
39744001
// Copy results back to class data structures
39754002
if (use_flat_arrays) {
39764003
// Copy directly to flat arrays
@@ -4117,6 +4144,8 @@ void CollisionDetection::calculateVoxelRayPathLengths_GPU(const std::vector<vec3
41174144
total_ray_voxel_intersections += count;
41184145
}
41194146
}
4147+
4148+
return true; // GPU execution succeeded
41204149
}
41214150
#endif
41224151

0 commit comments

Comments
 (0)