diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d9129e0429..3bf74c38c4 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -24,7 +24,7 @@ jobs: # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, macos-latest] #os: [ubuntu-latest, windows-latest] build_type: [Release] #c_compiler: [gcc, clang, cl] @@ -36,10 +36,14 @@ jobs: - os: ubuntu-latest c_compiler: gcc cpp_compiler: g++ - #- os: ubuntu-latest - # c_compiler: clang - # cpp_compiler: clang++ - #exclude: + # macOS (Apple Silicon arm64 runner) with Apple Clang + - os: macos-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + # the default gcc entry must not apply to the macOS runner + - os: macos-latest + c_compiler: gcc #- os: windows-latest # c_compiler: gcc #- os: windows-latest @@ -52,12 +56,18 @@ jobs: with: submodules: recursive - - name: Install system dependencies + - name: Install system dependencies (Linux) + if: runner.os == 'Linux' run: | sudo apt update sudo apt-get update sudo apt-get -y install libglm-dev fuse libfuse2 ocl-icd-opencl-dev pocl-opencl-icd libassimp-dev libopencv-dev libfftw3-dev libgsl-dev libspdlog-dev + - name: Install system dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew install gsl assimp glm opencv libomp spdlog opencl-headers opencl-clhpp-headers + - name: Set reusable strings # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. id: strings diff --git a/.gitmodules b/.gitmodules index b91ffc032a..7bf68f1541 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,7 +19,8 @@ url = git@github.com:llohse/libnpy.git [submodule "external/CLWrapper"] path = external/CLWrapper - url = git@github.com:otto-link/CLWrapper.git + url = https://github.com/Leonhardmaster2/CLWrapper.git + branch = ios [submodule "external/PointSampler"] path = external/PointSampler url = git@github.com:otto-link/PointSampler.git diff --git a/CMakeLists.txt b/CMakeLists.txt index b57d524eec..f9a8bf8a1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,23 +36,33 @@ if(MSVC) add_definitions(-DHMAP_MSVC) - add_compile_options(/W4 /Od) + # do NOT pass /Od here: add_compile_options lands after the per-config flags + # on the MSVC command line, where it overrides Release's /O2 (last flag wins), + # silently disabling optimization in every Release build + add_compile_options(/W4 "$<$>:/fp:fast>") add_compile_definitions(M_PI=3.14159265358979323846) -elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") +# MATCHES so that AppleClang (macOS default compiler) is covered too: +# STREQUAL "Clang" silently skipped this branch on macOS, which left Apple +# Silicon builds without any optimization flags (same bug class as the MSVC +# /Od issue fixed in 7237678) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|AppleClang)$") - message(STATUS "Compiler: Clang") + message(STATUS "Compiler: Clang (${CMAKE_CXX_COMPILER_ID})") add_compile_options( -Wall -Wextra -Wpedantic - -O3 - -Ofast - -ffast-math -pthread -fPIC + # do NOT apply optimization flags for Debug builds; mirrors the MSVC + # branch ($<$>:...>); on single-config generators + # without an explicit build type the flags apply by default; note that + # -Ofast is deprecated in recent clang, "-O3 -ffast-math" is equivalent + $<$>:-O3> + $<$>:-ffast-math> -Wno-deprecated-enum-enum-conversion -Wno-header-guard -Wno-gnu-zero-variadic-macro-arguments) @@ -78,6 +88,20 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() +# ----------------------------------------------------------------------------- +# Architecture tuning +# ----------------------------------------------------------------------------- + +# Apple Silicon: target the M1 baseline so the compiler can exploit the full +# NEON/FMA instruction set available on every Apple Silicon chip (M1 and up) +if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") + + message(STATUS "CPU tuning: Apple Silicon (-mcpu=apple-m1)") + + add_compile_options($<$>:-mcpu=apple-m1>) + +endif() + # ----------------------------------------------------------------------------- # Output # ----------------------------------------------------------------------------- @@ -90,15 +114,119 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) find_package(GSL REQUIRED COMPONENTS gsl gslcblas) find_package(assimp REQUIRED) + +# Homebrew assimp may embed an absolute libz.tbd path pointing at a +# CommandLineTools SDK that no longer exists; rebind ZLIB::ZLIB (and the +# assimp target link libraries) to the zlib from the active SDK when that +# happens (macOS only) +if(APPLE) + find_library( + HMAP_SDK_ZLIB z + PATHS "${CMAKE_OSX_SYSROOT}/usr/lib" + NO_DEFAULT_PATH) + + if(HMAP_SDK_ZLIB AND TARGET ZLIB::ZLIB) + get_target_property(_zlib_loc ZLIB::ZLIB IMPORTED_LOCATION) + + if(_zlib_loc AND NOT EXISTS "${_zlib_loc}") + set_target_properties(ZLIB::ZLIB PROPERTIES IMPORTED_LOCATION + "${HMAP_SDK_ZLIB}") + endif() + endif() + + if(HMAP_SDK_ZLIB AND TARGET assimp::assimp) + get_target_property(_assimp_libs assimp::assimp INTERFACE_LINK_LIBRARIES) + set(_assimp_libs_fixed "") + + foreach(_lib ${_assimp_libs}) + if(_lib MATCHES "CommandLineTools/.*libz\\.tbd" AND NOT EXISTS + "${_lib}") + list(APPEND _assimp_libs_fixed "${HMAP_SDK_ZLIB}") + else() + list(APPEND _assimp_libs_fixed "${_lib}") + endif() + endforeach() + + set_target_properties(assimp::assimp PROPERTIES INTERFACE_LINK_LIBRARIES + "${_assimp_libs_fixed}") + endif() +endif() find_package(glm REQUIRED) -find_package(OpenMP REQUIRED) find_package(OpenCV REQUIRED) find_package(OpenCL REQUIRED) +# On macOS the OpenCL C/C++ headers come from keg-only Homebrew packages +# (brew install opencl-headers opencl-clhpp-headers) which are not linked +# into the default include path: point the compiler at them +if(APPLE) + if(NOT DEFINED HOMEBREW_PREFIX) + execute_process( + COMMAND brew --prefix + OUTPUT_VARIABLE HOMEBREW_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + endif() + + foreach(_keg opencl-headers opencl-clhpp-headers) + if(EXISTS "${HOMEBREW_PREFIX}/opt/${_keg}/include") + include_directories("${HOMEBREW_PREFIX}/opt/${_keg}/include") + endif() + endforeach() +endif() + include_directories(${OpenCV_INCLUDE_DIRS}) # OpenMP -add_compile_options(${OpenMP_CXX_FLAGS}) +# +# Apple Clang does not ship OpenMP; on macOS it is provided by the Homebrew +# `libomp` keg (brew install libomp). Point FindOpenMP at it when present. +if(APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if(NOT DEFINED HOMEBREW_PREFIX) + execute_process( + COMMAND brew --prefix + OUTPUT_VARIABLE HOMEBREW_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + endif() + + if(EXISTS "${HOMEBREW_PREFIX}/opt/libomp") + set(OpenMP_C_FLAGS + "-Xpreprocessor -fopenmp -I${HOMEBREW_PREFIX}/opt/libomp/include") + set(OpenMP_CXX_FLAGS + "-Xpreprocessor -fopenmp -I${HOMEBREW_PREFIX}/opt/libomp/include") + set(OpenMP_C_LIB_NAMES libomp) + set(OpenMP_CXX_LIB_NAMES libomp) + set(OpenMP_libomp_LIBRARY + "${HOMEBREW_PREFIX}/opt/libomp/lib/libomp.dylib") + endif() +endif() + +find_package(OpenMP QUIET) + +if(NOT OpenMP_CXX_FOUND) + if(APPLE) + # Only a few kernels use OpenMP (flow accumulation, drainage basins, + # local metrics); tile-based threading keeps working without it + message( + WARNING + "OpenMP not found: `brew install libomp` to enable it. Building " + "without OpenMP support.") + # without -fopenmp, clang ignores `#pragma omp` directives: silence the + # associated warnings + add_compile_options(-Wno-unknown-pragmas) + else() + message(FATAL_ERROR "OpenMP is required on this platform") + endif() +endif() + +# OpenMP +if(OpenMP_CXX_FOUND) + # OpenMP_CXX_FLAGS is a space-separated string: split it so that + # add_compile_options receives individual tokens (matters for the macOS + # libomp hint "-Xpreprocessor -fopenmp -I...") + separate_arguments(_openmp_cxx_flags UNIX_COMMAND "${OpenMP_CXX_FLAGS}") + add_compile_options(${_openmp_cxx_flags}) +endif() # OpenCL add_compile_definitions(CL_HPP_MINIMUM_OPENCL_VERSION=120 diff --git a/HighMap/CMakeLists.txt b/HighMap/CMakeLists.txt index 0ec530b6e9..77468003f3 100644 --- a/HighMap/CMakeLists.txt +++ b/HighMap/CMakeLists.txt @@ -39,8 +39,12 @@ target_link_libraries( nn-c::nn-c NoiseLib terrain-descriptors - OpenMP::OpenMP_CXX ${OpenCV_LIBS} point_sampler clwrapper ${OpenCL_LIBRARIES}) + +# OpenMP is optional on macOS (see root CMakeLists) +if(OpenMP_CXX_FOUND) + target_link_libraries(${PROJECT_NAME} OpenMP::OpenMP_CXX) +endif() diff --git a/HighMap/include/highmap/erosion.hpp b/HighMap/include/highmap/erosion.hpp index 20736d8bd0..0df93d228d 100644 --- a/HighMap/include/highmap/erosion.hpp +++ b/HighMap/include/highmap/erosion.hpp @@ -849,33 +849,33 @@ void hydraulic_particle(Array &z, float angle_bias = 30.f); /** - * @brief Particle-based hydraulic erosion with flow-field coupling - * (McDonald's model): persistent per-cell discharge and momentum fields, - * exponentially filtered across iterations, couple particles through the - * mean local flow, producing coherent drainage networks; a bank-stability - * debris flow runs in the same solver loop against a separate sediment - * layer. Clean-room port of erosiv/soillib (LGPL-3, Nicholas McDonald), + * @brief Particle-based hydraulic erosion with flow-field coupling (McDonald's + * model): persistent per-cell discharge and momentum fields, exponentially + * filtered across iterations, couple particles through the mean local flow, + * producing coherent drainage networks; a bank-stability debris flow runs in + * the same solver loop against a separate sediment layer. Clean-room port of + * erosiv/soillib (LGPL-3, Nicholas McDonald), * https://github.com/erosiv/soillib. * - * Parameters are physical: the terrain is interpreted as a - * world_extent_km x world_extent_km domain with height range z_scale_km. - * This makes behavior consistent across resolutions. + * Parameters are physical: the terrain is interpreted as a world_extent_km x + * world_extent_km domain with height range z_scale_km. This makes behavior + * consistent across resolutions. * - * @warning Single-resolution runs numerically diverge at high resolutions - * (>= 1024^2) beyond a few hundred steps with default parameters — use + * @warning Single-resolution runs numerically diverge at high resolutions (>= + * 1024^2) beyond a few hundred steps with default parameters — use * hydraulic_mcdonald_multiscale for high-resolution terrain. * - * Results are reproducible up to atomic scheduling order (particles read - * fields that other particles concurrently modify via atomic adds — a - * property shared with the reference implementation). + * Results are reproducible up to atomic scheduling order (particles read fields + * that other particles concurrently modify via atomic adds — a property shared + * with the reference implementation). * - * @param z Input/output heightmap. In: bedrock. Out: bedrock - * plus sediment (total surface). + * @param z Input/output heightmap. In: bedrock. Out: bedrock plus + * sediment (total surface). * @param steps Number of erosion iterations. * @param seed Random seed number. * @param p_sediment_map Optional output: final sediment layer. - * @param p_discharge_map Optional output: water discharge field (usable as - * a river / water mask). + * @param p_discharge_map Optional output: water discharge field (usable as a + * river / water mask). * @param world_extent_km Physical domain edge length [km]. * @param z_scale_km Physical height range of z's [0, 1] span [km]. * @param samples Particles per iteration. @@ -927,11 +927,11 @@ void hydraulic_mcdonald(Array &z, /** * @brief Multiscale driver for hydraulic_mcdonald: erodes on a halving - * resolution ladder derived from z.shape (coarsest first), resampling the - * full model state (bedrock, sediment, discharge, momentum) between levels. - * The numerically stable route to high-resolution erosion with this model. - * {512, 256, 128} on a 1024^2 input runs 256^2 (512 steps), 512^2 (256), - * then 1024^2 (128). See hydraulic_mcdonald for the model and parameters. + * resolution ladder derived from z.shape (coarsest first), resampling the full + * model state (bedrock, sediment, discharge, momentum) between levels. The + * numerically stable route to high-resolution erosion with this model. + * {512, 256, 128} on a 1024^2 input runs 256^2 (512 steps), 512^2 (256), then + * 1024^2 (128). See hydraulic_mcdonald for the model and parameters. * * **Example** * @include ex_hydraulic_mcdonald.cpp diff --git a/HighMap/include/highmap/hydrology/drainage_basin.hpp b/HighMap/include/highmap/hydrology/drainage_basin.hpp index 3dd8c12355..a8516162a8 100644 --- a/HighMap/include/highmap/hydrology/drainage_basin.hpp +++ b/HighMap/include/highmap/hydrology/drainage_basin.hpp @@ -1,6 +1,14 @@ /* Copyright (c) 2026 Otto Link. Distributed under the terms of the GNU General Public License. The full license is in the file LICENSE, distributed with this software. */ + +/** + * @file drainage_basin.hpp + * @author Otto Link (otto.link.bv@gmail.com) + * @brief Header file for DrainageBasin class and hydrology utilities. + * @copyright Copyright (c) 2026 + */ + #pragma once #include #include @@ -18,67 +26,221 @@ namespace hmap { +/** + * @class DrainageBasin + * @brief Represents a drainage basin network constructed on a 3D terrain mesh. + * + * This class handles the construction and analysis of hydrological flow + * networks (receivers, streams, outlets) on a 3D triangular mesh. It allows + * simulating + * river network development, calculating Strahler orders, inverting receiver + * maps, breaching lakes, and updating elevations based on response times. + */ class DrainageBasin { public: + /** + * @brief Construct a new Drainage Basin object. + * @param xyz_ Input 3D points representing the terrain vertices. + */ DrainageBasin(std::vector xyz_); + /** + * @brief Get the 3D coordinates of the terrain vertices. + * @return A const reference to the vector of 3D coordinates. + */ const std::vector &get_xyz() const; - size_t size() const; - void to_csv(const std::string &filename) const; + + /** + * @brief Get the number of vertices in the basin. + * @return The size as a size_t. + */ + size_t size() const; + + /** + * @brief Export the drainage basin data to a CSV file. + * @param filename Path to the output CSV file. + */ + void to_csv(const std::string &filename) const; // --- Geometry / Mesh --- + /** + * @brief Get the underlying terrain tri mesh (const). + * @return A const reference to the TerrainTriMesh. + */ const TerrainTriMesh &get_mesh() const; - TerrainTriMesh &get_mesh(); + /** + * @brief Get the underlying terrain tri mesh (non-const). + * @return A reference to the TerrainTriMesh. + */ + TerrainTriMesh &get_mesh(); + + /** + * @brief Compute the area of each vertex in the mesh. + * @return A vector containing the computed vertex areas. + */ std::vector compute_vertex_areas() const; - void remap(float zmin = 0.f, float zmax = 1.f); + + /** + * @brief Remap the elevation (z) values of the mesh vertices to a target + * range. + * @param zmin Minimum target elevation value. + * @param zmax Maximum target elevation value. + */ + void remap(float zmin = 0.f, float zmax = 1.f); // --- Flow graph construction --- + /** + * @brief Compute the flow receiver for each vertex deterministically. + */ void compute_receivers(); + + /** + * @brief Compute the flow receiver for each vertex with noise for stochastic + * variations. + * @param seed Seed for random generation. + * @param noise_strength Strength of the noise added to elevations during + * receiver computation. + */ void compute_receivers(unsigned int seed, float noise_strength = 0.25f); + + /** + * @brief Update the stream tree stochastically. + * @param seed Seed for random generation. + * @param noise_strength Strength of noise. + */ void update_stream_tree(unsigned int seed, float noise_strength); + + /** + * @brief Update the stream tree deterministically. + */ void update_stream_tree(); + + /** + * @brief Update cached traversal orders for upstream/downstream computations. + */ void update_traversals(); + /** + * @brief Get the indices of the outlets in the basin. + * @return A reference to the vector of outlet indices. + */ std::vector &get_outlets() const; - void set_outlets(const std::vector &outlet_indices); + + /** + * @brief Set the outlets of the basin. + * @param outlet_indices Vector containing the new outlet indices. + */ + void set_outlets(const std::vector &outlet_indices); + + /** + * @brief Get the receiver index of each vertex. + * @return A const reference to the vector of receiver indices. + */ const std::vector &get_receivers() const; + /** + * @brief Invert the receiver map to build a map of children/downstream + * receivers. + */ void invert_receiver_map(); // --- Basin topology utilities --- - std::vector compute_is_ridge_node() const; - std::vector compute_strahler_order() const; + /** + * @brief Compute whether each vertex is a ridge node (no upstream flow). + * @return A vector of booleans indicating ridge nodes. + */ + std::vector compute_is_ridge_node() const; + + /** + * @brief Compute the Strahler stream order for each vertex. + * @return A vector containing the Strahler order of each vertex. + */ + std::vector compute_strahler_order() const; + + /** + * @brief Find subroots of the flow network. + * @return A pair containing a vector of subroots indices and a boolean + * status. + */ std::pair, bool> find_subroots(); - std::vector> get_main_channels() const; + + /** + * @brief Get the main channel paths of the flow network. + * @return A vector of main channels, each represented as a vector of vertex + * indices. + */ + std::vector> get_main_channels() const; + + /** + * @brief Remove lakes by draining local depressions. + * @param subroot Vector of subroot indices to process. + */ void remove_lakes(const std::vector &subroot); // --- Hydrology computations --- + /** + * @brief Compute response times of the basin vertices. + * @param area_acc Accumulated area for each vertex. + * @param erodibility Erodibility coefficient for each vertex. + * @param m_exp Erodibility exponent. + * @return A vector of response times. + */ std::vector compute_response_times( const std::vector &area_acc, const std::vector &erodibility, float m_exp) const; + /** + * @brief Perform flow breaching to resolve depressions. + */ void flow_breach(); + /** + * @brief Compute the breaching paths for depressions. + * @return A vector of paths, each represented as a vector of 3D points. + */ std::vector> flow_breach_paths(); + /** + * @brief Update terrain elevations based on response times and uplift. + * @param response_times Vector of vertex response times. + * @param uplift_rate Rate of tectonic uplift. + * @param max_slope Maximum allowed slope for each vertex. + * @return The maximum change in elevation. + */ float update_elevations(const std::vector &response_times, float uplift_rate, const std::vector &max_slope); + /** + * @brief Accumulate contributing area down the network by outlet. + * @param area Input area contribution of each vertex. + * @param acc Output accumulated area. + */ void accumulate_area_by_outlet(const std::vector &area, std::vector &acc) const; // --- Traversal helpers --- + /** + * @brief Get the cached upstream traversal order from a specific outlet. + * @param outlet Index of the outlet. + * @return Const reference to the vector of vertex indices in upstream + * order. + */ const std::vector &for_each_upstream(size_t outlet) const; + /** + * @brief Get the cached downstream traversal order from a specific outlet. + * @param outlet Index of the outlet. + * @return A pair of reverse iterators for traversing downstream. + */ auto for_each_downstream(size_t outlet) const { const auto &t = traversals.at(outlet); @@ -111,16 +273,43 @@ class DrainageBasin // --- FUNCTIONS +/** + * @brief Find local minima along the border of a set of 3D coordinates. + * @param xyz Vector of 3D points. + * @param eps Tolerance for coordinates comparison. + * @return A vector of indices of the border minima. + */ std::vector find_border_minima(const std::vector &xyz, float eps = 1e-6f); +/** + * @brief Find sinks along the border of a terrain tri mesh. + * @param mesh Input terrain triangular mesh. + * @param eps Tolerance for border coordinate comparison. + * @return A vector of indices of the border sinks. + */ std::vector find_border_sinks(TerrainTriMesh &mesh, float eps = 1e-6f); +/** + * @brief Performs retopology of a heightmap to generate a 3D point cloud. + * @param z Input 2D heightmap array. + * @param max_error Maximum permitted elevation error. + * @param max_triangles Maximum number of triangles to generate. + * @param max_points Maximum number of points to generate. + * @return A vector of 3D coordinates representing the + * retopologized vertices. + */ std::vector heightmap_retopology(const Array &z, float max_error, int max_triangles = 0, int max_points = 0); +/** + * @brief Sample a specified number of points along the border. + * @param xyz Vector of 3D points. + * @param nb Number of border points to sample. + * @return A vector of indices of the sampled border points. + */ std::vector sample_border_points(const std::vector &xyz, size_t nb); diff --git a/HighMap/include/highmap/hydrology/drainage_basin_cell_based.hpp b/HighMap/include/highmap/hydrology/drainage_basin_cell_based.hpp index d6e08eadfe..b046105e9d 100644 --- a/HighMap/include/highmap/hydrology/drainage_basin_cell_based.hpp +++ b/HighMap/include/highmap/hydrology/drainage_basin_cell_based.hpp @@ -1,6 +1,14 @@ /* Copyright (c) 2026 Otto Link. Distributed under the terms of the GNU General Public License. The full license is in the file LICENSE, distributed with this software. */ + +/** + * @file drainage_basin_cell_based.hpp + * @author Otto Link (otto.link.bv@gmail.com) + * @brief Header file for DrainageBasinCellBased class. + * @copyright Copyright (c) 2026 + */ + #pragma once #include #include @@ -10,70 +18,173 @@ namespace hmap { +/** + * @enum FlowDirectionMethod + * @brief Enumeration of available algorithms for computing flow directions. + */ enum FlowDirectionMethod : int { - FDM_D8, - FDM_PRIORITY_FLOOD + FDM_D8, ///< Standard D8 flow direction algorithm. + FDM_PRIORITY_FLOOD ///< Priority flood flow routing algorithm. }; +/** + * @class DrainageBasinCellBased + * @brief Represents a cell-based hydrology drainage basin network on a 2D + * heightmap grid. + * + * This class implements hydrological flow network computations (receivers, + * outlets, main channels, upstream traversals) on a regular grid represented by + * a 2D Array. + */ class DrainageBasinCellBased { public: // --- Construction --- + /** + * @brief Default constructor. + */ DrainageBasinCellBased() = default; + + /** + * @brief Construct a new cell-based drainage basin using a heightmap. + * @param z_ The input heightmap array. + */ DrainageBasinCellBased(const Array &z_); // --- Geometry / Mesh --- + /** + * @brief Get the underlying heightmap array. + * @return A const reference to the heightmap Array. + */ const Array &get_z() const; // --- Flow graph construction --- + /** + * @brief Compute flow receivers using standard D8, with optional noise. + * @param seed Seed for random generation. + * @param noise_strength Strength of elevation noise added for stochastic flow + * paths. + */ void compute_receivers(unsigned int seed = 0, float noise_strength = 0.f); + + /** + * @brief Compute flow receivers using the priority flood routing algorithm. + */ void compute_receivers_priority_flood(); + + /** + * @brief Update the stream tree stochastically. + * @param seed Seed for random generation. + * @param noise_strength Strength of noise. + */ void update_stream_tree(unsigned int seed, float noise_strength); + + /** + * @brief Update the stream tree deterministically. + */ void update_stream_tree(); + + /** + * @brief Update cached traversal orders for upstream/downstream computations. + */ void update_traversals(); + /** + * @brief Get the outlets of the basin. + * @return A vector of 2D grid coordinates of the outlets. + */ std::vector get_outlets() const; + + /** + * @brief Set the outlets of the basin. + * @param outlet_indices Vector of outlet 2D coordinates. + */ void set_outlets(const std::vector &outlet_indices); + /** + * @brief Compute upstream traversal orders for the entire grid. + * @return A vector of paths, each represented as a vector of 2D coordinates. + */ std::vector> compute_upstream_traversals(); // --- Basin topology utilities --- + /** + * @brief Find subroots of the flow network. + * @return A pair containing a matrix of subroots and a boolean status. + */ std::pair, bool> find_subroots(); - void remove_lakes(const Mat &subroot); + /** + * @brief Remove lakes by draining local depressions. + * @param subroot Matrix representing subroots. + */ + void remove_lakes(const Mat &subroot); + + /** + * @brief Get the main channel paths of the flow network. + * @return A vector of main channels, each represented as a vector of 2D grid + * coordinates. + */ std::vector> get_main_channels() const; // --- Hydrology computations --- + /** + * @brief Compute response times of the basin cells. + * @param area_acc Accumulated area array. + * @param erodibility Erodibility coefficient array. + * @param m_exp Erodibility exponent. + * @return An Array of response times. + */ Array compute_response_times(const Array &area_acc, const Array &erodibility, float m_exp) const; + /** + * @brief Update elevations based on response times and uplift. + * @param response_times Array of cell response times. + * @param uplift_rate Rate of tectonic uplift. + * @param max_slope Maximum allowed slope array. + * @return The maximum change in elevation. + */ float update_elevations(const Array &response_times, float uplift_rate, const Array &max_slope); + /** + * @brief Accumulate contributing area down the network by outlet. + * @param acc Output accumulated area array. + */ void accumulate_area_by_outlet(Array &acc) const; + /** + * @brief Perform flow breaching to resolve depressions. + */ void flow_breach(); // --- Members --- - Array z; + Array z; ///< The heightmap array. - Mat outlets_mask; - Mat receivers; - Mat> children; - Mat roots; + Mat outlets_mask; ///< Mask indicating outlet cells. + Mat receivers; ///< Grid of receiver coordinates. + Mat> children; ///< Grid of children coordinates. + Mat roots; ///< Grid of basin root + // coordinates. - std::unordered_map, IVec2Hash> traversals; + std::unordered_map, IVec2Hash> + traversals; ///< + // Cached + // traversal + // paths. - const glm::ivec2 null_cell = glm::ivec2(-1, -1); + const glm::ivec2 null_cell = glm::ivec2(-1, -1); ///< Constant representing an + // invalid/null cell. private: // constants @@ -83,6 +194,13 @@ class DrainageBasinCellBased {1.f, M_SQRT1_2, 1.f, M_SQRT1_2, 1.f, M_SQRT1_2, 1.f, M_SQRT1_2}; }; +/** + * @brief Invert the receiver map to build a map of children/downstream + * receivers. + * @param receivers Input grid of receiver coordinates. + * @return A matrix of vectors representing children coordinates for + * each cell. + */ Mat> invert_receiver_map( const Mat &receivers); diff --git a/HighMap/include/highmap/internal/string_utils.hpp b/HighMap/include/highmap/internal/string_utils.hpp index 1ebe125778..c5058a320b 100644 --- a/HighMap/include/highmap/internal/string_utils.hpp +++ b/HighMap/include/highmap/internal/string_utils.hpp @@ -25,8 +25,9 @@ namespace hmap * @note If the input file has no extension, the suffix is added directly to the * filename. * - * @example - * @code std::filesystem::path path = "example.txt"; + * **Example** + * @code{.cpp} + * std::filesystem::path path = "example.txt"; * std::filesystem::path new_path = add_filename_suffix(path, "_backup"); * std::cout << new_path; // Outputs "example_backup.txt" * @endcode @@ -62,8 +63,11 @@ std::filesystem::path make_unique_temp_dir(const std::string &prefix); * @return A new string padded with leading zeros to reach the specified * length. * - * @example zfill("42", 5); // returns "00042" + * **Example** + * @code{.cpp} + * zfill("42", 5); // returns "00042" * zfill("12345", 5); // returns "12345" + * @endcode */ std::string zfill(const std::string &str, int n_zero); diff --git a/HighMap/include/highmap/openmp.hpp b/HighMap/include/highmap/openmp.hpp index 6aed29b9b7..4a76bec039 100644 --- a/HighMap/include/highmap/openmp.hpp +++ b/HighMap/include/highmap/openmp.hpp @@ -10,7 +10,14 @@ namespace hmap { -bool init_openmp(int num_threads = 8); +/** + * @brief Initialize OpenMP and set the number of threads to use. + * + * @param num_threads Number of threads; pass 0 (default) to use every + * available processor (`omp_get_num_procs()`). + * @return true if OpenMP is enabled, false otherwise. + */ +bool init_openmp(int num_threads = 0); void log_openmp_info(); diff --git a/HighMap/include/highmap/random.hpp b/HighMap/include/highmap/random.hpp index c0c9f770a7..6aab0f5efb 100644 --- a/HighMap/include/highmap/random.hpp +++ b/HighMap/include/highmap/random.hpp @@ -55,8 +55,8 @@ float splitmix64_to_unit_float(unsigned int seed, size_t k); * mantissa precision of a `float`, providing a deterministic pseudo-random * uniform distribution. * - * @param h 64-bit hash value. - * @return Uniform floating-point value in the range [0, 1). + * @param h 64-bit hash value. + * @return Uniform floating-point value in the range [0, 1). */ float uniform01(uint64_t h); diff --git a/HighMap/include/highmap/statistics.hpp b/HighMap/include/highmap/statistics.hpp index 0dede8c61b..fd137baee5 100644 --- a/HighMap/include/highmap/statistics.hpp +++ b/HighMap/include/highmap/statistics.hpp @@ -34,7 +34,7 @@ enum NormalizationMethod : int * @return Length scale in grid cells. * * **Example** - * @include ex_cautocorr_length_scale.cpp + * @include ex_autocorr_length_scale.cpp * * See unit tests: @ref test_autocorr_length_scale.cpp */ diff --git a/HighMap/include/highmap/synthesis.hpp b/HighMap/include/highmap/synthesis.hpp index 716807c208..4c123591a8 100644 --- a/HighMap/include/highmap/synthesis.hpp +++ b/HighMap/include/highmap/synthesis.hpp @@ -51,10 +51,10 @@ namespace hmap * @return Array Resulting synthesized heightmap. * * **Example** - * @include non_parametric_sampling.cpp + * @include ex_non_parametric_sampling.cpp * * **Result** - * @image html non_parametric_sampling.png + * @image html ex_non_parametric_sampling.png */ Array non_parametric_sampling(const Array &array, glm::ivec2 patch_shape, diff --git a/HighMap/include/highmap/transform.hpp b/HighMap/include/highmap/transform.hpp index 2c1adc0fc4..2785376356 100644 --- a/HighMap/include/highmap/transform.hpp +++ b/HighMap/include/highmap/transform.hpp @@ -35,10 +35,10 @@ namespace hmap * @param array Input array to be flipped horizontally. * * **Example** - * @include flip_lr.cpp + * @include ex_flip_ud.cpp * * **Result** - * @image html flip_lr.png + * @image html ex_flip_ud.png */ void flip_lr(Array &array); @@ -51,10 +51,10 @@ void flip_lr(Array &array); * @param array Input array to be flipped vertically. * * **Example** - * @include flip_ud.cpp + * @include ex_flip_ud.cpp * * **Result** - * @image html flip_ud.png + * @image html ex_flip_ud.png */ void flip_ud(Array &array); @@ -127,6 +127,7 @@ void rot90(Array &array); * * @param array Input array to be rotated. * @param angle Rotation angle in degrees. + * @param zoom_in If true, zoom in after rotation (default is true). * @param zero_padding If true, use zero-padding to fill the borders; otherwise, * use symmetry (default is false). * diff --git a/HighMap/include/highmap/virtual_array/tile_storage.hpp b/HighMap/include/highmap/virtual_array/tile_storage.hpp index 1e38656a69..615a61bc7b 100644 --- a/HighMap/include/highmap/virtual_array/tile_storage.hpp +++ b/HighMap/include/highmap/virtual_array/tile_storage.hpp @@ -93,6 +93,10 @@ class RamTileStorage : public TileStorage private: std::unordered_map tiles; + // tiles can be lazily created from concurrent distributed_tile_loop() + // workers, the map needs protection against data races; mutable so that + // clone() can lock from a const context + mutable std::mutex mutex; }; // ===================================== diff --git a/HighMap/include/highmap/virtual_array/virtual_array.hpp b/HighMap/include/highmap/virtual_array/virtual_array.hpp index 7ca5017de0..6a6c5022e0 100644 --- a/HighMap/include/highmap/virtual_array/virtual_array.hpp +++ b/HighMap/include/highmap/virtual_array/virtual_array.hpp @@ -8,6 +8,7 @@ * @copyright Copyright (c) 2025 */ #pragma once +#include #include #include diff --git a/HighMap/include/highmap/virtual_array/virtual_array.inl b/HighMap/include/highmap/virtual_array/virtual_array.inl index 27a8cd6be8..a976c2c22b 100644 --- a/HighMap/include/highmap/virtual_array/virtual_array.inl +++ b/HighMap/include/highmap/virtual_array/virtual_array.inl @@ -2,6 +2,9 @@ Public License. The full license is in the file LICENSE, distributed with this software. */ #pragma once +#if defined(__APPLE__) +#include +#endif struct TileAccess { @@ -184,6 +187,25 @@ void sequential_tile_loop(const VirtualArray &ref_va, } } +/// Return a sensible default worker count for tile-parallel computations. +/// +/// On Apple Silicon (heterogeneous P/E cores) the pool is sized after the +/// performance cores: long per-tile tasks scheduled on efficiency cores +/// become stragglers that delay the whole batch. Other platforms fall back +/// to the full hardware concurrency. +inline int recommended_thread_count() +{ +#if defined(__APPLE__) + int nperf = 0; + size_t len = sizeof(nperf); + if (sysctlbyname("hw.perflevel0.physicalcpu", &nperf, &len, nullptr, 0) == + 0 && + nperf > 0) + return nperf; +#endif + return int(std::thread::hardware_concurrency()); +} + template void distributed_tile_loop(const VirtualArray &ref_va, RegionDispatcher &&dispatcher, @@ -193,17 +215,23 @@ void distributed_tile_loop(const VirtualArray &ref_va, const int ny = ceil_div(ref_va.shape.y, ref_va.tile_shape.y); const int ntasks = nx * ny; - if (nthreads <= 0) nthreads = std::thread::hardware_concurrency(); + if (nthreads <= 0) nthreads = recommended_thread_count(); nthreads = std::min(nthreads, ntasks); - std::vector> futures; - futures.reserve(nthreads); + // dynamic scheduling: workers pull the next tile through a shared atomic + // counter. This balances uneven per-tile costs (data-dependent erosion) + // and keeps heterogeneous cores (e.g. Apple Silicon P/E clusters) busy + // until the last task, unlike the previous static strided partitioning + std::atomic next_task{0}; - auto worker = [&](int thread_id) + auto worker = [&]() { - for (int k = thread_id; k < ntasks; k += nthreads) + while (true) { + const int k = next_task.fetch_add(1, std::memory_order_relaxed); + if (k >= ntasks) break; + const int ty = k / nx; const int tx = k % nx; @@ -212,10 +240,11 @@ void distributed_tile_loop(const VirtualArray &ref_va, } }; + std::vector> futures; + futures.reserve(nthreads); + for (int t = 0; t < nthreads; ++t) - { - futures.emplace_back(std::async(std::launch::async, worker, t)); - } + futures.emplace_back(std::async(std::launch::async, worker)); for (auto &f : futures) f.get(); diff --git a/HighMap/src/gpu_opencl/gpu_opencl.cpp b/HighMap/src/gpu_opencl/gpu_opencl.cpp index 94bf956430..7bb7abb42d 100644 --- a/HighMap/src/gpu_opencl/gpu_opencl.cpp +++ b/HighMap/src/gpu_opencl/gpu_opencl.cpp @@ -33,267 +33,267 @@ bool init_opencl() { if (!clwrapper::DeviceManager::get_instance().is_ready()) return false; + auto &km = clwrapper::KernelManager::get_instance(); + km.clear_sources(); + + std::string opencl_build_options = "-cl-fast-relaxed-math " + "-cl-mad-enable " + "-cl-no-signed-zeros " + "-cl-denorms-are-zero " + "-cl-finite-math-only "; + + km.set_build_options(opencl_build_options); + // load and build kernels - std::string code; - code.reserve(270000); + auto add = [&](const std::string &src) { km.add_kernel(src, false, false); }; - code += + add( #include "kernels/_common_index.cl" - ; - code += + ); + add( #include "kernels/_common_math.cl" - ; - code += + ); + add( #include "kernels/_common_rand.cl" - ; - code += + ); + add( #include "kernels/_common_sort.cl" - ; + ); // - code += + add( #include "kernels/advection_particle.cl" - ; - code += + ); + add( #include "kernels/advection_warp.cl" - ; - code += + ); + add( #include "kernels/bilateral_filter.cl" - ; - code += + ); + add( #include "kernels/blend_poisson_bf.cl" - ; - code += + ); + add( #include "kernels/coastal_fetch.cl" - ; - code += + ); + add( #include "kernels/curvature_quadric.cl" - ; - code += + ); + add( #include "kernels/directional_blur.cl" - ; - code += + ); + add( #include "kernels/eulerian_transport.cl" - ; - code += + ); + add( #include "kernels/expand.cl" - ; - code += + ); + add( #include "kernels/flow_accum_stochastic.cl" - ; - code += + ); + add( #include "kernels/flow_direction_d8.cl" - ; - code += + ); + add( #include "kernels/gabor_wave.cl" - ; - code += + ); + add( #include "kernels/gavoronoise.cl" - ; - code += + ); + add( #include "kernels/generate_riverbed.cl" - ; - code += + ); + add( #include "kernels/gradient_norm.cl" - ; - code += + ); + add( #include "kernels/harmonic_interpolation.cl" - ; - code += + ); + add( #include "kernels/hemisphere_field.cl" - ; - code += + ); + add( #include "kernels/hydraulic_mcdonald.cl" - ; - code += + ); + add( #include "kernels/hydraulic_particle.cl" - ; - code += + ); + add( #include "kernels/hydraulic_schott.cl" - ; - code += + ); + add( #include "kernels/hydraulic_vpipes.cl" - ; - code += + ); + add( #include "kernels/interpolate_array.cl" - ; - code += + ); + add( #include "kernels/jump_flooding.cl" - ; - code += + ); + add( #include "kernels/laplace.cl" - ; - code += + ); + add( #include "kernels/laplacian_fract.cl" - ; - code += + ); + add( #include "kernels/local_max.cl" - ; - code += + ); + add( #include "kernels/local_mean.cl" - ; - code += + ); + add( #include "kernels/local_min.cl" - ; - code += + ); + add( #include "kernels/local_relief.cl" - ; - code += + ); + add( #include "kernels/local_skewness.cl" - ; - code += + ); + add( #include "kernels/local_variance.cl" - ; - code += + ); + add( #include "kernels/local_z_score.cl" - ; - code += + ); + add( #include "kernels/maximum_smooth.cl" - ; - code += + ); + add( #include "kernels/mean_shift.cl" - ; - code += + ); + add( #include "kernels/median_3x3.cl" - ; - code += + ); + add( #include "kernels/minimum_smooth.cl" - ; - code += + ); + add( #include "kernels/mountain_range_radial.cl" - ; - code += + ); + add( #include "kernels/noise_a.cl" - ; - code += + ); + add( #include "kernels/noise_b.cl" - ; - code += + ); + add( #include "kernels/normal_displacement.cl" - ; - code += + ); + add( #include "kernels/phase_averaging.cl" - ; - code += + ); + add( #include "kernels/phase_field.cl" - ; - code += + ); + add( #include "kernels/plateau.cl" - ; - code += + ); + add( #include "kernels/polygon_field.cl" - ; - code += + ); + add( #include "kernels/project_slope_along_direction.cl" - ; - code += + ); + add( #include "kernels/rotate.cl" - ; - code += + ); + add( #include "kernels/ruggedness.cl" - ; - code += + ); + add( #include "kernels/rugosity.cl" - ; - code += + ); + add( #include "kernels/sdf_2d_polyline.cl" - ; - code += + ); + add( #include "kernels/shallow_viscous_flow.cl" - ; - code += + ); + add( #include "kernels/skeleton.cl" - ; - code += + ); + add( #include "kernels/smooth_cpulse.cl" - ; - code += + ); + add( #include "kernels/snow_simulation.cl" - ; - code += + ); + add( #include "kernels/sparse_max_convolution.cl" - ; - code += + ); + add( #include "kernels/thermal.cl" - ; - code += + ); + add( #include "kernels/thermal_flatten.cl" - ; - code += + ); + add( #include "kernels/thermal_inflate.cl" - ; - code += + ); + add( #include "kernels/thermal_olsen.cl" - ; - code += + ); + add( #include "kernels/thermal_rib.cl" - ; - code += + ); + add( #include "kernels/thermal_ridge.cl" - ; - code += + ); + add( #include "kernels/thermal_schott.cl" - ; - code += + ); + add( #include "kernels/thermal_scree.cl" - ; - code += + ); + add( #include "kernels/topographic_position_index.cl" - ; - code += + ); + add( #include "kernels/vorolines.cl" - ; - code += + ); + add( #include "kernels/voronoi_base.cl" - ; - code += + ); + add( #include "kernels/voronoi_edge_distance.cl" - ; - code += + ); + add( #include "kernels/voronoi_fbm.cl" - ; - code += + ); + add( #include "kernels/voronoi_main.cl" - ; - code += + ); + add( #include "kernels/voronoise.cl" - ; - code += + ); + add( #include "kernels/vororand_main.cl" - ; - code += + ); + add( #include "kernels/warp.cl" - ; - code += + ); + add( #include "kernels/water_depth_filter.cl" - ; - code += + ); + add( #include "kernels/wavelet_noise.cl" - ; + ); // - code += + add( #include "kernels/rifts.cl" - ; - code += + ); + add( #include "kernels/strata.cl" - ; - code += + ); + add( #include "kernels/strata_cells.cl" - ; - code += + ); + add( #include "kernels/strata_terrace.cl" - ; - - std::string opencl_build_options = "-cl-fast-relaxed-math " - "-cl-mad-enable " - "-cl-no-signed-zeros " - "-cl-denorms-are-zero " - "-cl-finite-math-only "; - - clwrapper::KernelManager::get_instance().set_build_options( - opencl_build_options); + ); - constexpr bool clear_sources = true; - clwrapper::KernelManager::get_instance().add_kernel(code, clear_sources); + km.build_program(); return true; } diff --git a/HighMap/src/gpu_opencl/kernels/shallow_viscous_flow.cl b/HighMap/src/gpu_opencl/kernels/shallow_viscous_flow.cl index 14115c2879..a39aaa3ae4 100644 --- a/HighMap/src/gpu_opencl/kernels/shallow_viscous_flow.cl +++ b/HighMap/src/gpu_opencl/kernels/shallow_viscous_flow.cl @@ -18,10 +18,8 @@ kernel void shallow_viscous_flow(read_only image2d_t z, const sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST; - const float diag = 0.70710678f; float h = TGET(h_in, i, j); - float zc = TGET(z, i, j); // neighbors float hxp = TGET(h_in, i + 1, j); @@ -29,40 +27,50 @@ kernel void shallow_viscous_flow(read_only image2d_t z, float hyp = TGET(h_in, i, j + 1); float hym = TGET(h_in, i, j - 1); + // a dry cell surrounded by dry cells cannot evolve (common case once the + // fluid front has stabilized), skip the expensive mobility evaluation + if (h == 0.f && hxp == 0.f && hxm == 0.f && hyp == 0.f && hym == 0.f) + { + TSET(h_out, i, j, 0.f); + return; + } + + float zc = TGET(z, i, j); + + // total water surface elevations float Hc = h + zc; float Hxp = hxp + TGET(z, i + 1, j); float Hxm = hxm + TGET(z, i - 1, j); float Hyp = hyp + TGET(z, i, j + 1); float Hym = hym + TGET(z, i, j - 1); - // face mobilities - float Mp = pow(0.5f * (h + hxp), power) / viscosity; - float Mm = pow(0.5f * (h + hxm), power) / viscosity; - float Myp = pow(0.5f * (h + hyp), power) / viscosity; - float Mym = pow(0.5f * (h + hym), power) / viscosity; - - // fluxes through faces - float qxp = -Mp * (Hxp - Hc); - float qxm = -Mm * (Hc - Hxm); - float qyp = -Myp * (Hyp - Hc); - float qym = -Mym * (Hc - Hym); - - // divergence - float dhdt = -(qxp - qxm + qyp - qym); + // face mobilities (power-law fluid, viscosity = 1 -> water) + float inv_visc = 1.f / viscosity; + float Mp = pow(fmax(0.f, 0.5f * (h + hxp)), power) * inv_visc; + float Mm = pow(fmax(0.f, 0.5f * (h + hxm)), power) * inv_visc; + float Myp = pow(fmax(0.f, 0.5f * (h + hyp)), power) * inv_visc; + float Mym = pow(fmax(0.f, 0.5f * (h + hym)), power) * inv_visc; - // explicit update - float h_new = fmax(0.f, h + dt * dhdt); + // Outgoing flux through each face, capped so that the source cell cannot + // give away more than a quarter of its depth per face and per time step. + // Both cells sharing a face compute the same capped value (same mobility, + // same elevation drop, same source depth), hence fluxes are antisymmetric + // between neighbors: the update preserves positivity (h >= 0) and is + // strictly mass-conservative, which makes the explicit scheme stable even + // when dt is locally too large (no negative-depth clamping artifacts). + float cap = 0.25f / dt; - // smoothing - float h_avg = (TGET(h_in, i - 1, j) + TGET(h_in, i + 1, j) + - TGET(h_in, i, j - 1) + TGET(h_in, i, j + 1) + - diag * (TGET(h_in, i - 1, j - 1) + TGET(h_in, i + 1, j - 1) + - TGET(h_in, i - 1, j + 1) + TGET(h_in, i + 1, j + 1))) / - (4.f + 4.f * diag); + float out = min(Mp * fmax(0.f, Hc - Hxp), h * cap) + + min(Mm * fmax(0.f, Hc - Hxm), h * cap) + + min(Myp * fmax(0.f, Hc - Hyp), h * cap) + + min(Mym * fmax(0.f, Hc - Hym), h * cap); - float k_visc = 0.001f; - // h_new = mix(h_new, h_avg, k_visc); + float in = min(Mp * fmax(0.f, Hxp - Hc), hxp * cap) + + min(Mm * fmax(0.f, Hxm - Hc), hxm * cap) + + min(Myp * fmax(0.f, Hyp - Hc), hyp * cap) + + min(Mym * fmax(0.f, Hym - Hc), hym * cap); - TSET(h_out, i, j, h_new); + // explicit update (fmax only guards against round-off) + TSET(h_out, i, j, fmax(0.f, h + dt * (in - out))); } )"" diff --git a/HighMap/src/hydrology/flow_simulation.cpp b/HighMap/src/hydrology/flow_simulation.cpp index ef5b540028..b8af6bf0e0 100644 --- a/HighMap/src/hydrology/flow_simulation.cpp +++ b/HighMap/src/hydrology/flow_simulation.cpp @@ -40,63 +40,161 @@ Array flow_simulation(const Array &z, Array ft(shape); // top Array fb(shape); // bottom + Array u(shape); // velocity (output of the water pass) + Array v(shape); + + // All the state (terrain, water depth, fluxes) is uploaded once and then + // kept GPU-resident for the whole simulation using ping-pong images; the + // host is only touched again for the final readback. + + clwrapper::Run run_fp("hydraulic_vpipes_flow_pass"); + + // inputs (slot 0-5) + run_fp.bind_imagef("z", z.vector, shape.x, shape.y); + run_fp.bind_imagef("fl", + fl.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + run_fp.bind_imagef("fr", + fr.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + run_fp.bind_imagef("ft", + ft.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + run_fp.bind_imagef("fb", + fb.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + run_fp.bind_imagef("d1", + d.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + + // outputs (slot 6-9), ping-pong counterparts of the flux inputs + run_fp.bind_imagef("fl_out", + fl.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + run_fp.bind_imagef("fr_out", + fr.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + run_fp.bind_imagef("ft_out", + ft.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + run_fp.bind_imagef("fb_out", + fb.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + + // ping-pong counterpart of the water depth input (not a kernel argument + // of this pass, only shared with the water pass below) + cl::Image2D img_d_alt = run_fp.create_imagef("d2", + d.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + + run_fp.bind_arguments(shape.x, + shape.y, + dt, + flux_diffusion ? 1 : 0, + flux_diffusion_strength); + + // --- water transport, reads the device images of the flow pass directly + + clwrapper::Run run_wa("hydraulic_vpipes_water_pass"); + + run_wa.bind_imagef("z", z.vector, shape.x, shape.y, run_fp.get_imagef("z")); + run_wa.bind_imagef("fl", + fl.vector, + shape.x, + shape.y, + run_fp.get_imagef("fl_out")); + run_wa.bind_imagef("fr", + fr.vector, + shape.x, + shape.y, + run_fp.get_imagef("fr_out")); + run_wa.bind_imagef("ft", + ft.vector, + shape.x, + shape.y, + run_fp.get_imagef("ft_out")); + run_wa.bind_imagef("fb", + fb.vector, + shape.x, + shape.y, + run_fp.get_imagef("fb_out")); + run_wa.bind_imagef("d1", + d.vector, + shape.x, + shape.y, + run_fp.get_imagef("d1")); + run_wa.bind_imagef("d2_out", d.vector, shape.x, shape.y, img_d_alt); + run_wa.bind_imagef("u_out", u.vector, shape.x, shape.y, true); // outputs + run_wa.bind_imagef("v_out", v.vector, shape.x, shape.y, true); + + run_wa.bind_arguments(shape.x, shape.y, dt, water_height); + + // --- main loop, swap image arguments to ping-pong between the two states + + const cl::Image2D img_flux_a[4] = {run_fp.get_imagef("fl"), + run_fp.get_imagef("fr"), + run_fp.get_imagef("ft"), + run_fp.get_imagef("fb")}; + const cl::Image2D img_flux_b[4] = {run_fp.get_imagef("fl_out"), + run_fp.get_imagef("fr_out"), + run_fp.get_imagef("ft_out"), + run_fp.get_imagef("fb_out")}; + const cl::Image2D img_d_main = run_fp.get_imagef("d1"); + for (int it = 0; it < iterations; ++it) { - - // --- flux update - - auto run_fp = clwrapper::Run("hydraulic_vpipes_flow_pass"); - - run_fp.bind_imagef("z", z.vector, shape.x, shape.y); // inputs - run_fp.bind_imagef("fl", fl.vector, shape.x, shape.y); - run_fp.bind_imagef("fr", fr.vector, shape.x, shape.y); - run_fp.bind_imagef("ft", ft.vector, shape.x, shape.y); - run_fp.bind_imagef("fb", fb.vector, shape.x, shape.y); - run_fp.bind_imagef("d1", d.vector, shape.x, shape.y); - - run_fp.bind_imagef("fl_out", fl.vector, shape.x, shape.y, true); // outputs - run_fp.bind_imagef("fr_out", fr.vector, shape.x, shape.y, true); - run_fp.bind_imagef("ft_out", ft.vector, shape.x, shape.y, true); - run_fp.bind_imagef("fb_out", fb.vector, shape.x, shape.y, true); - - run_fp.bind_arguments(shape.x, - shape.y, - dt, - flux_diffusion ? 1 : 0, - flux_diffusion_strength); + const int in_side = it & 1; + const int out_side = in_side ^ 1; + + // flux update: reads state #in_side, writes state #out_side + for (int k = 0; k < 4; ++k) + run_fp.set_argument(1 + k, + in_side == 0 ? img_flux_a[k] : img_flux_b[k]); + run_fp.set_argument(5, in_side == 0 ? img_d_main : img_d_alt); + for (int k = 0; k < 4; ++k) + run_fp.set_argument(6 + k, + out_side == 0 ? img_flux_a[k] : img_flux_b[k]); run_fp.execute({shape.x, shape.y}); - // update flux (from GPU to CPU) - run_fp.read_imagef("fl_out"); - run_fp.read_imagef("fr_out"); - run_fp.read_imagef("ft_out"); - run_fp.read_imagef("fb_out"); - - // --- water transport - - auto run_wa = clwrapper::Run("hydraulic_vpipes_water_pass"); - - run_wa.bind_imagef("z", z.vector, shape.x, shape.y); // inputs - run_wa.bind_imagef("fl", fl.vector, shape.x, shape.y); - run_wa.bind_imagef("fr", fr.vector, shape.x, shape.y); - run_wa.bind_imagef("ft", ft.vector, shape.x, shape.y); - run_wa.bind_imagef("fb", fb.vector, shape.x, shape.y); - run_wa.bind_imagef("d1", d.vector, shape.x, shape.y); - - Array u(shape), v(shape); - - run_wa.bind_imagef("d2_out", d.vector, shape.x, shape.y, true); // outputs - run_wa.bind_imagef("u_out", u.vector, shape.x, shape.y, true); - run_wa.bind_imagef("v_out", v.vector, shape.x, shape.y, true); - - run_wa.bind_arguments(shape.x, shape.y, dt, water_height); + // water transport: consumes the fluxes just computed + for (int k = 0; k < 4; ++k) + run_wa.set_argument(1 + k, + out_side == 0 ? img_flux_a[k] : img_flux_b[k]); + run_wa.set_argument(5, in_side == 0 ? img_d_main : img_d_alt); + run_wa.set_argument(6, out_side == 0 ? img_d_main : img_d_alt); run_wa.execute({shape.x, shape.y}); + } - run_wa.read_imagef("d2_out"); - run_wa.read_imagef("u_out"); - run_wa.read_imagef("v_out"); + // final readback of the water depth (the fluxes and velocities are never + // needed on the host) + if (iterations > 0) + { + if (iterations % 2 == 0) + run_fp.read_imagef("d1"); + else + run_fp.read_imagef("d2"); } // remove thin layer of remaining water @@ -121,30 +219,79 @@ Array flow_simulation_viscous(const Array &z, const glm::ivec2 shape = z.shape; Array d = water_height * depth_map; - - auto run = clwrapper::Run("shallow_viscous_flow"); - - run.bind_imagef("z", z.vector, shape.x, shape.y); // inputs - run.bind_imagef("d_in", d.vector, shape.x, shape.y); - run.bind_imagef("d_out", d.vector, shape.x, shape.y, true); // outputs - - run.bind_arguments(shape.x, shape.y, dt, viscosity, power); + Array d_alt(shape, 0.f); // readback buffer of the ping-pong counterpart + + // The simulation state stays on the GPU: two depth images are used in + // alternance (ping-pong), so one iteration is a single kernel launch with + // zero host/device transfer. The terrain is uploaded only once. + + clwrapper::Run run_a("shallow_viscous_flow"); + + run_a.bind_imagef("z", z.vector, shape.x, shape.y); // input + run_a.bind_imagef("d_in", + d.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + run_a.bind_imagef("d_out", + d_alt.vector, + shape.x, + shape.y, + clwrapper::Direction::INOUT); + + run_a.bind_arguments(shape.x, shape.y, dt, viscosity, power); + + clwrapper::Run run_b("shallow_viscous_flow"); + + run_b.bind_imagef("z", z.vector, shape.x, shape.y); // input + run_b.bind_imagef("d_in", + d_alt.vector, + shape.x, + shape.y, + run_a.get_imagef("d_out")); // cross-wired ping-pong + run_b.bind_imagef("d_out", + d.vector, + shape.x, + shape.y, + run_a.get_imagef("d_in")); + + run_b.bind_arguments(shape.x, shape.y, dt, viscosity, power); + + Array *p_d_last = &d; // host mirror of the latest device state + clwrapper::Run *p_run_last = nullptr; // who wrote it for (int it = 0; it < iterations; ++it) { + // refresh the time step regularly (needs one readback of the current + // depth map) if (it % 10 == 0) { - dt = helper_vflow_compute_adaptive_dt(d, viscosity, power); - run.set_argument(5, dt); - } + if (p_run_last) p_run_last->read_imagef("d_out"); - run.write_imagef("z"); - run.write_imagef("d_in"); + dt = helper_vflow_compute_adaptive_dt(*p_d_last, viscosity, power); + run_a.set_argument(5, dt); + run_b.set_argument(5, dt); + } - run.execute({shape.x, shape.y}); + if (it % 2 == 0) + { + run_a.execute({shape.x, shape.y}); + p_run_last = &run_a; + p_d_last = &d_alt; + } + else + { + run_b.execute({shape.x, shape.y}); + p_run_last = &run_b; + p_d_last = &d; + } + } - // update flux (from GPU to CPU) - run.read_imagef("d_out"); + // final readback + if (p_run_last) + { + p_run_last->read_imagef("d_out"); + if (p_d_last != &d) d = std::move(d_alt); } // remove thin layer of remaining water diff --git a/HighMap/src/openmp/openmp.cpp b/HighMap/src/openmp/openmp.cpp index 474c8e5cf8..05f07ee69a 100644 --- a/HighMap/src/openmp/openmp.cpp +++ b/HighMap/src/openmp/openmp.cpp @@ -16,6 +16,7 @@ namespace hmap bool init_openmp(int num_threads) { #ifdef _OPENMP + if (num_threads <= 0) num_threads = omp_get_num_procs(); omp_set_num_threads(num_threads); log_openmp_info(); return true; diff --git a/HighMap/src/virtual_array/ram_tile_storage.cpp b/HighMap/src/virtual_array/ram_tile_storage.cpp index 66d56e8d53..f16fac608d 100644 --- a/HighMap/src/virtual_array/ram_tile_storage.cpp +++ b/HighMap/src/virtual_array/ram_tile_storage.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "highmap/array.hpp" @@ -17,11 +18,21 @@ namespace hmap std::unique_ptr RamTileStorage::clone() const { - return std::make_unique(*this); + // std::mutex is not copyable: copy the tiles manually under the lock + auto clone = std::make_unique(); + + const std::lock_guard lock(this->mutex); + clone->tiles = this->tiles; + + return clone; } Array &RamTileStorage::get_tile(const TileRegion ®ion) { + // concurrent workers (VA_DISTRIBUTED mode) may request/insert tiles at + // the same time: guard the lazy creation path + const std::lock_guard lock(this->mutex); + auto it = tiles.find(region.key); if (it != tiles.end()) return it->second; diff --git a/README.md b/README.md index 9b242b1840..c4f3a9df43 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,27 @@ vcpkg install libpng glm opencl assimp opencv[openexr] You should then be able to build the sources using Visual Studio. +#### macOS + +Install the dependencies using [Homebrew](https://brew.sh): +``` bash +brew install gsl assimp glm opencv libomp spdlog opencl-headers opencl-clhpp-headers +``` + +Then build as on Linux: +``` bash +mkdir build && cd build +cmake .. +make +``` + +>[!NOTE] +> Apple Clang does not bundle OpenMP, the `libomp` package provides it. If +> `libomp` is missing the library still builds, but the few OpenMP-based +> kernels (flow accumulation, drainage basins, local metrics) run +> single-threaded. Apple Silicon builds are tuned with `-mcpu=apple-m1`. + + ### CMake Integration To integrate HighMap into your CMake-based project, follow these steps: diff --git a/docs/images/ex_chop.png b/docs/images/ex_chop.png new file mode 100644 index 0000000000..e6dbe8521b Binary files /dev/null and b/docs/images/ex_chop.png differ diff --git a/docs/images/ex_chop_max_smooth.png b/docs/images/ex_chop_max_smooth.png new file mode 100644 index 0000000000..89601407de Binary files /dev/null and b/docs/images/ex_chop_max_smooth.png differ diff --git a/docs/images/ex_color_adjust.png b/docs/images/ex_color_adjust.png new file mode 100644 index 0000000000..ba85bfba19 Binary files /dev/null and b/docs/images/ex_color_adjust.png differ diff --git a/docs/images/ex_gradient_norm_filtered.png b/docs/images/ex_gradient_norm_filtered.png new file mode 100644 index 0000000000..1af75a4ed1 Binary files /dev/null and b/docs/images/ex_gradient_norm_filtered.png differ diff --git a/docs/images/ex_local_max.png b/docs/images/ex_local_max.png new file mode 100644 index 0000000000..0018997479 Binary files /dev/null and b/docs/images/ex_local_max.png differ diff --git a/docs/images/ex_maximum_smooth.png b/docs/images/ex_maximum_smooth.png new file mode 100644 index 0000000000..e7de850314 Binary files /dev/null and b/docs/images/ex_maximum_smooth.png differ diff --git a/docs/images/ex_maximum_smooth_scalar.png b/docs/images/ex_maximum_smooth_scalar.png new file mode 100644 index 0000000000..d7a995251c Binary files /dev/null and b/docs/images/ex_maximum_smooth_scalar.png differ diff --git a/docs/images/ex_non_parametric_sampling.png b/docs/images/ex_non_parametric_sampling.png index a539faf0cc..0fc703fa98 100644 Binary files a/docs/images/ex_non_parametric_sampling.png and b/docs/images/ex_non_parametric_sampling.png differ diff --git a/docs/images/ex_path_bezier.png b/docs/images/ex_path_bezier.png new file mode 100644 index 0000000000..ca50af6fe4 Binary files /dev/null and b/docs/images/ex_path_bezier.png differ diff --git a/docs/images/ex_path_bezier_round.png b/docs/images/ex_path_bezier_round.png new file mode 100644 index 0000000000..ca50af6fe4 Binary files /dev/null and b/docs/images/ex_path_bezier_round.png differ diff --git a/docs/images/ex_path_bspline.png b/docs/images/ex_path_bspline.png new file mode 100644 index 0000000000..ca50af6fe4 Binary files /dev/null and b/docs/images/ex_path_bspline.png differ diff --git a/docs/images/ex_path_catmullrom.png b/docs/images/ex_path_catmullrom.png new file mode 100644 index 0000000000..ca50af6fe4 Binary files /dev/null and b/docs/images/ex_path_catmullrom.png differ diff --git a/docs/images/ex_path_decasteljau.png b/docs/images/ex_path_decasteljau.png new file mode 100644 index 0000000000..ca50af6fe4 Binary files /dev/null and b/docs/images/ex_path_decasteljau.png differ diff --git a/docs/images/ex_phemisphere_field.png b/docs/images/ex_phemisphere_field.png new file mode 100644 index 0000000000..70b7643aeb Binary files /dev/null and b/docs/images/ex_phemisphere_field.png differ diff --git a/examples/ex_chop/CMakeLists.txt b/examples/ex_chop/CMakeLists.txt new file mode 100644 index 0000000000..5382b375e9 --- /dev/null +++ b/examples/ex_chop/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_chop ex_chop.cpp) +target_link_libraries(ex_chop highmap) diff --git a/examples/ex_chop/ex_chop.cpp b/examples/ex_chop/ex_chop.cpp new file mode 100644 index 0000000000..8ed5e42c0e --- /dev/null +++ b/examples/ex_chop/ex_chop.cpp @@ -0,0 +1,16 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + int seed = 1; + + hmap::Array z = hmap::noise_fbm(hmap::NoiseType::PERLIN, shape, kw, seed); + hmap::remap(z); + + hmap::Array z1 = z; + hmap::chop(z1, 0.4f); + + hmap::export_banner_png("ex_chop.png", {z, z1}, hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_chop_max_smooth/CMakeLists.txt b/examples/ex_chop_max_smooth/CMakeLists.txt new file mode 100644 index 0000000000..a315e8b15c --- /dev/null +++ b/examples/ex_chop_max_smooth/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_chop_max_smooth ex_chop_max_smooth.cpp) +target_link_libraries(ex_chop_max_smooth highmap) diff --git a/examples/ex_chop_max_smooth/ex_chop_max_smooth.cpp b/examples/ex_chop_max_smooth/ex_chop_max_smooth.cpp new file mode 100644 index 0000000000..c61269742c --- /dev/null +++ b/examples/ex_chop_max_smooth/ex_chop_max_smooth.cpp @@ -0,0 +1,18 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + int seed = 1; + + hmap::Array z = hmap::noise_fbm(hmap::NoiseType::PERLIN, shape, kw, seed); + hmap::remap(z); + + hmap::Array z1 = z; + hmap::chop_max_smooth(z1, 0.6f); + + hmap::export_banner_png("ex_chop_max_smooth.png", + {z, z1}, + hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_color_adjust/CMakeLists.txt b/examples/ex_color_adjust/CMakeLists.txt new file mode 100644 index 0000000000..cc2d964538 --- /dev/null +++ b/examples/ex_color_adjust/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_color_adjust ex_color_adjust.cpp) +target_link_libraries(ex_color_adjust highmap) diff --git a/examples/ex_color_adjust/ex_color_adjust.cpp b/examples/ex_color_adjust/ex_color_adjust.cpp new file mode 100644 index 0000000000..c24293e6ea --- /dev/null +++ b/examples/ex_color_adjust/ex_color_adjust.cpp @@ -0,0 +1,28 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + int seed = 1; + + hmap::Array z = hmap::noise_fbm(hmap::NoiseType::PERLIN, shape, kw, seed); + hmap::remap(z); + + hmap::Tensor tex = hmap::colorize_slope_height_heatmap(z, + hmap::Cmap::VIRIDIS); + hmap::Array r = tex.get_slice(0); + hmap::Array g = tex.get_slice(1); + hmap::Array b = tex.get_slice(2); + + hmap::ColorAdjust params; + params.contrast = 1.5f; + params.saturation = 1.2f; + hmap::color_adjust(r, g, b, params, {0, 0}); + + hmap::Tensor tex_adj(z.shape, 3); + tex_adj.set_slice(0, r); + tex_adj.set_slice(1, g); + tex_adj.set_slice(2, b); + tex_adj.to_png("ex_color_adjust.png"); +} diff --git a/examples/ex_cv_mat_to_array/CMakeLists.txt b/examples/ex_cv_mat_to_array/CMakeLists.txt new file mode 100644 index 0000000000..a3a097b720 --- /dev/null +++ b/examples/ex_cv_mat_to_array/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_cv_mat_to_array ex_cv_mat_to_array.cpp) +target_link_libraries(ex_cv_mat_to_array highmap) diff --git a/examples/ex_cv_mat_to_array/ex_cv_mat_to_array.cpp b/examples/ex_cv_mat_to_array/ex_cv_mat_to_array.cpp new file mode 100644 index 0000000000..1e52e43277 --- /dev/null +++ b/examples/ex_cv_mat_to_array/ex_cv_mat_to_array.cpp @@ -0,0 +1,10 @@ +#include + +#include "highmap.hpp" + +int main(void) +{ + cv::Mat mat = cv::Mat::zeros(256, 256, CV_32FC1); + hmap::Array z = hmap::cv_mat_to_array(mat); + return 0; +} diff --git a/examples/ex_gradient_norm_filtered/CMakeLists.txt b/examples/ex_gradient_norm_filtered/CMakeLists.txt new file mode 100644 index 0000000000..1d4a669fb1 --- /dev/null +++ b/examples/ex_gradient_norm_filtered/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_gradient_norm_filtered ex_gradient_norm_filtered.cpp) +target_link_libraries(ex_gradient_norm_filtered highmap) diff --git a/examples/ex_gradient_norm_filtered/ex_gradient_norm_filtered.cpp b/examples/ex_gradient_norm_filtered/ex_gradient_norm_filtered.cpp new file mode 100644 index 0000000000..eec1b79636 --- /dev/null +++ b/examples/ex_gradient_norm_filtered/ex_gradient_norm_filtered.cpp @@ -0,0 +1,18 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + int seed = 1; + + hmap::Array z = hmap::noise_fbm(hmap::NoiseType::PERLIN, shape, kw, seed); + hmap::remap(z); + + hmap::Array g = hmap::gradient_norm_filtered(z, 5); + hmap::remap(g); + + hmap::export_banner_png("ex_gradient_norm_filtered.png", + {z, g}, + hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_local_max/CMakeLists.txt b/examples/ex_local_max/CMakeLists.txt new file mode 100644 index 0000000000..88cf3b2f91 --- /dev/null +++ b/examples/ex_local_max/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_local_max ex_local_max.cpp) +target_link_libraries(ex_local_max highmap) diff --git a/examples/ex_local_max/ex_local_max.cpp b/examples/ex_local_max/ex_local_max.cpp new file mode 100644 index 0000000000..791aa5b37b --- /dev/null +++ b/examples/ex_local_max/ex_local_max.cpp @@ -0,0 +1,18 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + int seed = 1; + + hmap::Array z = hmap::noise_fbm(hmap::NoiseType::PERLIN, shape, kw, seed); + hmap::remap(z); + + hmap::Array z_max = hmap::local_max(z, 5); + hmap::Array z_min = hmap::local_min(z, 5); + + hmap::export_banner_png("ex_local_max.png", + {z, z_max, z_min}, + hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_maximum_smooth/CMakeLists.txt b/examples/ex_maximum_smooth/CMakeLists.txt new file mode 100644 index 0000000000..2d5940bda0 --- /dev/null +++ b/examples/ex_maximum_smooth/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_maximum_smooth ex_maximum_smooth.cpp) +target_link_libraries(ex_maximum_smooth highmap) diff --git a/examples/ex_maximum_smooth/ex_maximum_smooth.cpp b/examples/ex_maximum_smooth/ex_maximum_smooth.cpp new file mode 100644 index 0000000000..ec5021f027 --- /dev/null +++ b/examples/ex_maximum_smooth/ex_maximum_smooth.cpp @@ -0,0 +1,18 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + + hmap::Array z1 = hmap::noise(hmap::NoiseType::SIMPLEX2, shape, kw, 1); + hmap::Array z2 = hmap::noise(hmap::NoiseType::SIMPLEX2, shape, kw, 2); + hmap::remap(z1); + hmap::remap(z2); + + hmap::Array z_smooth = hmap::maximum_smooth(z1, z2, 0.2f); + + hmap::export_banner_png("ex_maximum_smooth.png", + {z1, z2, z_smooth}, + hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_maximum_smooth_scalar/CMakeLists.txt b/examples/ex_maximum_smooth_scalar/CMakeLists.txt new file mode 100644 index 0000000000..d5054594a8 --- /dev/null +++ b/examples/ex_maximum_smooth_scalar/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_maximum_smooth_scalar ex_maximum_smooth_scalar.cpp) +target_link_libraries(ex_maximum_smooth_scalar highmap) diff --git a/examples/ex_maximum_smooth_scalar/ex_maximum_smooth_scalar.cpp b/examples/ex_maximum_smooth_scalar/ex_maximum_smooth_scalar.cpp new file mode 100644 index 0000000000..45df146d34 --- /dev/null +++ b/examples/ex_maximum_smooth_scalar/ex_maximum_smooth_scalar.cpp @@ -0,0 +1,30 @@ +#include + +#include "highmap.hpp" + +int main(void) +{ + float a = 0.5f; + float b = 0.7f; + float val = hmap::maximum_smooth(a, b, 0.2f); + + // Also generate an image for Doxygen + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + hmap::Array z = hmap::noise(hmap::NoiseType::SIMPLEX2, shape, kw, 1); + hmap::remap(z); + + hmap::Array z_smooth(shape); + for (int i = 0; i < shape.x; ++i) + { + for (int j = 0; j < shape.y; ++j) + { + z_smooth(i, j) = hmap::maximum_smooth(z(i, j), 0.5f, 0.2f); + } + } + + hmap::export_banner_png("ex_maximum_smooth_scalar.png", + {z, z_smooth}, + hmap::Cmap::VIRIDIS); + return 0; +} diff --git a/examples/ex_path_bezier/CMakeLists.txt b/examples/ex_path_bezier/CMakeLists.txt new file mode 100644 index 0000000000..5300747a5f --- /dev/null +++ b/examples/ex_path_bezier/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_path_bezier ex_path_bezier.cpp) +target_link_libraries(ex_path_bezier highmap) diff --git a/examples/ex_path_bezier/ex_path_bezier.cpp b/examples/ex_path_bezier/ex_path_bezier.cpp new file mode 100644 index 0000000000..e94d13e355 --- /dev/null +++ b/examples/ex_path_bezier/ex_path_bezier.cpp @@ -0,0 +1,17 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::Path path; + path.add_point({10.f, 10.f}); + path.add_point({50.f, 100.f}); + path.add_point({200.f, 50.f}); + path.add_point({240.f, 240.f}); + + hmap::Path bpath = hmap::bezier(path, 0.3f, 10); + + hmap::Array z({256, 256}, 0.f); + bpath.to_array(z); + + z.to_png("ex_path_bezier.png", hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_path_bezier_round/CMakeLists.txt b/examples/ex_path_bezier_round/CMakeLists.txt new file mode 100644 index 0000000000..3861cd6b05 --- /dev/null +++ b/examples/ex_path_bezier_round/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_path_bezier_round ex_path_bezier_round.cpp) +target_link_libraries(ex_path_bezier_round highmap) diff --git a/examples/ex_path_bezier_round/ex_path_bezier_round.cpp b/examples/ex_path_bezier_round/ex_path_bezier_round.cpp new file mode 100644 index 0000000000..1cbb10b870 --- /dev/null +++ b/examples/ex_path_bezier_round/ex_path_bezier_round.cpp @@ -0,0 +1,17 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::Path path; + path.add_point({10.f, 10.f}); + path.add_point({50.f, 100.f}); + path.add_point({200.f, 50.f}); + path.add_point({240.f, 240.f}); + + hmap::Path bpath = hmap::bezier_round(path, 0.3f, 10); + + hmap::Array z({256, 256}, 0.f); + bpath.to_array(z); + + z.to_png("ex_path_bezier_round.png", hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_path_bspline/CMakeLists.txt b/examples/ex_path_bspline/CMakeLists.txt new file mode 100644 index 0000000000..f69af7fdd6 --- /dev/null +++ b/examples/ex_path_bspline/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_path_bspline ex_path_bspline.cpp) +target_link_libraries(ex_path_bspline highmap) diff --git a/examples/ex_path_bspline/ex_path_bspline.cpp b/examples/ex_path_bspline/ex_path_bspline.cpp new file mode 100644 index 0000000000..5f5bce89a0 --- /dev/null +++ b/examples/ex_path_bspline/ex_path_bspline.cpp @@ -0,0 +1,17 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::Path path; + path.add_point({10.f, 10.f}); + path.add_point({50.f, 100.f}); + path.add_point({200.f, 50.f}); + path.add_point({240.f, 240.f}); + + hmap::Path bpath = hmap::bspline(path, 10); + + hmap::Array z({256, 256}, 0.f); + bpath.to_array(z); + + z.to_png("ex_path_bspline.png", hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_path_catmullrom/CMakeLists.txt b/examples/ex_path_catmullrom/CMakeLists.txt new file mode 100644 index 0000000000..0789e02b88 --- /dev/null +++ b/examples/ex_path_catmullrom/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_path_catmullrom ex_path_catmullrom.cpp) +target_link_libraries(ex_path_catmullrom highmap) diff --git a/examples/ex_path_catmullrom/ex_path_catmullrom.cpp b/examples/ex_path_catmullrom/ex_path_catmullrom.cpp new file mode 100644 index 0000000000..5330811558 --- /dev/null +++ b/examples/ex_path_catmullrom/ex_path_catmullrom.cpp @@ -0,0 +1,17 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::Path path; + path.add_point({10.f, 10.f}); + path.add_point({50.f, 100.f}); + path.add_point({200.f, 50.f}); + path.add_point({240.f, 240.f}); + + hmap::Path bpath = hmap::catmullrom(path, 10); + + hmap::Array z({256, 256}, 0.f); + bpath.to_array(z); + + z.to_png("ex_path_catmullrom.png", hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_path_decasteljau/CMakeLists.txt b/examples/ex_path_decasteljau/CMakeLists.txt new file mode 100644 index 0000000000..a00fc7c558 --- /dev/null +++ b/examples/ex_path_decasteljau/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_path_decasteljau ex_path_decasteljau.cpp) +target_link_libraries(ex_path_decasteljau highmap) diff --git a/examples/ex_path_decasteljau/ex_path_decasteljau.cpp b/examples/ex_path_decasteljau/ex_path_decasteljau.cpp new file mode 100644 index 0000000000..17454aa3ff --- /dev/null +++ b/examples/ex_path_decasteljau/ex_path_decasteljau.cpp @@ -0,0 +1,17 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::Path path; + path.add_point({10.f, 10.f}); + path.add_point({50.f, 100.f}); + path.add_point({200.f, 50.f}); + path.add_point({240.f, 240.f}); + + hmap::Path bpath = hmap::decasteljau(path, 10); + + hmap::Array z({256, 256}, 0.f); + bpath.to_array(z); + + z.to_png("ex_path_decasteljau.png", hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_path_remove_geometry_loops/CMakeLists.txt b/examples/ex_path_remove_geometry_loops/CMakeLists.txt new file mode 100644 index 0000000000..87ba1e4527 --- /dev/null +++ b/examples/ex_path_remove_geometry_loops/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_path_remove_geometry_loops ex_path_remove_geometry_loops.cpp) +target_link_libraries(ex_path_remove_geometry_loops highmap) diff --git a/examples/ex_path_remove_geometry_loops/ex_path_remove_geometry_loops.cpp b/examples/ex_path_remove_geometry_loops/ex_path_remove_geometry_loops.cpp new file mode 100644 index 0000000000..8a8d7d8029 --- /dev/null +++ b/examples/ex_path_remove_geometry_loops/ex_path_remove_geometry_loops.cpp @@ -0,0 +1,14 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::Path path; + path.add_point({10.f, 10.f}); + path.add_point({100.f, 10.f}); + path.add_point({100.f, 100.f}); + path.add_point({10.f, 100.f}); + path.add_point({10.f, 10.f}); // loop + + hmap::Path clean_path = hmap::remove_geometric_loops(path); + return 0; +} diff --git a/examples/ex_path_to_array/CMakeLists.txt b/examples/ex_path_to_array/CMakeLists.txt new file mode 100644 index 0000000000..6b26a1d107 --- /dev/null +++ b/examples/ex_path_to_array/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_path_to_array ex_path_to_array.cpp) +target_link_libraries(ex_path_to_array highmap) diff --git a/examples/ex_path_to_array/ex_path_to_array.cpp b/examples/ex_path_to_array/ex_path_to_array.cpp new file mode 100644 index 0000000000..ce653e8bc2 --- /dev/null +++ b/examples/ex_path_to_array/ex_path_to_array.cpp @@ -0,0 +1,12 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::Path path; + path.add_point({10.f, 10.f}); + path.add_point({240.f, 240.f}); + + hmap::Array z({256, 256}, 0.f); + path.to_array(z); + return 0; +} diff --git a/examples/ex_path_to_png/CMakeLists.txt b/examples/ex_path_to_png/CMakeLists.txt new file mode 100644 index 0000000000..adc800e01d --- /dev/null +++ b/examples/ex_path_to_png/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_path_to_png ex_path_to_png.cpp) +target_link_libraries(ex_path_to_png highmap) diff --git a/examples/ex_path_to_png/ex_path_to_png.cpp b/examples/ex_path_to_png/ex_path_to_png.cpp new file mode 100644 index 0000000000..29acfd5acd --- /dev/null +++ b/examples/ex_path_to_png/ex_path_to_png.cpp @@ -0,0 +1,11 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::Path path; + path.add_point({10.f, 10.f}); + path.add_point({240.f, 240.f}); + + path.to_png("ex_path_to_png_output.png"); + return 0; +} diff --git a/examples/ex_phemisphere_field/CMakeLists.txt b/examples/ex_phemisphere_field/CMakeLists.txt new file mode 100644 index 0000000000..6409030143 --- /dev/null +++ b/examples/ex_phemisphere_field/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_phemisphere_field ex_phemisphere_field.cpp) +target_link_libraries(ex_phemisphere_field highmap) diff --git a/examples/ex_phemisphere_field/ex_phemisphere_field.cpp b/examples/ex_phemisphere_field/ex_phemisphere_field.cpp new file mode 100644 index 0000000000..8b4f8a98f2 --- /dev/null +++ b/examples/ex_phemisphere_field/ex_phemisphere_field.cpp @@ -0,0 +1,14 @@ +#include "highmap.hpp" + +int main(void) +{ + hmap::gpu::init_opencl(); + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + int seed = 1; + + hmap::Array z = hmap::gpu::hemisphere_field(shape, kw, seed); + hmap::remap(z); + + z.to_png("ex_phemisphere_field.png", hmap::Cmap::VIRIDIS); +} diff --git a/examples/ex_to_exr/CMakeLists.txt b/examples/ex_to_exr/CMakeLists.txt new file mode 100644 index 0000000000..b37c4a892d --- /dev/null +++ b/examples/ex_to_exr/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_to_exr ex_to_exr.cpp) +target_link_libraries(ex_to_exr highmap) diff --git a/examples/ex_to_exr/ex_to_exr.cpp b/examples/ex_to_exr/ex_to_exr.cpp new file mode 100644 index 0000000000..f8f25105c7 --- /dev/null +++ b/examples/ex_to_exr/ex_to_exr.cpp @@ -0,0 +1,10 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + hmap::Array z = hmap::noise(hmap::NoiseType::SIMPLEX2, shape, kw, 1); + z.to_exr("ex_to_exr_output.exr"); + return 0; +} diff --git a/examples/ex_to_raw_16bit/CMakeLists.txt b/examples/ex_to_raw_16bit/CMakeLists.txt new file mode 100644 index 0000000000..f521b759bf --- /dev/null +++ b/examples/ex_to_raw_16bit/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_to_raw_16bit ex_to_raw_16bit.cpp) +target_link_libraries(ex_to_raw_16bit highmap) diff --git a/examples/ex_to_raw_16bit/ex_to_raw_16bit.cpp b/examples/ex_to_raw_16bit/ex_to_raw_16bit.cpp new file mode 100644 index 0000000000..b610ca49f5 --- /dev/null +++ b/examples/ex_to_raw_16bit/ex_to_raw_16bit.cpp @@ -0,0 +1,10 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + hmap::Array z = hmap::noise(hmap::NoiseType::SIMPLEX2, shape, kw, 1); + z.to_raw_16bit("ex_to_raw_16bit_output.raw"); + return 0; +} diff --git a/examples/ex_to_tiff/CMakeLists.txt b/examples/ex_to_tiff/CMakeLists.txt new file mode 100644 index 0000000000..c4030d3f25 --- /dev/null +++ b/examples/ex_to_tiff/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex_to_tiff ex_to_tiff.cpp) +target_link_libraries(ex_to_tiff highmap) diff --git a/examples/ex_to_tiff/ex_to_tiff.cpp b/examples/ex_to_tiff/ex_to_tiff.cpp new file mode 100644 index 0000000000..931fc33891 --- /dev/null +++ b/examples/ex_to_tiff/ex_to_tiff.cpp @@ -0,0 +1,10 @@ +#include "highmap.hpp" + +int main(void) +{ + glm::ivec2 shape = {256, 256}; + glm::vec2 kw = {4.f, 4.f}; + hmap::Array z = hmap::noise(hmap::NoiseType::SIMPLEX2, shape, kw, 1); + z.to_tiff("ex_to_tiff_output.tiff"); + return 0; +} diff --git a/external/CLWrapper b/external/CLWrapper index 5657a3edd2..69e0e8b6ed 160000 --- a/external/CLWrapper +++ b/external/CLWrapper @@ -1 +1 @@ -Subproject commit 5657a3edd2e4c01878ba2117038dd94870d1a30e +Subproject commit 69e0e8b6ed37d5068232b1c16cb0bdd9e71fb887 diff --git a/external/PointSampler b/external/PointSampler index 72ab37ff0c..0bfe2c6eb7 160000 --- a/external/PointSampler +++ b/external/PointSampler @@ -1 +1 @@ -Subproject commit 72ab37ff0c237022c487ad8e6a6363a454279419 +Subproject commit 0bfe2c6eb7b5405190a0cd5c29db0eac02949c15