Skip to content

Commit 342d304

Browse files
committed
[1.3.70] 2026-03-19
- `deletePlantInstance()` now cleans up hidden prototype primitives from the Context when all plant instances have been deleted, preventing orphaned hidden primitives and materials that could never be freed. Added optional `include_hidden` parameter to `getAllPlantUUIDs()` to allow querying hidden prototype primitives. - CMake now verifies that the CUDA language can actually be enabled (e.g., Visual Studio MSBuild integration is installed) before attempting a GPU build in the radiation, collision detection, and energy balance plugins, preventing cryptic build failures when the CUDA toolkit is installed without Visual Studio integration. - Bundled Vulkan SDK headers and glslang 16.2.0 shader compiler source with the radiation plugin.
1 parent 83dbce9 commit 342d304

213 files changed

Lines changed: 456366 additions & 23 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/CHANGELOG.md

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

3+
# [1.3.70] 2026-03-19
4+
5+
## Plant Architecture
6+
- `deletePlantInstance()` now cleans up hidden prototype primitives from the Context when all plant instances have been deleted, preventing orphaned hidden primitives and materials that could never be freed. Added optional `include_hidden` parameter to `getAllPlantUUIDs()` to allow querying hidden prototype primitives.
7+
8+
## Radiation
9+
- Fixed camera pixel label UUID mapping where the CPU-side conversion from internal primitive indices to Helios UUIDs produced incorrect labels when `buildGeometryData` reordered primitives by parent object. The GPU intersection programs already store actual UUIDs directly, so the redundant conversion was removed.
10+
11+
## Build System
12+
- CMake now verifies that the CUDA language can actually be enabled (e.g., Visual Studio MSBuild integration is installed) before attempting a GPU build in the radiation, collision detection, and energy balance plugins, preventing cryptic build failures when the CUDA toolkit is installed without Visual Studio integration.
13+
- Bundled Vulkan SDK headers and glslang 16.2.0 shader compiler source with the radiation plugin.
14+
315
# [1.3.69] 2026-03-16
416

517
## Core

doc/UserGuide.dox

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@
235235

236236
The NVIDIA CUDA library is required to build the Aerial LiDAR plugin. For the radiation model, CUDA enables the OptiX backend (which is the preferred backend on NVIDIA systems), but is not required if you use the Vulkan backend instead. Consult this page for help choosing the right CUDA version based on your C++ compiler and GPU compute capability: \ref ChoosingCUDA.
237237

238+
\note <b>Install Visual Studio before the CUDA Toolkit.</b> The CUDA installer copies MSBuild integration files (<tt>.props</tt>, <tt>.targets</tt>, <tt>.xml</tt>) into Visual Studio's <tt>BuildCustomizations</tt> directory. If Visual Studio is not installed first, these files are placed only in the CUDA Toolkit directory and CMake will fail with "No CUDA toolset found." If you have already installed CUDA before Visual Studio, you can fix this by reinstalling the CUDA Toolkit (or by manually copying the files from <tt>C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v1X.X\\extras\\visual_studio_integration\\MSBuildExtensions\\</tt> into your Visual Studio <tt>BuildCustomizations</tt> directory).
239+
238240
The latest CUDA version can be downloaded here: <a href="https://developer.nvidia.com/cuda-downloads">https://developer.nvidia.com/cuda-downloads</a> (note that if you installed Visual Studio 2019 instead of 2022, you'll need to install CUDA 10.2 instead of the latest version). Download the base CUDA Toolkit installer for your Windows OS. Installing from the "exe (network)" type will result in a smaller file being downloaded. Click through the installer and accept all the default options. By default, it should have installed to 'C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v1X.X\\', where v1X.X is the version that you installed. You can verify this installation location using the file browser.
239241

240242
In order to use CUDA within CLion, you need to tell it where to find the CUDA toolkit. To do this, open up the settings in CLion, and go to "Build, Execution, Deployment" and then to CMake. Click the icon next to the "Environment" field to edit environmental variables. Find the variable named "Path", and double click to edit it. At the end of the list of directories, add the path to the CUDA bin folder, i.e., 'C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v1X.X\\bin;'. Make sure there is a semicolon before and after the path, and that you replace the v1X.X with your actual CUDA version. It is important to hit the return key, which should cause the Path variable list to turn blue. When you hit OK, the Path variable should be listed under the Environment field.

plugins/collisiondetection/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Mo
1111
# Try to find CUDA toolkit
1212
find_package(CUDAToolkit QUIET)
1313

14+
# Verify that the CUDA language can actually be enabled (e.g., VS integration is installed)
15+
if(CUDAToolkit_FOUND)
16+
include(CheckLanguage)
17+
check_language(CUDA)
18+
if(NOT CMAKE_CUDA_COMPILER)
19+
message(STATUS "CUDA toolkit found but CUDA language could not be enabled (missing Visual Studio integration?) - falling back to CPU-only build")
20+
set(CUDAToolkit_FOUND FALSE)
21+
endif()
22+
endif()
23+
1424
if(CUDAToolkit_FOUND)
1525
message(STATUS "CUDA found - building with GPU acceleration")
1626
set(CMAKE_CUDA_COMPILER ${CUDAToolkit_NVCC_EXECUTABLE})

plugins/energybalance/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Mo
1010
# Try to find CUDA toolkit (optional)
1111
find_package(CUDAToolkit QUIET)
1212

13+
# Verify that the CUDA language can actually be enabled (e.g., VS integration is installed)
14+
if(CUDAToolkit_FOUND)
15+
include(CheckLanguage)
16+
check_language(CUDA)
17+
if(NOT CMAKE_CUDA_COMPILER)
18+
message(STATUS "CUDA toolkit found but CUDA language could not be enabled (missing Visual Studio integration?) - falling back to CPU-only build")
19+
set(CUDAToolkit_FOUND FALSE)
20+
endif()
21+
endif()
22+
1323
if(CUDAToolkit_FOUND)
1424
set(CMAKE_CUDA_COMPILER ${CUDAToolkit_NVCC_EXECUTABLE})
1525

plugins/plantarchitecture/doc/PlantArchitecture.dox

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,7 @@ The Object IDs and Primitive UUIDs of an entire plant, or organ groups in the pl
11251125
<tr> <td>\ref PlantArchitecture::getPlantFlowerObjectIDs()</td><td>Returns a vector of Object IDs for all flower objects.</td> </tr>
11261126
<tr> <td>\ref PlantArchitecture::getPlantFruitObjectIDs()</td><td>Returns a vector of Object IDs for all fruit objects.</td> </tr>
11271127
<tr> <td colspan=2> **Primitive UUIDs** </td> </tr>
1128-
<tr> <td>\ref PlantArchitecture::getAllPlantUUIDs()</td><td>Returns a vector of UUIDs for all primitives the entire plant.</td> </tr>
1128+
<tr> <td>\ref PlantArchitecture::getAllPlantUUIDs(uint, bool)</td><td>Returns a vector of UUIDs for all primitives in the entire plant. Optionally includes hidden prototype primitives when \p include_hidden is set to true.</td> </tr>
11291129
</table>
11301130

11311131
Note that individual methods to get UUIDs for all organ types are not provided. Instead, the user can query the Object IDs for each organ type and then get the corresponding UUIDs using the Context method \ref helios::Context::getObjectPrimitiveUUIDs().

plugins/plantarchitecture/include/PlantArchitecture.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,7 @@ class PlantArchitecture {
19211921
//! Delete an existing plant instance
19221922
/**
19231923
* \param[in] plantID ID of the plant instance to be deleted.
1924+
* \note When all plant instances have been deleted, hidden prototype objects are also cleaned up from the Context.
19241925
*/
19251926
void deletePlantInstance(uint plantID);
19261927

@@ -2653,9 +2654,10 @@ class PlantArchitecture {
26532654
//! Get primitive UUIDs for all primitives in a given plant
26542655
/**
26552656
* \param[in] plantID ID of the plant instance.
2656-
* \return Vector of primitive UUIDs for all primitives in the plant.
2657+
* \param[in] include_hidden If true, also include UUIDs of hidden prototype primitives managed by this PlantArchitecture instance (prototypes are shared across all plant instances, not specific to one plant).
2658+
* \return Vector of primitive UUIDs for all primitives in the plant (and optionally hidden prototypes).
26572659
*/
2658-
[[nodiscard]] std::vector<uint> getAllPlantUUIDs(uint plantID) const;
2660+
[[nodiscard]] std::vector<uint> getAllPlantUUIDs(uint plantID, bool include_hidden = false) const;
26592661

26602662
//! Get object IDs for all internode (Tube) objects for a given plant
26612663
/**
@@ -3045,6 +3047,12 @@ class PlantArchitecture {
30453047
// Key is the prototype function pointer; value index is the unique fruit prototype
30463048
std::map<uint (*)(helios::Context *context_ptr, uint subdivisions), std::vector<uint>> unique_fruit_prototype_objIDs;
30473049

3050+
//! Get object IDs for all hidden prototype objects managed by this PlantArchitecture instance
3051+
[[nodiscard]] std::vector<uint> getAllPrototypeObjectIDs() const;
3052+
3053+
//! Delete all hidden prototype objects from the Context and clear the prototype maps
3054+
void deleteAllPrototypes();
3055+
30483056
bool build_context_geometry_internode = true;
30493057
bool build_context_geometry_petiole = true;
30503058
bool build_context_geometry_peduncle = true;

plugins/plantarchitecture/src/PlantArchitecture.cpp

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4452,8 +4452,59 @@ std::vector<uint> PlantArchitecture::getAllPlantObjectIDs(uint plantID) const {
44524452
return objIDs;
44534453
}
44544454

4455-
std::vector<uint> PlantArchitecture::getAllPlantUUIDs(uint plantID) const {
4456-
return context_ptr->getObjectPrimitiveUUIDs(getAllPlantObjectIDs(plantID));
4455+
std::vector<uint> PlantArchitecture::getAllPrototypeObjectIDs() const {
4456+
std::vector<uint> objIDs;
4457+
for (const auto &[key, prototype_vec] : unique_leaf_prototype_objIDs) {
4458+
for (const auto &leaflet_vec : prototype_vec) {
4459+
for (uint objID : leaflet_vec) {
4460+
if (context_ptr->doesObjectExist(objID)) {
4461+
objIDs.push_back(objID);
4462+
}
4463+
}
4464+
}
4465+
}
4466+
for (const auto &[key, prototype_vec] : unique_closed_flower_prototype_objIDs) {
4467+
for (uint objID : prototype_vec) {
4468+
if (context_ptr->doesObjectExist(objID)) {
4469+
objIDs.push_back(objID);
4470+
}
4471+
}
4472+
}
4473+
for (const auto &[key, prototype_vec] : unique_open_flower_prototype_objIDs) {
4474+
for (uint objID : prototype_vec) {
4475+
if (context_ptr->doesObjectExist(objID)) {
4476+
objIDs.push_back(objID);
4477+
}
4478+
}
4479+
}
4480+
for (const auto &[key, prototype_vec] : unique_fruit_prototype_objIDs) {
4481+
for (uint objID : prototype_vec) {
4482+
if (context_ptr->doesObjectExist(objID)) {
4483+
objIDs.push_back(objID);
4484+
}
4485+
}
4486+
}
4487+
return objIDs;
4488+
}
4489+
4490+
void PlantArchitecture::deleteAllPrototypes() {
4491+
std::vector<uint> prototype_objIDs = getAllPrototypeObjectIDs();
4492+
for (uint objID : prototype_objIDs) {
4493+
context_ptr->deleteObject(objID);
4494+
}
4495+
unique_leaf_prototype_objIDs.clear();
4496+
unique_open_flower_prototype_objIDs.clear();
4497+
unique_closed_flower_prototype_objIDs.clear();
4498+
unique_fruit_prototype_objIDs.clear();
4499+
}
4500+
4501+
std::vector<uint> PlantArchitecture::getAllPlantUUIDs(uint plantID, bool include_hidden) const {
4502+
std::vector<uint> objIDs = getAllPlantObjectIDs(plantID);
4503+
if (include_hidden) {
4504+
std::vector<uint> prototype_objIDs = getAllPrototypeObjectIDs();
4505+
objIDs.insert(objIDs.end(), prototype_objIDs.begin(), prototype_objIDs.end());
4506+
}
4507+
return context_ptr->getObjectPrimitiveUUIDs(objIDs);
44574508
}
44584509

44594510
std::vector<uint> PlantArchitecture::getPlantInternodeObjectIDs(uint plantID) const {
@@ -4882,6 +4933,10 @@ void PlantArchitecture::deletePlantInstance(uint plantID) {
48824933
context_ptr->deleteObject(getAllPlantObjectIDs(plantID));
48834934

48844935
plant_instances.erase(plantID);
4936+
4937+
if (plant_instances.empty()) {
4938+
deleteAllPrototypes();
4939+
}
48854940
}
48864941

48874942
void PlantArchitecture::deletePlantInstance(const std::vector<uint> &plantIDs) {

plugins/plantarchitecture/tests/selfTest.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2524,6 +2524,82 @@ DOCTEST_TEST_CASE("PlantArchitecture setProgressCallback") {
25242524
}
25252525
}
25262526

2527+
DOCTEST_TEST_CASE("getAllPlantUUIDs with include_hidden parameter") {
2528+
Context context;
2529+
PlantArchitecture plantarchitecture(&context);
2530+
plantarchitecture.disableMessages();
2531+
plantarchitecture.loadPlantModelFromLibrary("bean");
2532+
uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(make_vec3(0, 0, 0), 5000);
2533+
2534+
std::vector<uint> uuids_default = plantarchitecture.getAllPlantUUIDs(plantID);
2535+
std::vector<uint> uuids_no_hidden = plantarchitecture.getAllPlantUUIDs(plantID, false);
2536+
std::vector<uint> uuids_with_hidden = plantarchitecture.getAllPlantUUIDs(plantID, true);
2537+
2538+
// Default behavior should match explicit false
2539+
DOCTEST_CHECK(uuids_default.size() == uuids_no_hidden.size());
2540+
2541+
// include_hidden=true should return more UUIDs (the hidden prototypes)
2542+
DOCTEST_CHECK(uuids_with_hidden.size() > uuids_no_hidden.size());
2543+
}
2544+
2545+
DOCTEST_TEST_CASE("deletePlantInstance cleans up prototypes when all plants deleted") {
2546+
Context context;
2547+
PlantArchitecture plantarchitecture(&context);
2548+
plantarchitecture.disableMessages();
2549+
plantarchitecture.loadPlantModelFromLibrary("bean");
2550+
2551+
uint plantID1 = plantarchitecture.buildPlantInstanceFromLibrary(make_vec3(0, 0, 0), 5000);
2552+
uint plantID2 = plantarchitecture.buildPlantInstanceFromLibrary(make_vec3(1, 0, 0), 5000);
2553+
2554+
// Identify hidden prototype UUIDs
2555+
std::vector<uint> all_uuids = plantarchitecture.getAllPlantUUIDs(plantID1, true);
2556+
std::vector<uint> visible_uuids = plantarchitecture.getAllPlantUUIDs(plantID1, false);
2557+
DOCTEST_CHECK(all_uuids.size() > visible_uuids.size());
2558+
2559+
// Collect prototype UUIDs (those in all but not in visible)
2560+
std::set<uint> visible_set(visible_uuids.begin(), visible_uuids.end());
2561+
std::vector<uint> prototype_uuids;
2562+
for (uint uuid : all_uuids) {
2563+
if (visible_set.find(uuid) == visible_set.end()) {
2564+
prototype_uuids.push_back(uuid);
2565+
}
2566+
}
2567+
DOCTEST_CHECK(prototype_uuids.size() > 0);
2568+
2569+
// Delete first plant — prototypes should survive
2570+
plantarchitecture.deletePlantInstance(plantID1);
2571+
for (uint uuid : prototype_uuids) {
2572+
DOCTEST_CHECK(context.doesPrimitiveExist(uuid));
2573+
}
2574+
2575+
// Delete second plant — prototypes should now be cleaned up
2576+
plantarchitecture.deletePlantInstance(plantID2);
2577+
for (uint uuid : prototype_uuids) {
2578+
DOCTEST_CHECK(!context.doesPrimitiveExist(uuid));
2579+
}
2580+
}
2581+
2582+
DOCTEST_TEST_CASE("deletePlantInstance preserves prototypes when plants remain") {
2583+
Context context;
2584+
PlantArchitecture plantarchitecture(&context);
2585+
plantarchitecture.disableMessages();
2586+
plantarchitecture.loadPlantModelFromLibrary("bean");
2587+
2588+
uint plantID1 = plantarchitecture.buildPlantInstanceFromLibrary(make_vec3(0, 0, 0), 5000);
2589+
uint plantID2 = plantarchitecture.buildPlantInstanceFromLibrary(make_vec3(1, 0, 0), 5000);
2590+
2591+
// Get prototype UUIDs via the second plant
2592+
std::vector<uint> uuids_with_hidden = plantarchitecture.getAllPlantUUIDs(plantID2, true);
2593+
std::vector<uint> uuids_without_hidden = plantarchitecture.getAllPlantUUIDs(plantID2, false);
2594+
DOCTEST_CHECK(uuids_with_hidden.size() > uuids_without_hidden.size());
2595+
2596+
// Delete first plant — prototypes should still be accessible for remaining plant
2597+
plantarchitecture.deletePlantInstance(plantID1);
2598+
2599+
std::vector<uint> uuids_after = plantarchitecture.getAllPlantUUIDs(plantID2, true);
2600+
DOCTEST_CHECK(uuids_after.size() > plantarchitecture.getAllPlantUUIDs(plantID2, false).size());
2601+
}
2602+
25272603
int PlantArchitecture::selfTest(int argc, char **argv) {
25282604
return helios::runDoctestWithValidation(argc, argv);
25292605
}

plugins/radiation/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ message(STATUS "[Radiation] FORCE_VULKAN_BACKEND = ${FORCE_VULKAN_BACKEND}")
3333
# Try to find CUDA/OptiX (optional)
3434
find_package(CUDAToolkit QUIET)
3535

36+
# Verify that the CUDA language can actually be enabled (e.g., VS integration is installed)
37+
if(CUDAToolkit_FOUND AND NOT FORCE_VULKAN_BACKEND)
38+
include(CheckLanguage)
39+
check_language(CUDA)
40+
if(NOT CMAKE_CUDA_COMPILER)
41+
message(STATUS "CUDA toolkit found but CUDA language could not be enabled (missing Visual Studio integration?) - falling back to Vulkan-only build")
42+
set(CUDAToolkit_FOUND FALSE)
43+
endif()
44+
endif()
45+
3646
set(HAVE_OPTIX FALSE)
3747
set(HAVE_OPTIX8 FALSE)
3848
set(OPTIX_BACKEND_SOURCES "")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Language: Cpp
2+
IndentWidth: 4
3+
PointerAlignment: Left
4+
BreakBeforeBraces: Custom
5+
BraceWrapping: { AfterFunction: true, AfterControlStatement: false }
6+
IndentCaseLabels: false
7+
ReflowComments: false
8+
ColumnLimit: 120
9+
AccessModifierOffset: -4
10+
AlignTrailingComments: true
11+
AllowShortBlocksOnASingleLine: false
12+
AllowShortIfStatementsOnASingleLine: false
13+
AllowShortLoopsOnASingleLine: false

0 commit comments

Comments
 (0)